Move to promises

Closes https://github.com/Chocobozzz/PeerTube/issues/74
This commit is contained in:
Chocobozzz 2017-07-05 13:26:25 +02:00
parent 5fe7e89831
commit 6fcd19ba73
88 changed files with 1980 additions and 2505 deletions

View file

@ -49,6 +49,7 @@
"async": "^2.0.0",
"bcrypt": "^1.0.2",
"bittorrent-tracker": "^9.0.0",
"bluebird": "^3.5.0",
"body-parser": "^1.12.4",
"concurrently": "^3.1.0",
"config": "^1.14.0",
@ -78,7 +79,7 @@
"scripty": "^1.5.0",
"sequelize": "4.0.0-2",
"ts-node": "^3.0.6",
"typescript": "^2.3.4",
"typescript": "^2.4.1",
"validator": "^7.0.0",
"winston": "^2.1.1",
"ws": "^2.0.0"
@ -95,7 +96,7 @@
"@types/mkdirp": "^0.3.29",
"@types/morgan": "^1.7.32",
"@types/multer": "^0.0.34",
"@types/node": "^7.0.18",
"@types/node": "^8.0.3",
"@types/request": "^0.0.44",
"@types/sequelize": "^4.0.55",
"@types/validator": "^6.2.0",

View file

@ -1,25 +1,28 @@
import * as eachSeries from 'async/eachSeries'
import * as rimraf from 'rimraf'
import * as Promise from 'bluebird'
import { CONFIG } from '../../../server/initializers/constants'
import { database as db } from '../../../server/initializers/database'
db.init(true, function () {
db.sequelize.drop().asCallback(function (err) {
if (err) throw err
db.init(true)
.then(() => {
return db.sequelize.drop()
})
.then(() => {
console.info('Tables of %s deleted.', CONFIG.DATABASE.DBNAME)
const STORAGE = CONFIG.STORAGE
eachSeries(Object.keys(STORAGE), function (storage, callbackEach) {
Promise.mapSeries(Object.keys(STORAGE), storage => {
const storageDir = STORAGE[storage]
rimraf(storageDir, function (err) {
console.info('%s deleted.', storageDir)
return callbackEach(err)
return new Promise((res, rej) => {
rimraf(storageDir, function (err) {
if (err) return rej(err)
console.info('%s deleted.', storageDir)
return res()
})
})
}, function () {
process.exit(0)
})
.then(() => process.exit(0))
})
})

View file

@ -11,13 +11,11 @@ if (program.user === undefined) {
process.exit(-1)
}
db.init(true, function () {
db.User.loadByUsername(program.user, function (err, user) {
if (err) {
console.error(err)
return
}
db.init(true)
.then(() => {
return db.User.loadByUsername(program.user)
})
.then(user => {
if (!user) {
console.error('User unknown.')
return
@ -40,15 +38,9 @@ db.init(true, function () {
rl.on('line', function (password) {
user.password = password
user.save().asCallback(function (err) {
if (err) {
console.error(err)
} else {
console.log('User password updated.')
}
process.exit(0)
})
user.save()
.then(() => console.log('User password updated.'))
.catch(err => console.error(err))
.finally(() => process.exit(0))
})
})
})

View file

@ -1,4 +1,4 @@
#!/usr/bin/env sh
#!/bin/bash
npm run build:server
@ -6,5 +6,5 @@ cd client || exit -1
npm test || exit -1
cd .. || exit -1
npm run tslint -- --type-check --project ./tsconfig.json -c ./tslint.json server.ts server/**/*.ts || exit -1
npm run tslint -- --type-check --project ./tsconfig.json -c ./tslint.json server.ts "server/**/*.ts" || exit -1
mocha --bail server/tests

View file

@ -5,33 +5,32 @@ import { CONFIG, STATIC_PATHS } from '../server/initializers/constants'
import { database as db } from '../server/initializers/database'
import { hasFriends } from '../server/lib/friends'
db.init(true, function () {
hasFriends(function (err, itHasFriends) {
if (err) throw err
db.init(true)
.then(() => {
return hasFriends()
})
.then(itHasFriends => {
if (itHasFriends === true) {
console.log('Cannot update host because you have friends!')
process.exit(-1)
}
console.log('Updating torrent files.')
db.Video.list(function (err, videos) {
if (err) throw err
videos.forEach(function (video) {
const torrentName = video.id + '.torrent'
const torrentPath = CONFIG.STORAGE.TORRENTS_DIR + torrentName
const filename = video.id + video.extname
const parsed = parseTorrent(readFileSync(torrentPath))
parsed.announce = [ CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOST + '/tracker/socket' ]
parsed.urlList = [ CONFIG.WEBSERVER.URL + STATIC_PATHS.WEBSEED + filename ]
const buf = parseTorrent.toTorrentFile(parsed)
writeFileSync(torrentPath, buf)
})
process.exit(0)
})
return db.Video.list()
})
.then(videos => {
videos.forEach(function (video) {
const torrentName = video.id + '.torrent'
const torrentPath = CONFIG.STORAGE.TORRENTS_DIR + torrentName
const filename = video.id + video.extname
const parsed = parseTorrent(readFileSync(torrentPath))
parsed.announce = [ CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOST + '/tracker/socket' ]
parsed.urlList = [ CONFIG.WEBSERVER.URL + STATIC_PATHS.WEBSEED + filename ]
const buf = parseTorrent.toTorrentFile(parsed)
writeFileSync(torrentPath, buf)
})
process.exit(0)
})
})

View file

@ -29,7 +29,7 @@ import { logger } from './server/helpers/logger'
import { API_VERSION, CONFIG } from './server/initializers/constants'
// Initialize database and models
import { database as db } from './server/initializers/database'
db.init(false, onDatabaseInitDone)
db.init(false).then(() => onDatabaseInitDone())
// ----------- Checker -----------
import { checkMissedConfig, checkFFmpeg, checkConfig } from './server/initializers/checker'
@ -38,11 +38,7 @@ const missed = checkMissedConfig()
if (missed.length !== 0) {
throw new Error('Miss some configurations keys : ' + missed)
}
checkFFmpeg(function (err) {
if (err) {
throw err
}
})
checkFFmpeg()
const errorMessage = checkConfig()
if (errorMessage !== null) {
@ -138,12 +134,11 @@ app.use(function (err, req, res, next) {
function onDatabaseInitDone () {
const port = CONFIG.LISTEN.PORT
// Run the migration scripts if needed
migrate(function (err) {
if (err) throw err
installApplication(function (err) {
if (err) throw err
migrate()
.then(() => {
return installApplication()
})
.then(() => {
// ----------- Make the server listening -----------
server.listen(port, function () {
// Activate the communication with friends
@ -156,5 +151,4 @@ function onDatabaseInitDone () {
logger.info('Webserver: %s', CONFIG.WEBSERVER.URL)
})
})
})
}

View file

@ -24,16 +24,17 @@ function getLocalClient (req: express.Request, res: express.Response, next: expr
return res.type('json').status(403).end()
}
db.OAuthClient.loadFirstClient(function (err, client) {
if (err) return next(err)
if (!client) return next(new Error('No client available.'))
db.OAuthClient.loadFirstClient()
.then(client => {
if (!client) throw new Error('No client available.')
const json: OAuthClientLocal = {
client_id: client.clientId,
client_secret: client.clientSecret
}
res.json(json)
})
const json: OAuthClientLocal = {
client_id: client.clientId,
client_secret: client.clientSecret
}
res.json(json)
})
.catch(err => next(err))
}
// ---------------------------------------------------------------------------

View file

@ -1,5 +1,4 @@
import * as express from 'express'
import { waterfall } from 'async'
import { database as db } from '../../initializers/database'
import { CONFIG } from '../../initializers'
@ -57,65 +56,39 @@ export {
function addPods (req: express.Request, res: express.Response, next: express.NextFunction) {
const informations = req.body
waterfall<string, Error>([
function addPod (callback) {
const pod = db.Pod.build(informations)
pod.save().asCallback(function (err, podCreated) {
// Be sure about the number of parameters for the callback
return callback(err, podCreated)
})
},
function sendMyVideos (podCreated: PodInstance, callback) {
sendOwnedVideosToPod(podCreated.id)
callback(null)
},
function fetchMyCertificate (callback) {
getMyPublicCert(function (err, cert) {
if (err) {
logger.error('Cannot read cert file.')
return callback(err)
}
return callback(null, cert)
})
}
], function (err, cert) {
if (err) return next(err)
return res.json({ cert: cert, email: CONFIG.ADMIN.EMAIL })
})
const pod = db.Pod.build(informations)
pod.save()
.then(podCreated => {
return sendOwnedVideosToPod(podCreated.id)
})
.then(() => {
return getMyPublicCert()
})
.then(cert => {
return res.json({ cert: cert, email: CONFIG.ADMIN.EMAIL })
})
.catch(err => next(err))
}
function listPods (req: express.Request, res: express.Response, next: express.NextFunction) {
db.Pod.list(function (err, podsList) {
if (err) return next(err)
res.json(getFormatedObjects(podsList, podsList.length))
})
db.Pod.list()
.then(podsList => res.json(getFormatedObjects(podsList, podsList.length)))
.catch(err => next(err))
}
function makeFriendsController (req: express.Request, res: express.Response, next: express.NextFunction) {
const hosts = req.body.hosts as string[]
makeFriends(hosts, function (err) {
if (err) {
logger.error('Could not make friends.', { error: err })
return
}
logger.info('Made friends!')
})
makeFriends(hosts)
.then(() => logger.info('Made friends!'))
.catch(err => logger.error('Could not make friends.', { error: err }))
// Don't wait the process that could be long
res.type('json').status(204).end()
}
function quitFriendsController (req: express.Request, res: express.Response, next: express.NextFunction) {
quitFriends(function (err) {
if (err) return next(err)
res.type('json').status(204).end()
})
quitFriends()
.then(() => res.type('json').status(204).end())
.catch(err => next(err))
}

View file

@ -1,5 +1,4 @@
import * as express from 'express'
import * as waterfall from 'async/waterfall'
import { database as db } from '../../../initializers/database'
import { checkSignature, signatureValidator } from '../../../middlewares'
@ -24,17 +23,10 @@ export {
function removePods (req: express.Request, res: express.Response, next: express.NextFunction) {
const host = req.body.signature.host
waterfall([
function loadPod (callback) {
db.Pod.loadByHost(host, callback)
},
function deletePod (pod, callback) {
pod.destroy().asCallback(callback)
}
], function (err) {
if (err) return next(err)
return res.type('json').status(204).end()
})
db.Pod.loadByHost(host)
.then(pod => {
return pod.destroy()
})
.then(() => res.type('json').status(204).end())
.catch(err => next(err))
}

View file

@ -1,6 +1,5 @@
import * as express from 'express'
import * as Sequelize from 'sequelize'
import { eachSeries, waterfall } from 'async'
import * as Promise from 'bluebird'
import { database as db } from '../../../initializers/database'
import {
@ -16,20 +15,14 @@ import {
remoteQaduVideosValidator,
remoteEventsVideosValidator
} from '../../../middlewares'
import {
logger,
commitTransaction,
retryTransactionWrapper,
rollbackTransaction,
startSerializableTransaction
} from '../../../helpers'
import { logger, retryTransactionWrapper } from '../../../helpers'
import { quickAndDirtyUpdatesVideoToFriends } from '../../../lib'
import { PodInstance, VideoInstance } from '../../../models'
const ENDPOINT_ACTIONS = REQUEST_ENDPOINT_ACTIONS[REQUEST_ENDPOINTS.VIDEOS]
// Functions to call when processing a remote request
const functionsHash = {}
const functionsHash: { [ id: string ]: (...args) => Promise<any> } = {}
functionsHash[ENDPOINT_ACTIONS.ADD] = addRemoteVideoRetryWrapper
functionsHash[ENDPOINT_ACTIONS.UPDATE] = updateRemoteVideoRetryWrapper
functionsHash[ENDPOINT_ACTIONS.REMOVE] = removeRemoteVideo
@ -72,20 +65,19 @@ function remoteVideos (req: express.Request, res: express.Response, next: expres
// We need to process in the same order to keep consistency
// TODO: optimization
eachSeries(requests, function (request: any, callbackEach) {
Promise.mapSeries(requests, (request: any) => {
const data = request.data
// Get the function we need to call in order to process the request
const fun = functionsHash[request.type]
if (fun === undefined) {
logger.error('Unkown remote request type %s.', request.type)
return callbackEach(null)
return
}
fun.call(this, data, fromPod, callbackEach)
}, function (err) {
if (err) logger.error('Error managing remote videos.', { error: err })
return fun.call(this, data, fromPod)
})
.catch(err => logger.error('Error managing remote videos.', { error: err }))
// We don't need to keep the other pod waiting
return res.type('json').status(204).end()
@ -95,13 +87,12 @@ function remoteVideosQadu (req: express.Request, res: express.Response, next: ex
const requests = req.body.data
const fromPod = res.locals.secure.pod
eachSeries(requests, function (request: any, callbackEach) {
Promise.mapSeries(requests, (request: any) => {
const videoData = request.data
quickAndDirtyUpdateVideoRetryWrapper(videoData, fromPod, callbackEach)
}, function (err) {
if (err) logger.error('Error managing remote videos.', { error: err })
return quickAndDirtyUpdateVideoRetryWrapper(videoData, fromPod)
})
.catch(err => logger.error('Error managing remote videos.', { error: err }))
return res.type('json').status(204).end()
}
@ -110,414 +101,303 @@ function remoteVideosEvents (req: express.Request, res: express.Response, next:
const requests = req.body.data
const fromPod = res.locals.secure.pod
eachSeries(requests, function (request: any, callbackEach) {
Promise.mapSeries(requests, (request: any) => {
const eventData = request.data
processVideosEventsRetryWrapper(eventData, fromPod, callbackEach)
}, function (err) {
if (err) logger.error('Error managing remote videos.', { error: err })
return processVideosEventsRetryWrapper(eventData, fromPod)
})
.catch(err => logger.error('Error managing remote videos.', { error: err }))
return res.type('json').status(204).end()
}
function processVideosEventsRetryWrapper (eventData: any, fromPod: PodInstance, finalCallback: (err: Error) => void) {
function processVideosEventsRetryWrapper (eventData: any, fromPod: PodInstance) {
const options = {
arguments: [ eventData, fromPod ],
errorMessage: 'Cannot process videos events with many retries.'
}
retryTransactionWrapper(processVideosEvents, options, finalCallback)
return retryTransactionWrapper(processVideosEvents, options)
}
function processVideosEvents (eventData: any, fromPod: PodInstance, finalCallback: (err: Error) => void) {
waterfall([
startSerializableTransaction,
function processVideosEvents (eventData: any, fromPod: PodInstance) {
function findVideo (t, callback) {
fetchOwnedVideo(eventData.remoteId, function (err, videoInstance) {
return callback(err, t, videoInstance)
})
},
return db.sequelize.transaction(t => {
return fetchOwnedVideo(eventData.remoteId)
.then(videoInstance => {
const options = { transaction: t }
function updateVideoIntoDB (t, videoInstance, callback) {
const options = { transaction: t }
let columnToUpdate
let qaduType
let columnToUpdate
let qaduType
switch (eventData.eventType) {
case REQUEST_VIDEO_EVENT_TYPES.VIEWS:
columnToUpdate = 'views'
qaduType = REQUEST_VIDEO_QADU_TYPES.VIEWS
break
switch (eventData.eventType) {
case REQUEST_VIDEO_EVENT_TYPES.VIEWS:
columnToUpdate = 'views'
qaduType = REQUEST_VIDEO_QADU_TYPES.VIEWS
break
case REQUEST_VIDEO_EVENT_TYPES.LIKES:
columnToUpdate = 'likes'
qaduType = REQUEST_VIDEO_QADU_TYPES.LIKES
break
case REQUEST_VIDEO_EVENT_TYPES.LIKES:
columnToUpdate = 'likes'
qaduType = REQUEST_VIDEO_QADU_TYPES.LIKES
break
case REQUEST_VIDEO_EVENT_TYPES.DISLIKES:
columnToUpdate = 'dislikes'
qaduType = REQUEST_VIDEO_QADU_TYPES.DISLIKES
break
case REQUEST_VIDEO_EVENT_TYPES.DISLIKES:
columnToUpdate = 'dislikes'
qaduType = REQUEST_VIDEO_QADU_TYPES.DISLIKES
break
default:
return callback(new Error('Unknown video event type.'))
}
const query = {}
query[columnToUpdate] = eventData.count
videoInstance.increment(query, options).asCallback(function (err) {
return callback(err, t, videoInstance, qaduType)
})
},
function sendQaduToFriends (t, videoInstance, qaduType, callback) {
const qadusParams = [
{
videoId: videoInstance.id,
type: qaduType
default:
throw new Error('Unknown video event type.')
}
]
quickAndDirtyUpdatesVideoToFriends(qadusParams, t, function (err) {
return callback(err, t)
const query = {}
query[columnToUpdate] = eventData.count
return videoInstance.increment(query, options).then(() => ({ videoInstance, qaduType }))
})
},
.then(({ videoInstance, qaduType }) => {
const qadusParams = [
{
videoId: videoInstance.id,
type: qaduType
}
]
commitTransaction
], function (err: Error, t: Sequelize.Transaction) {
if (err) {
logger.debug('Cannot process a video event.', { error: err })
return rollbackTransaction(err, t, finalCallback)
}
logger.info('Remote video event processed for video %s.', eventData.remoteId)
return finalCallback(null)
return quickAndDirtyUpdatesVideoToFriends(qadusParams, t)
})
})
.then(() => logger.info('Remote video event processed for video %s.', eventData.remoteId))
.catch(err => {
logger.debug('Cannot process a video event.', { error: err })
throw err
})
}
function quickAndDirtyUpdateVideoRetryWrapper (videoData: any, fromPod: PodInstance, finalCallback: (err: Error) => void) {
function quickAndDirtyUpdateVideoRetryWrapper (videoData: any, fromPod: PodInstance) {
const options = {
arguments: [ videoData, fromPod ],
errorMessage: 'Cannot update quick and dirty the remote video with many retries.'
}
retryTransactionWrapper(quickAndDirtyUpdateVideo, options, finalCallback)
return retryTransactionWrapper(quickAndDirtyUpdateVideo, options)
}
function quickAndDirtyUpdateVideo (videoData: any, fromPod: PodInstance, finalCallback: (err: Error) => void) {
function quickAndDirtyUpdateVideo (videoData: any, fromPod: PodInstance) {
let videoName
waterfall([
startSerializableTransaction,
return db.sequelize.transaction(t => {
return fetchRemoteVideo(fromPod.host, videoData.remoteId)
.then(videoInstance => {
const options = { transaction: t }
function findVideo (t, callback) {
fetchRemoteVideo(fromPod.host, videoData.remoteId, function (err, videoInstance) {
return callback(err, t, videoInstance)
videoName = videoInstance.name
if (videoData.views) {
videoInstance.set('views', videoData.views)
}
if (videoData.likes) {
videoInstance.set('likes', videoData.likes)
}
if (videoData.dislikes) {
videoInstance.set('dislikes', videoData.dislikes)
}
return videoInstance.save(options)
})
},
function updateVideoIntoDB (t, videoInstance, callback) {
const options = { transaction: t }
videoName = videoInstance.name
if (videoData.views) {
videoInstance.set('views', videoData.views)
}
if (videoData.likes) {
videoInstance.set('likes', videoData.likes)
}
if (videoData.dislikes) {
videoInstance.set('dislikes', videoData.dislikes)
}
videoInstance.save(options).asCallback(function (err) {
return callback(err, t)
})
},
commitTransaction
], function (err: Error, t: Sequelize.Transaction) {
if (err) {
logger.debug('Cannot quick and dirty update the remote video.', { error: err })
return rollbackTransaction(err, t, finalCallback)
}
logger.info('Remote video %s quick and dirty updated', videoName)
return finalCallback(null)
})
.then(() => logger.info('Remote video %s quick and dirty updated', videoName))
.catch(err => logger.debug('Cannot quick and dirty update the remote video.', { error: err }))
}
// Handle retries on fail
function addRemoteVideoRetryWrapper (videoToCreateData: any, fromPod: PodInstance, finalCallback: (err: Error) => void) {
function addRemoteVideoRetryWrapper (videoToCreateData: any, fromPod: PodInstance) {
const options = {
arguments: [ videoToCreateData, fromPod ],
errorMessage: 'Cannot insert the remote video with many retries.'
}
retryTransactionWrapper(addRemoteVideo, options, finalCallback)
return retryTransactionWrapper(addRemoteVideo, options)
}
function addRemoteVideo (videoToCreateData: any, fromPod: PodInstance, finalCallback: (err: Error) => void) {
function addRemoteVideo (videoToCreateData: any, fromPod: PodInstance) {
logger.debug('Adding remote video "%s".', videoToCreateData.remoteId)
waterfall([
return db.sequelize.transaction(t => {
return db.Video.loadByHostAndRemoteId(fromPod.host, videoToCreateData.remoteId)
.then(video => {
if (video) throw new Error('RemoteId and host pair is not unique.')
startSerializableTransaction,
function assertRemoteIdAndHostUnique (t, callback) {
db.Video.loadByHostAndRemoteId(fromPod.host, videoToCreateData.remoteId, function (err, video) {
if (err) return callback(err)
if (video) return callback(new Error('RemoteId and host pair is not unique.'))
return callback(null, t)
return undefined
})
},
.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
function findOrCreateAuthor (t, callback) {
const name = videoToCreateData.author
const podId = fromPod.id
// This author is from another pod so we do not associate a user
const userId = null
db.Author.findOrCreateAuthor(name, podId, userId, t, function (err, authorInstance) {
return callback(err, t, authorInstance)
return db.Author.findOrCreateAuthor(name, podId, userId, t)
})
},
.then(author => {
const tags = videoToCreateData.tags
function findOrCreateTags (t, author, callback) {
const tags = videoToCreateData.tags
db.Tag.findOrCreateTags(tags, t, function (err, tagInstances) {
return callback(err, t, author, tagInstances)
return db.Tag.findOrCreateTags(tags, t).then(tagInstances => ({ author, tagInstances }))
})
},
function createVideoObject (t, author, tagInstances, callback) {
const videoData = {
name: videoToCreateData.name,
remoteId: videoToCreateData.remoteId,
extname: videoToCreateData.extname,
infoHash: videoToCreateData.infoHash,
category: videoToCreateData.category,
licence: videoToCreateData.licence,
language: videoToCreateData.language,
nsfw: videoToCreateData.nsfw,
description: videoToCreateData.description,
authorId: author.id,
duration: videoToCreateData.duration,
createdAt: videoToCreateData.createdAt,
// FIXME: updatedAt does not seems to be considered by Sequelize
updatedAt: videoToCreateData.updatedAt,
views: videoToCreateData.views,
likes: videoToCreateData.likes,
dislikes: videoToCreateData.dislikes
}
const video = db.Video.build(videoData)
return callback(null, t, tagInstances, video)
},
function generateThumbnail (t, tagInstances, video, callback) {
db.Video.generateThumbnailFromData(video, videoToCreateData.thumbnailData, function (err) {
if (err) {
logger.error('Cannot generate thumbnail from data.', { error: err })
return callback(err)
.then(({ author, tagInstances }) => {
const videoData = {
name: videoToCreateData.name,
remoteId: videoToCreateData.remoteId,
extname: videoToCreateData.extname,
infoHash: videoToCreateData.infoHash,
category: videoToCreateData.category,
licence: videoToCreateData.licence,
language: videoToCreateData.language,
nsfw: videoToCreateData.nsfw,
description: videoToCreateData.description,
authorId: author.id,
duration: videoToCreateData.duration,
createdAt: videoToCreateData.createdAt,
// FIXME: updatedAt does not seems to be considered by Sequelize
updatedAt: videoToCreateData.updatedAt,
views: videoToCreateData.views,
likes: videoToCreateData.likes,
dislikes: videoToCreateData.dislikes
}
return callback(err, t, tagInstances, video)
const video = db.Video.build(videoData)
return { tagInstances, video }
})
},
function insertVideoIntoDB (t, tagInstances, video, callback) {
const options = {
transaction: t
}
video.save(options).asCallback(function (err, videoCreated) {
return callback(err, t, tagInstances, videoCreated)
.then(({ tagInstances, video }) => {
return db.Video.generateThumbnailFromData(video, videoToCreateData.thumbnailData).then(() => ({ tagInstances, video }))
})
},
.then(({ tagInstances, video }) => {
const options = {
transaction: t
}
function associateTagsToVideo (t, tagInstances, video, callback) {
const options = {
transaction: t
}
video.setTags(tagInstances, options).asCallback(function (err) {
return callback(err, t)
return video.save(options).then(videoCreated => ({ tagInstances, videoCreated }))
})
},
.then(({ tagInstances, videoCreated }) => {
const options = {
transaction: t
}
commitTransaction
], function (err: Error, t: Sequelize.Transaction) {
if (err) {
// This is just a debug because we will retry the insert
logger.debug('Cannot insert the remote video.', { error: err })
return rollbackTransaction(err, t, finalCallback)
}
logger.info('Remote video %s inserted.', videoToCreateData.name)
return finalCallback(null)
return videoCreated.setTags(tagInstances, options)
})
})
.then(() => logger.info('Remote video %s inserted.', videoToCreateData.name))
.catch(err => {
logger.debug('Cannot insert the remote video.', { error: err })
throw err
})
}
// Handle retries on fail
function updateRemoteVideoRetryWrapper (videoAttributesToUpdate: any, fromPod: PodInstance, finalCallback: (err: Error) => void) {
function updateRemoteVideoRetryWrapper (videoAttributesToUpdate: any, fromPod: PodInstance) {
const options = {
arguments: [ videoAttributesToUpdate, fromPod ],
errorMessage: 'Cannot update the remote video with many retries'
}
retryTransactionWrapper(updateRemoteVideo, options, finalCallback)
return retryTransactionWrapper(updateRemoteVideo, options)
}
function updateRemoteVideo (videoAttributesToUpdate: any, fromPod: PodInstance, finalCallback: (err: Error) => void) {
function updateRemoteVideo (videoAttributesToUpdate: any, fromPod: PodInstance) {
logger.debug('Updating remote video "%s".', videoAttributesToUpdate.remoteId)
waterfall([
return db.sequelize.transaction(t => {
return fetchRemoteVideo(fromPod.host, videoAttributesToUpdate.remoteId)
.then(videoInstance => {
const tags = videoAttributesToUpdate.tags
startSerializableTransaction,
function findVideo (t, callback) {
fetchRemoteVideo(fromPod.host, videoAttributesToUpdate.remoteId, function (err, videoInstance) {
return callback(err, t, videoInstance)
return db.Tag.findOrCreateTags(tags, t).then(tagInstances => ({ videoInstance, tagInstances }))
})
},
.then(({ videoInstance, tagInstances }) => {
const options = { transaction: t }
function findOrCreateTags (t, videoInstance, callback) {
const tags = videoAttributesToUpdate.tags
videoInstance.set('name', videoAttributesToUpdate.name)
videoInstance.set('category', videoAttributesToUpdate.category)
videoInstance.set('licence', videoAttributesToUpdate.licence)
videoInstance.set('language', videoAttributesToUpdate.language)
videoInstance.set('nsfw', videoAttributesToUpdate.nsfw)
videoInstance.set('description', videoAttributesToUpdate.description)
videoInstance.set('infoHash', videoAttributesToUpdate.infoHash)
videoInstance.set('duration', videoAttributesToUpdate.duration)
videoInstance.set('createdAt', videoAttributesToUpdate.createdAt)
videoInstance.set('updatedAt', videoAttributesToUpdate.updatedAt)
videoInstance.set('extname', videoAttributesToUpdate.extname)
videoInstance.set('views', videoAttributesToUpdate.views)
videoInstance.set('likes', videoAttributesToUpdate.likes)
videoInstance.set('dislikes', videoAttributesToUpdate.dislikes)
db.Tag.findOrCreateTags(tags, t, function (err, tagInstances) {
return callback(err, t, videoInstance, tagInstances)
return videoInstance.save(options).then(() => ({ videoInstance, tagInstances }))
})
},
.then(({ videoInstance, tagInstances }) => {
const options = { transaction: t }
function updateVideoIntoDB (t, videoInstance, tagInstances, callback) {
const options = { transaction: t }
videoInstance.set('name', videoAttributesToUpdate.name)
videoInstance.set('category', videoAttributesToUpdate.category)
videoInstance.set('licence', videoAttributesToUpdate.licence)
videoInstance.set('language', videoAttributesToUpdate.language)
videoInstance.set('nsfw', videoAttributesToUpdate.nsfw)
videoInstance.set('description', videoAttributesToUpdate.description)
videoInstance.set('infoHash', videoAttributesToUpdate.infoHash)
videoInstance.set('duration', videoAttributesToUpdate.duration)
videoInstance.set('createdAt', videoAttributesToUpdate.createdAt)
videoInstance.set('updatedAt', videoAttributesToUpdate.updatedAt)
videoInstance.set('extname', videoAttributesToUpdate.extname)
videoInstance.set('views', videoAttributesToUpdate.views)
videoInstance.set('likes', videoAttributesToUpdate.likes)
videoInstance.set('dislikes', videoAttributesToUpdate.dislikes)
videoInstance.save(options).asCallback(function (err) {
return callback(err, t, videoInstance, tagInstances)
return videoInstance.setTags(tagInstances, options)
})
},
function associateTagsToVideo (t, videoInstance, tagInstances, callback) {
const options = { transaction: t }
videoInstance.setTags(tagInstances, options).asCallback(function (err) {
return callback(err, t)
})
},
commitTransaction
], function (err: Error, t: Sequelize.Transaction) {
if (err) {
// This is just a debug because we will retry the insert
logger.debug('Cannot update the remote video.', { error: err })
return rollbackTransaction(err, t, finalCallback)
}
logger.info('Remote video %s updated', videoAttributesToUpdate.name)
return finalCallback(null)
})
.then(() => logger.info('Remote video %s updated', videoAttributesToUpdate.name))
.catch(err => {
// This is just a debug because we will retry the insert
logger.debug('Cannot update the remote video.', { error: err })
throw err
})
}
function removeRemoteVideo (videoToRemoveData: any, fromPod: PodInstance, callback: (err: Error) => void) {
function removeRemoteVideo (videoToRemoveData: any, fromPod: PodInstance) {
// We need the instance because we have to remove some other stuffs (thumbnail etc)
fetchRemoteVideo(fromPod.host, videoToRemoveData.remoteId, function (err, video) {
// Do not return the error, continue the process
if (err) return callback(null)
logger.debug('Removing remote video %s.', video.remoteId)
video.destroy().asCallback(function (err) {
// Do not return the error, continue the process
if (err) {
logger.error('Cannot remove remote video with id %s.', videoToRemoveData.remoteId, { error: err })
}
return callback(null)
return fetchRemoteVideo(fromPod.host, videoToRemoveData.remoteId)
.then(video => {
logger.debug('Removing remote video %s.', video.remoteId)
return video.destroy()
})
.catch(err => {
logger.debug('Could not fetch remote video.', { host: fromPod.host, remoteId: videoToRemoveData.remoteId, error: err })
})
})
}
function reportAbuseRemoteVideo (reportData: any, fromPod: PodInstance, callback: (err: Error) => void) {
fetchOwnedVideo(reportData.videoRemoteId, function (err, video) {
if (err || !video) {
if (!err) err = new Error('video not found')
function reportAbuseRemoteVideo (reportData: any, fromPod: PodInstance) {
return fetchOwnedVideo(reportData.videoRemoteId)
.then(video => {
logger.debug('Reporting remote abuse for video %s.', video.id)
logger.error('Cannot load video from id.', { error: err, id: reportData.videoRemoteId })
// Do not return the error, continue the process
return callback(null)
}
logger.debug('Reporting remote abuse for video %s.', video.id)
const videoAbuseData = {
reporterUsername: reportData.reporterUsername,
reason: reportData.reportReason,
reporterPodId: fromPod.id,
videoId: video.id
}
db.VideoAbuse.create(videoAbuseData).asCallback(function (err) {
if (err) {
logger.error('Cannot create remote abuse video.', { error: err })
const videoAbuseData = {
reporterUsername: reportData.reporterUsername,
reason: reportData.reportReason,
reporterPodId: fromPod.id,
videoId: video.id
}
return callback(null)
return db.VideoAbuse.create(videoAbuseData)
})
})
.catch(err => logger.error('Cannot create remote abuse video.', { error: err }))
}
function fetchOwnedVideo (id: string, callback: (err: Error, video?: VideoInstance) => void) {
db.Video.load(id, function (err, video) {
if (err || !video) {
if (!err) err = new Error('video not found')
function fetchOwnedVideo (id: string) {
return db.Video.load(id)
.then(video => {
if (!video) throw new Error('Video not found')
return video
})
.catch(err => {
logger.error('Cannot load owned video from id.', { error: err, id })
return callback(err)
}
return callback(null, video)
})
throw err
})
}
function fetchRemoteVideo (podHost: string, remoteId: string, callback: (err: Error, video?: VideoInstance) => void) {
db.Video.loadByHostAndRemoteId(podHost, remoteId, function (err, video) {
if (err || !video) {
if (!err) err = new Error('video not found')
function fetchRemoteVideo (podHost: string, remoteId: string) {
return db.Video.loadByHostAndRemoteId(podHost, remoteId)
.then(video => {
if (!video) throw new Error('Video not found')
return video
})
.catch(err => {
logger.error('Cannot load video from host and remote id.', { error: err, podHost, remoteId })
return callback(err)
}
return callback(null, video)
})
throw err
})
}

View file

@ -1,5 +1,5 @@
import * as express from 'express'
import { parallel } from 'async'
import * as Promise from 'bluebird'
import {
AbstractRequestScheduler,
@ -27,33 +27,27 @@ export {
// ---------------------------------------------------------------------------
function getRequestSchedulersStats (req: express.Request, res: express.Response, next: express.NextFunction) {
parallel({
Promise.props({
requestScheduler: buildRequestSchedulerStats(getRequestScheduler()),
requestVideoQaduScheduler: buildRequestSchedulerStats(getRequestVideoQaduScheduler()),
requestVideoEventScheduler: buildRequestSchedulerStats(getRequestVideoEventScheduler())
}, function (err, result) {
if (err) return next(err)
return res.json(result)
})
.then(result => res.json(result))
.catch(err => next(err))
}
// ---------------------------------------------------------------------------
function buildRequestSchedulerStats (requestScheduler: AbstractRequestScheduler) {
return function (callback) {
requestScheduler.remainingRequestsCount(function (err, count) {
if (err) return callback(err)
function buildRequestSchedulerStats (requestScheduler: AbstractRequestScheduler<any>) {
return requestScheduler.remainingRequestsCount().then(count => {
const result: RequestSchedulerStatsAttributes = {
totalRequests: count,
requestsLimitPods: requestScheduler.limitPods,
requestsLimitPerPod: requestScheduler.limitPerPod,
remainingMilliSeconds: requestScheduler.remainingMilliSeconds(),
milliSecondsInterval: requestScheduler.requestInterval
}
const result: RequestSchedulerStatsAttributes = {
totalRequests: count,
requestsLimitPods: requestScheduler.limitPods,
requestsLimitPerPod: requestScheduler.limitPerPod,
remainingMilliSeconds: requestScheduler.remainingMilliSeconds(),
milliSecondsInterval: requestScheduler.requestInterval
}
return callback(null, result)
})
}
return result
})
}

View file

@ -1,8 +1,7 @@
import * as express from 'express'
import { waterfall } from 'async'
import { database as db } from '../../initializers/database'
import { CONFIG, USER_ROLES } from '../../initializers'
import { USER_ROLES } from '../../initializers'
import { logger, getFormatedObjects } from '../../helpers'
import {
authenticate,
@ -87,78 +86,61 @@ function createUser (req: express.Request, res: express.Response, next: express.
role: USER_ROLES.USER
})
user.save().asCallback(function (err) {
if (err) return next(err)
return res.type('json').status(204).end()
})
user.save()
.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, function (err, user) {
if (err) return next(err)
return res.json(user.toFormatedJSON())
})
db.User.loadByUsername(res.locals.oauth.token.user.username)
.then(user => res.json(user.toFormatedJSON()))
.catch(err => next(err))
}
function getUserVideoRating (req: express.Request, res: express.Response, next: express.NextFunction) {
const videoId = '' + req.params.videoId
const userId = +res.locals.oauth.token.User.id
db.UserVideoRate.load(userId, videoId, null, function (err, ratingObj) {
if (err) return next(err)
const rating = ratingObj ? ratingObj.type : 'none'
const json: FormatedUserVideoRate = {
videoId,
rating
}
res.json(json)
})
db.UserVideoRate.load(userId, videoId, null)
.then(ratingObj => {
const rating = ratingObj ? ratingObj.type : 'none'
const json: FormatedUserVideoRate = {
videoId,
rating
}
res.json(json)
})
.catch(err => next(err))
}
function listUsers (req: express.Request, res: express.Response, next: express.NextFunction) {
db.User.listForApi(req.query.start, req.query.count, req.query.sort, function (err, usersList, usersTotal) {
if (err) return next(err)
res.json(getFormatedObjects(usersList, usersTotal))
})
db.User.listForApi(req.query.start, req.query.count, req.query.sort)
.then(resultList => {
res.json(getFormatedObjects(resultList.data, resultList.total))
})
.catch(err => next(err))
}
function removeUser (req: express.Request, res: express.Response, next: express.NextFunction) {
waterfall([
function loadUser (callback) {
db.User.loadById(req.params.id, callback)
},
function deleteUser (user, callback) {
user.destroy().asCallback(callback)
}
], function andFinally (err) {
if (err) {
db.User.loadById(req.params.id)
.then(user => user.destroy())
.then(() => res.sendStatus(204))
.catch(err => {
logger.error('Errors when removed the user.', { error: err })
return next(err)
}
return res.sendStatus(204)
})
})
}
function updateUser (req: express.Request, res: express.Response, next: express.NextFunction) {
db.User.loadByUsername(res.locals.oauth.token.user.username, function (err, user) {
if (err) return next(err)
db.User.loadByUsername(res.locals.oauth.token.user.username)
.then(user => {
if (req.body.password) user.password = req.body.password
if (req.body.displayNSFW !== undefined) user.displayNSFW = req.body.displayNSFW
if (req.body.password) user.password = req.body.password
if (req.body.displayNSFW !== undefined) user.displayNSFW = req.body.displayNSFW
user.save().asCallback(function (err) {
if (err) return next(err)
return res.sendStatus(204)
return user.save()
})
})
.then(() => res.sendStatus(204))
.catch(err => next(err))
}
function success (req: express.Request, res: express.Response, next: express.NextFunction) {

View file

@ -1,16 +1,11 @@
import * as express from 'express'
import * as Sequelize from 'sequelize'
import { waterfall } from 'async'
import { database as db } from '../../../initializers/database'
import * as friends from '../../../lib/friends'
import {
logger,
getFormatedObjects,
retryTransactionWrapper,
startSerializableTransaction,
commitTransaction,
rollbackTransaction
retryTransactionWrapper
} from '../../../helpers'
import {
authenticate,
@ -21,6 +16,7 @@ import {
setVideoAbusesSort,
setPagination
} from '../../../middlewares'
import { VideoInstance } from '../../../models'
const abuseVideoRouter = express.Router()
@ -48,11 +44,9 @@ export {
// ---------------------------------------------------------------------------
function listVideoAbuses (req: express.Request, res: express.Response, next: express.NextFunction) {
db.VideoAbuse.listForApi(req.query.start, req.query.count, req.query.sort, function (err, abusesList, abusesTotal) {
if (err) return next(err)
res.json(getFormatedObjects(abusesList, abusesTotal))
})
db.VideoAbuse.listForApi(req.query.start, req.query.count, req.query.sort)
.then(result => res.json(getFormatedObjects(result.data, result.total)))
.catch(err => next(err))
}
function reportVideoAbuseRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
@ -61,14 +55,12 @@ function reportVideoAbuseRetryWrapper (req: express.Request, res: express.Respon
errorMessage: 'Cannot report abuse to the video with many retries.'
}
retryTransactionWrapper(reportVideoAbuse, options, function (err) {
if (err) return next(err)
return res.type('json').status(204).end()
})
retryTransactionWrapper(reportVideoAbuse, options)
.then(() => res.type('json').status(204).end())
.catch(err => next(err))
}
function reportVideoAbuse (req: express.Request, res: express.Response, finalCallback: (err: Error) => void) {
function reportVideoAbuse (req: express.Request, res: express.Response) {
const videoInstance = res.locals.video
const reporterUsername = res.locals.oauth.token.User.username
@ -79,40 +71,26 @@ function reportVideoAbuse (req: express.Request, res: express.Response, finalCal
reporterPodId: null // This is our pod that reported this abuse
}
waterfall([
return db.sequelize.transaction(t => {
return db.VideoAbuse.create(abuse, { transaction: t })
.then(abuse => {
// We send the information to the destination pod
if (videoInstance.isOwned() === false) {
const reportData = {
reporterUsername,
reportReason: abuse.reason,
videoRemoteId: videoInstance.remoteId
}
startSerializableTransaction,
function createAbuse (t, callback) {
db.VideoAbuse.create(abuse).asCallback(function (err, abuse) {
return callback(err, t, abuse)
})
},
function sendToFriendsIfNeeded (t, abuse, callback) {
// We send the information to the destination pod
if (videoInstance.isOwned() === false) {
const reportData = {
reporterUsername,
reportReason: abuse.reason,
videoRemoteId: videoInstance.remoteId
return friends.reportAbuseVideoToFriend(reportData, videoInstance, t).then(() => videoInstance)
}
friends.reportAbuseVideoToFriend(reportData, videoInstance)
}
return callback(null, t)
},
commitTransaction
], function andFinally (err: Error, t: Sequelize.Transaction) {
if (err) {
logger.debug('Cannot update the video.', { error: err })
return rollbackTransaction(err, t, finalCallback)
}
logger.info('Abuse report for video %s created.', videoInstance.name)
return finalCallback(null)
return videoInstance
})
})
.then((videoInstance: VideoInstance) => logger.info('Abuse report for video %s created.', videoInstance.name))
.catch(err => {
logger.debug('Cannot update the video.', { error: err })
throw err
})
}

View file

@ -32,12 +32,10 @@ function addVideoToBlacklist (req: express.Request, res: express.Response, next:
videoId: videoInstance.id
}
db.BlacklistedVideo.create(toCreate).asCallback(function (err) {
if (err) {
db.BlacklistedVideo.create(toCreate)
.then(() => res.type('json').status(204).end())
.catch(err => {
logger.error('Errors when blacklisting video ', { error: err })
return next(err)
}
return res.type('json').status(204).end()
})
})
}

View file

@ -1,9 +1,7 @@
import * as express from 'express'
import * as Sequelize from 'sequelize'
import * as fs from 'fs'
import * as Promise from 'bluebird'
import * as multer from 'multer'
import * as path from 'path'
import { waterfall } from 'async'
import { database as db } from '../../../initializers/database'
import {
@ -35,13 +33,12 @@ import {
} from '../../../middlewares'
import {
logger,
commitTransaction,
retryTransactionWrapper,
rollbackTransaction,
startSerializableTransaction,
generateRandomString,
getFormatedObjects
getFormatedObjects,
renamePromise
} from '../../../helpers'
import { TagInstance } from '../../../models'
import { abuseVideoRouter } from './abuse'
import { blacklistRouter } from './blacklist'
@ -60,10 +57,15 @@ const storage = multer.diskStorage({
if (file.mimetype === 'video/webm') extension = 'webm'
else if (file.mimetype === 'video/mp4') extension = 'mp4'
else if (file.mimetype === 'video/ogg') extension = 'ogv'
generateRandomString(16, function (err, randomString) {
const fieldname = err ? undefined : randomString
cb(null, fieldname + '.' + extension)
})
generateRandomString(16)
.then(randomString => {
const filename = randomString
cb(null, filename + '.' + extension)
})
.catch(err => {
logger.error('Cannot generate random string for file name.', { error: err })
throw err
})
}
})
@ -144,125 +146,97 @@ function addVideoRetryWrapper (req: express.Request, res: express.Response, next
errorMessage: 'Cannot insert the video with many retries.'
}
retryTransactionWrapper(addVideo, options, function (err) {
if (err) return next(err)
// TODO : include Location of the new video -> 201
return res.type('json').status(204).end()
})
retryTransactionWrapper(addVideo, options)
.then(() => {
// TODO : include Location of the new video -> 201
res.type('json').status(204).end()
})
.catch(err => next(err))
}
function addVideo (req: express.Request, res: express.Response, videoFile: Express.Multer.File, finalCallback: (err: Error) => void) {
function addVideo (req: express.Request, res: express.Response, videoFile: Express.Multer.File) {
const videoInfos = req.body
waterfall([
return db.sequelize.transaction(t => {
const user = res.locals.oauth.token.User
startSerializableTransaction,
const name = user.username
// null because it is OUR pod
const podId = null
const userId = user.id
function findOrCreateAuthor (t, callback) {
const user = res.locals.oauth.token.User
return db.Author.findOrCreateAuthor(name, podId, userId, t)
.then(author => {
const tags = videoInfos.tags
if (!tags) return { author, tagInstances: undefined }
const name = user.username
// null because it is OUR pod
const podId = null
const userId = user.id
db.Author.findOrCreateAuthor(name, podId, userId, t, function (err, authorInstance) {
return callback(err, t, authorInstance)
return db.Tag.findOrCreateTags(tags, t).then(tagInstances => ({ author, tagInstances }))
})
},
.then(({ author, tagInstances }) => {
const videoData = {
name: videoInfos.name,
remoteId: null,
extname: path.extname(videoFile.filename),
category: videoInfos.category,
licence: videoInfos.licence,
language: videoInfos.language,
nsfw: videoInfos.nsfw,
description: videoInfos.description,
duration: videoFile['duration'], // duration was added by a previous middleware
authorId: author.id
}
function findOrCreateTags (t, author, callback) {
const tags = videoInfos.tags
db.Tag.findOrCreateTags(tags, t, function (err, tagInstances) {
return callback(err, t, author, tagInstances)
const video = db.Video.build(videoData)
return { author, tagInstances, video }
})
},
.then(({ author, tagInstances, video }) => {
const videoDir = CONFIG.STORAGE.VIDEOS_DIR
const source = path.join(videoDir, videoFile.filename)
const destination = path.join(videoDir, video.getVideoFilename())
function createVideoObject (t, author, tagInstances, callback) {
const videoData = {
name: videoInfos.name,
remoteId: null,
extname: path.extname(videoFile.filename),
category: videoInfos.category,
licence: videoInfos.licence,
language: videoInfos.language,
nsfw: videoInfos.nsfw,
description: videoInfos.description,
duration: videoFile['duration'], // duration was added by a previous middleware
authorId: author.id
}
const video = db.Video.build(videoData)
return callback(null, t, author, tagInstances, video)
},
// Set the videoname the same as the id
function renameVideoFile (t, author, tagInstances, video, callback) {
const videoDir = CONFIG.STORAGE.VIDEOS_DIR
const source = path.join(videoDir, videoFile.filename)
const destination = path.join(videoDir, video.getVideoFilename())
fs.rename(source, destination, function (err) {
if (err) return callback(err)
// This is important in case if there is another attempt
videoFile.filename = video.getVideoFilename()
return callback(null, t, author, tagInstances, video)
return renamePromise(source, destination)
.then(() => {
// This is important in case if there is another attempt in the retry process
videoFile.filename = video.getVideoFilename()
return { author, tagInstances, video }
})
})
},
.then(({ author, tagInstances, video }) => {
const options = { transaction: t }
function insertVideoIntoDB (t, author, tagInstances, video, callback) {
const options = { transaction: t }
return video.save(options)
.then(videoCreated => {
// Do not forget to add Author informations to the created video
videoCreated.Author = author
// Add tags association
video.save(options).asCallback(function (err, videoCreated) {
if (err) return callback(err)
// Do not forget to add Author informations to the created video
videoCreated.Author = author
return callback(err, t, tagInstances, videoCreated)
return { tagInstances, video: videoCreated }
})
})
},
.then(({ tagInstances, video }) => {
if (!tagInstances) return video
function associateTagsToVideo (t, tagInstances, video, callback) {
const options = { transaction: t }
video.setTags(tagInstances, options).asCallback(function (err) {
video.Tags = tagInstances
return callback(err, t, video)
const options = { transaction: t }
return video.setTags(tagInstances, options)
.then(() => {
video.Tags = tagInstances
return video
})
})
},
.then(video => {
// Let transcoding job send the video to friends because the videofile extension might change
if (CONFIG.TRANSCODING.ENABLED === true) return undefined
function sendToFriends (t, video, callback) {
// Let transcoding job send the video to friends because the videofile extension might change
if (CONFIG.TRANSCODING.ENABLED === true) return callback(null, t)
video.toAddRemoteJSON(function (err, remoteVideo) {
if (err) return callback(err)
// Now we'll add the video's meta data to our friends
addVideoToFriends(remoteVideo, t, function (err) {
return callback(err, t)
})
return video.toAddRemoteJSON()
.then(remoteVideo => {
// Now we'll add the video's meta data to our friends
return addVideoToFriends(remoteVideo, t)
})
})
},
commitTransaction
], function andFinally (err: Error, t: Sequelize.Transaction) {
if (err) {
// This is just a debug because we will retry the insert
logger.debug('Cannot insert the video.', { error: err })
return rollbackTransaction(err, t, finalCallback)
}
logger.info('Video with name %s created.', videoInfos.name)
return finalCallback(null)
})
.then(() => logger.info('Video with name %s created.', videoInfos.name))
.catch((err: Error) => {
logger.debug('Cannot insert the video.', { error: err.stack })
throw err
})
}
@ -272,92 +246,75 @@ function updateVideoRetryWrapper (req: express.Request, res: express.Response, n
errorMessage: 'Cannot update the video with many retries.'
}
retryTransactionWrapper(updateVideo, options, function (err) {
if (err) return next(err)
// TODO : include Location of the new video -> 201
return res.type('json').status(204).end()
})
retryTransactionWrapper(updateVideo, options)
.then(() => {
// TODO : include Location of the new video -> 201
return res.type('json').status(204).end()
})
.catch(err => next(err))
}
function updateVideo (req: express.Request, res: express.Response, finalCallback: (err: Error) => void) {
function updateVideo (req: express.Request, res: express.Response) {
const videoInstance = res.locals.video
const videoFieldsSave = videoInstance.toJSON()
const videoInfosToUpdate = req.body
waterfall([
startSerializableTransaction,
function findOrCreateTags (t, callback) {
if (videoInfosToUpdate.tags) {
db.Tag.findOrCreateTags(videoInfosToUpdate.tags, t, function (err, tagInstances) {
return callback(err, t, tagInstances)
})
} else {
return callback(null, t, null)
}
},
function updateVideoIntoDB (t, tagInstances, callback) {
const options = {
transaction: t
}
if (videoInfosToUpdate.name !== undefined) videoInstance.set('name', videoInfosToUpdate.name)
if (videoInfosToUpdate.category !== undefined) videoInstance.set('category', videoInfosToUpdate.category)
if (videoInfosToUpdate.licence !== undefined) videoInstance.set('licence', videoInfosToUpdate.licence)
if (videoInfosToUpdate.language !== undefined) videoInstance.set('language', videoInfosToUpdate.language)
if (videoInfosToUpdate.nsfw !== undefined) videoInstance.set('nsfw', videoInfosToUpdate.nsfw)
if (videoInfosToUpdate.description !== undefined) videoInstance.set('description', videoInfosToUpdate.description)
videoInstance.save(options).asCallback(function (err) {
return callback(err, t, tagInstances)
})
},
function associateTagsToVideo (t, tagInstances, callback) {
if (tagInstances) {
const options = { transaction: t }
videoInstance.setTags(tagInstances, options).asCallback(function (err) {
videoInstance.Tags = tagInstances
return callback(err, t)
})
} else {
return callback(null, t)
}
},
function sendToFriends (t, callback) {
const json = videoInstance.toUpdateRemoteJSON()
// Now we'll update the video's meta data to our friends
updateVideoToFriends(json, t, function (err) {
return callback(err, t)
})
},
commitTransaction
], function andFinally (err: Error, t: Sequelize.Transaction) {
if (err) {
logger.debug('Cannot update the video.', { error: 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(videoFieldsSave).forEach(function (key) {
const value = videoFieldsSave[key]
videoInstance.set(key, value)
})
return rollbackTransaction(err, t, finalCallback)
return db.sequelize.transaction(t => {
let tagsPromise: Promise<TagInstance[]>
if (!videoInfosToUpdate.tags) {
tagsPromise = Promise.resolve(null)
} else {
tagsPromise = db.Tag.findOrCreateTags(videoInfosToUpdate.tags, t)
}
return tagsPromise
.then(tagInstances => {
const options = {
transaction: t
}
if (videoInfosToUpdate.name !== undefined) videoInstance.set('name', videoInfosToUpdate.name)
if (videoInfosToUpdate.category !== undefined) videoInstance.set('category', videoInfosToUpdate.category)
if (videoInfosToUpdate.licence !== undefined) videoInstance.set('licence', videoInfosToUpdate.licence)
if (videoInfosToUpdate.language !== undefined) videoInstance.set('language', videoInfosToUpdate.language)
if (videoInfosToUpdate.nsfw !== undefined) videoInstance.set('nsfw', videoInfosToUpdate.nsfw)
if (videoInfosToUpdate.description !== undefined) videoInstance.set('description', videoInfosToUpdate.description)
return videoInstance.save(options).then(() => tagInstances)
})
.then(tagInstances => {
if (!tagInstances) return
const options = { transaction: t }
return videoInstance.setTags(tagInstances, options)
.then(() => {
videoInstance.Tags = tagInstances
return
})
})
.then(() => {
const json = videoInstance.toUpdateRemoteJSON()
// Now we'll update the video's meta data to our friends
return updateVideoToFriends(json, t)
})
})
.then(() => {
logger.info('Video with name %s updated.', videoInstance.name)
return finalCallback(null)
})
.catch(err => {
logger.debug('Cannot update the video.', { error: 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(videoFieldsSave).forEach(function (key) {
const value = videoFieldsSave[key]
videoInstance.set(key, value)
})
throw err
})
}
@ -366,20 +323,17 @@ function getVideo (req: express.Request, res: express.Response, next: express.Ne
if (videoInstance.isOwned()) {
// The increment is done directly in the database, not using the instance value
videoInstance.increment('views').asCallback(function (err) {
if (err) {
logger.error('Cannot add view to video %d.', videoInstance.id)
return
}
// FIXME: make a real view system
// For example, only add a view when a user watch a video during 30s etc
const qaduParams = {
videoId: videoInstance.id,
type: REQUEST_VIDEO_QADU_TYPES.VIEWS
}
quickAndDirtyUpdateVideoToFriends(qaduParams)
})
videoInstance.increment('views')
.then(() => {
// FIXME: make a real view system
// For example, only add a view when a user watch a video during 30s etc
const qaduParams = {
videoId: videoInstance.id,
type: REQUEST_VIDEO_QADU_TYPES.VIEWS
}
return quickAndDirtyUpdateVideoToFriends(qaduParams)
})
.catch(err => logger.error('Cannot add view to video %d.', videoInstance.id, { error: err }))
} else {
// Just send the event to our friends
const eventParams = {
@ -394,33 +348,24 @@ function getVideo (req: express.Request, res: express.Response, next: express.Ne
}
function listVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
db.Video.listForApi(req.query.start, req.query.count, req.query.sort, function (err, videosList, videosTotal) {
if (err) return next(err)
res.json(getFormatedObjects(videosList, videosTotal))
})
db.Video.listForApi(req.query.start, req.query.count, req.query.sort)
.then(result => res.json(getFormatedObjects(result.data, result.total)))
.catch(err => next(err))
}
function removeVideo (req: express.Request, res: express.Response, next: express.NextFunction) {
const videoInstance = res.locals.video
videoInstance.destroy().asCallback(function (err) {
if (err) {
videoInstance.destroy()
.then(() => res.type('json').status(204).end())
.catch(err => {
logger.error('Errors when removed the video.', { error: err })
return next(err)
}
return res.type('json').status(204).end()
})
})
}
function searchVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
db.Video.searchAndPopulateAuthorAndPodAndTags(
req.params.value, req.query.field, req.query.start, req.query.count, req.query.sort,
function (err, videosList, videosTotal) {
if (err) return next(err)
res.json(getFormatedObjects(videosList, videosTotal))
}
)
db.Video.searchAndPopulateAuthorAndPodAndTags(req.params.value, req.query.field, req.query.start, req.query.count, req.query.sort)
.then(result => res.json(getFormatedObjects(result.data, result.total)))
.catch(err => next(err))
}

View file

@ -1,14 +1,9 @@
import * as express from 'express'
import * as Sequelize from 'sequelize'
import { waterfall } from 'async'
import { database as db } from '../../../initializers/database'
import {
logger,
retryTransactionWrapper,
startSerializableTransaction,
commitTransaction,
rollbackTransaction
retryTransactionWrapper
} from '../../../helpers'
import {
VIDEO_RATE_TYPES,
@ -46,137 +41,109 @@ function rateVideoRetryWrapper (req: express.Request, res: express.Response, nex
errorMessage: 'Cannot update the user video rate.'
}
retryTransactionWrapper(rateVideo, options, function (err) {
if (err) return next(err)
return res.type('json').status(204).end()
})
retryTransactionWrapper(rateVideo, options)
.then(() => res.type('json').status(204).end())
.catch(err => next(err))
}
function rateVideo (req: express.Request, res: express.Response, finalCallback: (err: Error) => void) {
function rateVideo (req: express.Request, res: express.Response) {
const rateType = req.body.rating
const videoInstance = res.locals.video
const userInstance = res.locals.oauth.token.User
waterfall([
startSerializableTransaction,
return db.sequelize.transaction(t => {
return db.UserVideoRate.load(userInstance.id, videoInstance.id, t)
.then(previousRate => {
const options = { transaction: t }
function findPreviousRate (t, callback) {
db.UserVideoRate.load(userInstance.id, videoInstance.id, t, function (err, previousRate) {
return callback(err, t, previousRate)
let likesToIncrement = 0
let dislikesToIncrement = 0
if (rateType === VIDEO_RATE_TYPES.LIKE) likesToIncrement++
else if (rateType === VIDEO_RATE_TYPES.DISLIKE) dislikesToIncrement++
// There was a previous rate, update it
if (previousRate) {
// We will remove the previous rate, so we will need to remove it from the video attribute
if (previousRate.type === VIDEO_RATE_TYPES.LIKE) likesToIncrement--
else if (previousRate.type === VIDEO_RATE_TYPES.DISLIKE) dislikesToIncrement--
previousRate.type = rateType
return previousRate.save(options).then(() => ({ t, likesToIncrement, dislikesToIncrement }))
} else { // There was not a previous rate, insert a new one
const query = {
userId: userInstance.id,
videoId: videoInstance.id,
type: rateType
}
return db.UserVideoRate.create(query, options).then(() => ({ likesToIncrement, dislikesToIncrement }))
}
})
},
function insertUserRateIntoDB (t, previousRate, callback) {
const options = { transaction: t }
let likesToIncrement = 0
let dislikesToIncrement = 0
if (rateType === VIDEO_RATE_TYPES.LIKE) likesToIncrement++
else if (rateType === VIDEO_RATE_TYPES.DISLIKE) dislikesToIncrement++
// There was a previous rate, update it
if (previousRate) {
// We will remove the previous rate, so we will need to remove it from the video attribute
if (previousRate.type === VIDEO_RATE_TYPES.LIKE) likesToIncrement--
else if (previousRate.type === VIDEO_RATE_TYPES.DISLIKE) dislikesToIncrement--
previousRate.type = rateType
previousRate.save(options).asCallback(function (err) {
return callback(err, t, likesToIncrement, dislikesToIncrement)
})
} else { // There was not a previous rate, insert a new one
const query = {
userId: userInstance.id,
videoId: videoInstance.id,
type: rateType
.then(({ likesToIncrement, dislikesToIncrement }) => {
const options = { transaction: t }
const incrementQuery = {
likes: likesToIncrement,
dislikes: dislikesToIncrement
}
db.UserVideoRate.create(query, options).asCallback(function (err) {
return callback(err, t, likesToIncrement, dislikesToIncrement)
})
}
},
function updateVideoAttributeDB (t, likesToIncrement, dislikesToIncrement, callback) {
const options = { transaction: t }
const incrementQuery = {
likes: likesToIncrement,
dislikes: dislikesToIncrement
}
// Even if we do not own the video we increment the attributes
// It is usefull for the user to have a feedback
videoInstance.increment(incrementQuery, options).asCallback(function (err) {
return callback(err, t, likesToIncrement, dislikesToIncrement)
// Even if we do not own the video we increment the attributes
// It is usefull for the user to have a feedback
return videoInstance.increment(incrementQuery, options).then(() => ({ likesToIncrement, dislikesToIncrement }))
})
},
.then(({ likesToIncrement, dislikesToIncrement }) => {
// No need for an event type, we own the video
if (videoInstance.isOwned()) return { likesToIncrement, dislikesToIncrement }
function sendEventsToFriendsIfNeeded (t, likesToIncrement, dislikesToIncrement, callback) {
// No need for an event type, we own the video
if (videoInstance.isOwned()) return callback(null, t, likesToIncrement, dislikesToIncrement)
const eventsParams = []
const eventsParams = []
if (likesToIncrement !== 0) {
eventsParams.push({
videoId: videoInstance.id,
type: REQUEST_VIDEO_EVENT_TYPES.LIKES,
count: likesToIncrement
})
}
if (likesToIncrement !== 0) {
eventsParams.push({
videoId: videoInstance.id,
type: REQUEST_VIDEO_EVENT_TYPES.LIKES,
count: likesToIncrement
})
}
if (dislikesToIncrement !== 0) {
eventsParams.push({
videoId: videoInstance.id,
type: REQUEST_VIDEO_EVENT_TYPES.DISLIKES,
count: dislikesToIncrement
})
}
if (dislikesToIncrement !== 0) {
eventsParams.push({
videoId: videoInstance.id,
type: REQUEST_VIDEO_EVENT_TYPES.DISLIKES,
count: dislikesToIncrement
})
}
addEventsToRemoteVideo(eventsParams, t, function (err) {
return callback(err, t, likesToIncrement, dislikesToIncrement)
return addEventsToRemoteVideo(eventsParams, t).then(() => ({ likesToIncrement, dislikesToIncrement }))
})
},
.then(({ likesToIncrement, dislikesToIncrement }) => {
// We do not own the video, there is no need to send a quick and dirty update to friends
// Our rate was already sent by the addEvent function
if (videoInstance.isOwned() === false) return undefined
function sendQaduToFriendsIfNeeded (t, likesToIncrement, dislikesToIncrement, callback) {
// We do not own the video, there is no need to send a quick and dirty update to friends
// Our rate was already sent by the addEvent function
if (videoInstance.isOwned() === false) return callback(null, t)
const qadusParams = []
const qadusParams = []
if (likesToIncrement !== 0) {
qadusParams.push({
videoId: videoInstance.id,
type: REQUEST_VIDEO_QADU_TYPES.LIKES
})
}
if (likesToIncrement !== 0) {
qadusParams.push({
videoId: videoInstance.id,
type: REQUEST_VIDEO_QADU_TYPES.LIKES
})
}
if (dislikesToIncrement !== 0) {
qadusParams.push({
videoId: videoInstance.id,
type: REQUEST_VIDEO_QADU_TYPES.DISLIKES
})
}
if (dislikesToIncrement !== 0) {
qadusParams.push({
videoId: videoInstance.id,
type: REQUEST_VIDEO_QADU_TYPES.DISLIKES
})
}
quickAndDirtyUpdatesVideoToFriends(qadusParams, t, function (err) {
return callback(err, t)
return quickAndDirtyUpdatesVideoToFriends(qadusParams, t)
})
},
commitTransaction
], function (err: Error, t: Sequelize.Transaction) {
if (err) {
// This is just a debug because we will retry the insert
logger.debug('Cannot add the user video rate.', { error: err })
return rollbackTransaction(err, t, finalCallback)
}
logger.info('User video rate for video %s of user %s updated.', videoInstance.name, userInstance.username)
return finalCallback(null)
})
.then(() => logger.info('User video rate for video %s of user %s updated.', videoInstance.name, userInstance.username))
.catch(err => {
// This is just a debug because we will retry the insert
logger.debug('Cannot add the user video rate.', { error: err })
throw err
})
}

View file

@ -1,8 +1,7 @@
import { parallel } from 'async'
import * as express from 'express'
import * as fs from 'fs'
import { join } from 'path'
import * as validator from 'validator'
import * as Promise from 'bluebird'
import { database as db } from '../initializers/database'
import {
@ -11,7 +10,7 @@ import {
STATIC_PATHS,
STATIC_MAX_AGE
} from '../initializers'
import { root } from '../helpers'
import { root, readFileBufferPromise } from '../helpers'
import { VideoInstance } from '../models'
const clientsRouter = express.Router()
@ -95,19 +94,15 @@ function generateWatchHtmlPage (req: express.Request, res: express.Response, nex
// Let Angular application handle errors
if (!validator.isUUID(videoId, 4)) return res.sendFile(indexPath)
parallel({
file: function (callback) {
fs.readFile(indexPath, callback)
},
Promise.all([
readFileBufferPromise(indexPath),
db.Video.loadAndPopulateAuthorAndPodAndTags(videoId)
])
.then(([ file, video ]) => {
file = file as Buffer
video = video as VideoInstance
video: function (callback) {
db.Video.loadAndPopulateAuthorAndPodAndTags(videoId, callback)
}
}, function (err: Error, result: { file: Buffer, video: VideoInstance }) {
if (err) return next(err)
const html = result.file.toString()
const video = result.video
const html = file.toString()
// Let Angular application handle errors
if (!video) return res.sendFile(indexPath)
@ -115,4 +110,5 @@ function generateWatchHtmlPage (req: express.Request, res: express.Response, nex
const htmlStringPageWithTags = addOpenGraphTags(html, video)
res.set('Content-Type', 'text/html; charset=UTF-8').send(htmlStringPageWithTags)
})
.catch(err => next(err))
}

View file

@ -4,6 +4,20 @@
*/
import { join } from 'path'
import { pseudoRandomBytes } from 'crypto'
import {
readdir,
readFile,
rename,
unlink,
writeFile,
access
} from 'fs'
import * as mkdirp from 'mkdirp'
import * as bcrypt from 'bcrypt'
import * as createTorrent from 'create-torrent'
import * as openssl from 'openssl-wrapper'
import * as Promise from 'bluebird'
function isTestInstance () {
return process.env.NODE_ENV === 'test'
@ -14,9 +28,82 @@ function root () {
return join(__dirname, '..', '..', '..')
}
function promisify0<A> (func: (cb: (err: any, result: A) => void) => void): () => Promise<A> {
return function promisified (): Promise<A> {
return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
func.apply(null, [ (err: any, res: A) => err ? reject(err) : resolve(res) ])
})
}
}
// Thanks to https://gist.github.com/kumasento/617daa7e46f13ecdd9b2
function promisify1<T, A> (func: (arg: T, cb: (err: any, result: A) => void) => void): (arg: T) => Promise<A> {
return function promisified (arg: T): Promise<A> {
return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
func.apply(null, [ arg, (err: any, res: A) => err ? reject(err) : resolve(res) ])
})
}
}
function promisify1WithVoid<T> (func: (arg: T, cb: (err: any) => void) => void): (arg: T) => Promise<void> {
return function promisified (arg: T): Promise<void> {
return new Promise<void>((resolve: () => void, reject: (err: any) => void) => {
func.apply(null, [ arg, (err: any) => err ? reject(err) : resolve() ])
})
}
}
function promisify2<T, U, A> (func: (arg1: T, arg2: U, cb: (err: any, result: A) => void) => void): (arg1: T, arg2: U) => Promise<A> {
return function promisified (arg1: T, arg2: U): Promise<A> {
return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
func.apply(null, [ arg1, arg2, (err: any, res: A) => err ? reject(err) : resolve(res) ])
})
}
}
function promisify2WithVoid<T, U> (func: (arg1: T, arg2: U, cb: (err: any) => void) => void): (arg1: T, arg2: U) => Promise<void> {
return function promisified (arg1: T, arg2: U): Promise<void> {
return new Promise<void>((resolve: () => void, reject: (err: any) => void) => {
func.apply(null, [ arg1, arg2, (err: any) => err ? reject(err) : resolve() ])
})
}
}
const readFilePromise = promisify2<string, string, string>(readFile)
const readFileBufferPromise = promisify1<string, Buffer>(readFile)
const unlinkPromise = promisify1WithVoid<string>(unlink)
const renamePromise = promisify2WithVoid<string, string>(rename)
const writeFilePromise = promisify2<string, any, void>(writeFile)
const readdirPromise = promisify1<string, string[]>(readdir)
const mkdirpPromise = promisify1<string, string>(mkdirp)
const pseudoRandomBytesPromise = promisify1<number, Buffer>(pseudoRandomBytes)
const accessPromise = promisify1WithVoid<string|Buffer>(access)
const opensslExecPromise = promisify2WithVoid<string, any>(openssl.exec)
const bcryptComparePromise = promisify2<any, string, boolean>(bcrypt.compare)
const bcryptGenSaltPromise = promisify1<number, string>(bcrypt.genSalt)
const bcryptHashPromise = promisify2<any, string|number, string>(bcrypt.hash)
const createTorrentPromise = promisify2<string, any, any>(createTorrent)
// ---------------------------------------------------------------------------
export {
isTestInstance,
root
root,
promisify0,
promisify1,
readdirPromise,
readFilePromise,
readFileBufferPromise,
unlinkPromise,
renamePromise,
writeFilePromise,
mkdirpPromise,
pseudoRandomBytesPromise,
accessPromise,
opensslExecPromise,
bcryptComparePromise,
bcryptGenSaltPromise,
bcryptHashPromise,
createTorrentPromise
}

View file

@ -1,70 +1,45 @@
import * as Sequelize from 'sequelize'
// TODO: import from ES6 when retry typing file will include errorFilter function
import * as retry from 'async/retry'
import * as Promise from 'bluebird'
import { database as db } from '../initializers/database'
import { logger } from './logger'
function commitTransaction (t: Sequelize.Transaction, callback: (err: Error) => void) {
return t.commit().asCallback(callback)
}
function rollbackTransaction (err: Error, t: Sequelize.Transaction, callback: (err: Error) => void) {
// Try to rollback transaction
if (t) {
// Do not catch err, report the original one
t.rollback().asCallback(function () {
return callback(err)
})
} else {
return callback(err)
}
}
type RetryTransactionWrapperOptions = { errorMessage: string, arguments?: any[] }
function retryTransactionWrapper (functionToRetry: Function, options: RetryTransactionWrapperOptions, finalCallback: Function) {
function retryTransactionWrapper (functionToRetry: (... args) => Promise<any>, options: RetryTransactionWrapperOptions) {
const args = options.arguments ? options.arguments : []
transactionRetryer(
return transactionRetryer(
function (callback) {
return functionToRetry.apply(this, args.concat([ callback ]))
},
function (err) {
if (err) {
logger.error(options.errorMessage, { error: err })
}
// Do not return the error, continue the process
return finalCallback(null)
functionToRetry.apply(this, args)
.then(result => callback(null, result))
.catch(err => callback(err))
}
)
.catch(err => {
// Do not throw the error, continue the process
logger.error(options.errorMessage, { error: err })
})
}
function transactionRetryer (func: Function, callback: (err: Error) => void) {
retry({
times: 5,
function transactionRetryer (func: Function) {
return new Promise((res, rej) => {
retry({
times: 5,
errorFilter: function (err) {
const willRetry = (err.name === 'SequelizeDatabaseError')
logger.debug('Maybe retrying the transaction function.', { willRetry })
return willRetry
}
}, func, callback)
}
function startSerializableTransaction (callback: (err: Error, t: Sequelize.Transaction) => void) {
db.sequelize.transaction(/* { isolationLevel: 'SERIALIZABLE' } */).asCallback(function (err, t) {
// We force to return only two parameters
return callback(err, t)
errorFilter: function (err) {
const willRetry = (err.name === 'SequelizeDatabaseError')
logger.debug('Maybe retrying the transaction function.', { willRetry })
return willRetry
}
}, func, function (err) {
err ? rej(err) : res()
})
})
}
// ---------------------------------------------------------------------------
export {
commitTransaction,
retryTransactionWrapper,
rollbackTransaction,
startSerializableTransaction,
transactionRetryer
}

View file

@ -1,7 +1,5 @@
import * as crypto from 'crypto'
import * as bcrypt from 'bcrypt'
import * as fs from 'fs'
import * as openssl from 'openssl-wrapper'
import { join } from 'path'
import {
@ -12,6 +10,14 @@ import {
BCRYPT_SALT_SIZE,
PUBLIC_CERT_NAME
} from '../initializers'
import {
readFilePromise,
bcryptComparePromise,
bcryptGenSaltPromise,
bcryptHashPromise,
accessPromise,
opensslExecPromise
} from './core-utils'
import { logger } from './logger'
function checkSignature (publicKey: string, data: string, hexSignature: string) {
@ -60,46 +66,32 @@ function sign (data: string|Object) {
return signature
}
function comparePassword (plainPassword: string, hashPassword: string, callback: (err: Error, match?: boolean) => void) {
bcrypt.compare(plainPassword, hashPassword, function (err, isPasswordMatch) {
if (err) return callback(err)
return callback(null, isPasswordMatch)
})
function comparePassword (plainPassword: string, hashPassword: string) {
return bcryptComparePromise(plainPassword, hashPassword)
}
function createCertsIfNotExist (callback: (err: Error) => void) {
certsExist(function (err, exist) {
if (err) return callback(err)
function createCertsIfNotExist () {
return certsExist().then(exist => {
if (exist === true) {
return callback(null)
return undefined
}
createCerts(function (err) {
return callback(err)
})
return createCerts()
})
}
function cryptPassword (password: string, callback: (err: Error, hash?: string) => void) {
bcrypt.genSalt(BCRYPT_SALT_SIZE, function (err, salt) {
if (err) return callback(err)
bcrypt.hash(password, salt, function (err, hash) {
return callback(err, hash)
})
})
function cryptPassword (password: string) {
return bcryptGenSaltPromise(BCRYPT_SALT_SIZE).then(salt => bcryptHashPromise(password, salt))
}
function getMyPrivateCert (callback: (err: Error, privateCert: string) => void) {
function getMyPrivateCert () {
const certPath = join(CONFIG.STORAGE.CERT_DIR, PRIVATE_CERT_NAME)
fs.readFile(certPath, 'utf8', callback)
return readFilePromise(certPath, 'utf8')
}
function getMyPublicCert (callback: (err: Error, publicCert: string) => void) {
function getMyPublicCert () {
const certPath = join(CONFIG.STORAGE.CERT_DIR, PUBLIC_CERT_NAME)
fs.readFile(certPath, 'utf8', callback)
return readFilePromise(certPath, 'utf8')
}
// ---------------------------------------------------------------------------
@ -116,23 +108,21 @@ export {
// ---------------------------------------------------------------------------
function certsExist (callback: (err: Error, certsExist: boolean) => void) {
function certsExist () {
const certPath = join(CONFIG.STORAGE.CERT_DIR, PRIVATE_CERT_NAME)
fs.access(certPath, function (err) {
// If there is an error the certificates do not exist
const exists = !err
return callback(null, exists)
})
// If there is an error the certificates do not exist
return accessPromise(certPath)
.then(() => true)
.catch(() => false)
}
function createCerts (callback: (err: Error) => void) {
certsExist(function (err, exist) {
if (err) return callback(err)
function createCerts () {
return certsExist().then(exist => {
if (exist === true) {
const errorMessage = 'Certs already exist.'
logger.warning(errorMessage)
return callback(new Error(errorMessage))
throw new Error(errorMessage)
}
logger.info('Generating a RSA key...')
@ -142,30 +132,27 @@ function createCerts (callback: (err: Error) => void) {
'out': privateCertPath,
'2048': false
}
openssl.exec('genrsa', genRsaOptions, function (err) {
if (err) {
logger.error('Cannot create private key on this pod.')
return callback(err)
}
return opensslExecPromise('genrsa', genRsaOptions)
.then(() => {
logger.info('RSA key generated.')
logger.info('Managing public key...')
logger.info('RSA key generated.')
logger.info('Managing public key...')
const publicCertPath = join(CONFIG.STORAGE.CERT_DIR, 'peertube.pub')
const rsaOptions = {
'in': privateCertPath,
'pubout': true,
'out': publicCertPath
}
openssl.exec('rsa', rsaOptions, function (err) {
if (err) {
logger.error('Cannot create public key on this pod.')
return callback(err)
const publicCertPath = join(CONFIG.STORAGE.CERT_DIR, 'peertube.pub')
const rsaOptions = {
'in': privateCertPath,
'pubout': true,
'out': publicCertPath
}
logger.info('Public key managed.')
return callback(null)
return opensslExecPromise('rsa', rsaOptions)
.then(() => logger.info('Public key managed.'))
.catch(err => {
logger.error('Cannot create public key on this pod.')
throw err
})
})
.catch(err => {
logger.error('Cannot create private key on this pod.')
throw err
})
})
})
}

View file

@ -1,5 +1,6 @@
import * as replay from 'request-replay'
import * as request from 'request'
import * as Promise from 'bluebird'
import {
RETRY_REQUESTS,
@ -14,16 +15,18 @@ type MakeRetryRequestParams = {
method: 'GET'|'POST',
json: Object
}
function makeRetryRequest (params: MakeRetryRequestParams, callback: request.RequestCallback) {
replay(
request(params, callback),
{
retries: RETRY_REQUESTS,
factor: 3,
maxTimeout: Infinity,
errorCodes: [ 'EADDRINFO', 'ETIMEDOUT', 'ECONNRESET', 'ESOCKETTIMEDOUT', 'ENOTFOUND', 'ECONNREFUSED' ]
}
)
function makeRetryRequest (params: MakeRetryRequestParams) {
return new Promise<{ response: request.RequestResponse, body: any }>((res, rej) => {
replay(
request(params, (err, response, body) => err ? rej(err) : res({ response, body })),
{
retries: RETRY_REQUESTS,
factor: 3,
maxTimeout: Infinity,
errorCodes: [ 'EADDRINFO', 'ETIMEDOUT', 'ECONNRESET', 'ESOCKETTIMEDOUT', 'ENOTFOUND', 'ECONNREFUSED' ]
}
)
})
}
type MakeSecureRequestParams = {
@ -33,41 +36,43 @@ type MakeSecureRequestParams = {
sign: boolean
data?: Object
}
function makeSecureRequest (params: MakeSecureRequestParams, callback: request.RequestCallback) {
const requestParams = {
url: REMOTE_SCHEME.HTTP + '://' + params.toPod.host + params.path,
json: {}
}
function makeSecureRequest (params: MakeSecureRequestParams) {
return new Promise<{ response: request.RequestResponse, body: any }>((res, rej) => {
const requestParams = {
url: REMOTE_SCHEME.HTTP + '://' + params.toPod.host + params.path,
json: {}
}
if (params.method !== 'POST') {
return callback(new Error('Cannot make a secure request with a non POST method.'), null, null)
}
if (params.method !== 'POST') {
return rej(new Error('Cannot make a secure request with a non POST method.'))
}
// Add signature if it is specified in the params
if (params.sign === true) {
const host = CONFIG.WEBSERVER.HOST
// Add signature if it is specified in the params
if (params.sign === true) {
const host = CONFIG.WEBSERVER.HOST
let dataToSign
let dataToSign
if (params.data) {
dataToSign = params.data
} else {
// We do not have data to sign so we just take our host
// It is not ideal but the connection should be in HTTPS
dataToSign = host
}
requestParams.json['signature'] = {
host, // Which host we pretend to be
signature: sign(dataToSign)
}
}
// If there are data informations
if (params.data) {
dataToSign = params.data
} else {
// We do not have data to sign so we just take our host
// It is not ideal but the connection should be in HTTPS
dataToSign = host
requestParams.json['data'] = params.data
}
requestParams.json['signature'] = {
host, // Which host we pretend to be
signature: sign(dataToSign)
}
}
// If there are data informations
if (params.data) {
requestParams.json['data'] = params.data
}
request.post(requestParams, callback)
request.post(requestParams, (err, response, body) => err ? rej(err) : res({ response, body }))
})
}
// ---------------------------------------------------------------------------

View file

@ -1,25 +1,14 @@
import * as express from 'express'
import { pseudoRandomBytes } from 'crypto'
import { logger } from './logger'
import { pseudoRandomBytesPromise } from './core-utils'
import { ResultList } from '../../shared'
function badRequest (req: express.Request, res: express.Response, next: express.NextFunction) {
res.type('json').status(400).end()
}
function generateRandomString (size: number, callback: (err: Error, randomString?: string) => void) {
pseudoRandomBytes(size, function (err, raw) {
if (err) return callback(err)
callback(null, raw.toString('hex'))
})
}
function createEmptyCallback () {
return function (err) {
if (err) logger.error('Error in empty callback.', { error: err })
}
function generateRandomString (size: number) {
return pseudoRandomBytesPromise(size).then(raw => raw.toString('hex'))
}
interface FormatableToJSON {
@ -33,17 +22,18 @@ function getFormatedObjects<U, T extends FormatableToJSON> (objects: T[], object
formatedObjects.push(object.toFormatedJSON())
})
return {
const res: ResultList<U> = {
total: objectsTotal,
data: formatedObjects
}
return res
}
// ---------------------------------------------------------------------------
export {
badRequest,
createEmptyCallback,
generateRandomString,
getFormatedObjects
}

View file

@ -2,6 +2,7 @@ import * as config from 'config'
import { database as db } from './database'
import { CONFIG } from './constants'
import { promisify0 } from '../helpers/core-utils'
// Some checks on configuration files
function checkConfig () {
@ -35,41 +36,36 @@ function checkMissedConfig () {
}
// Check the available codecs
function checkFFmpeg (callback: (err: Error) => void) {
function checkFFmpeg () {
const Ffmpeg = require('fluent-ffmpeg')
const getAvailableCodecsPromise = promisify0(Ffmpeg.getAvailableCodecs)
Ffmpeg.getAvailableCodecs(function (err, codecs) {
if (err) return callback(err)
if (CONFIG.TRANSCODING.ENABLED === false) return callback(null)
getAvailableCodecsPromise()
.then(codecs => {
if (CONFIG.TRANSCODING.ENABLED === false) return undefined
const canEncode = [ 'libx264' ]
canEncode.forEach(function (codec) {
if (codecs[codec] === undefined) {
return callback(new Error('Unknown codec ' + codec + ' in FFmpeg.'))
}
const canEncode = [ 'libx264' ]
canEncode.forEach(function (codec) {
if (codecs[codec] === undefined) {
throw new Error('Unknown codec ' + codec + ' in FFmpeg.')
}
if (codecs[codec].canEncode !== true) {
return callback(new Error('Unavailable encode codec ' + codec + ' in FFmpeg'))
}
if (codecs[codec].canEncode !== true) {
throw new Error('Unavailable encode codec ' + codec + ' in FFmpeg')
}
})
})
}
return callback(null)
function clientsExist () {
return db.OAuthClient.countTotal().then(totalClients => {
return totalClients !== 0
})
}
function clientsExist (callback: (err: Error, clientsExist?: boolean) => void) {
db.OAuthClient.countTotal(function (err, totalClients) {
if (err) return callback(err)
return callback(null, totalClients !== 0)
})
}
function usersExist (callback: (err: Error, usersExist?: boolean) => void) {
db.User.countTotal(function (err, totalUsers) {
if (err) return callback(err)
return callback(null, totalUsers !== 0)
function usersExist () {
return db.User.countTotal().then(totalUsers => {
return totalUsers !== 0
})
}

View file

@ -1,12 +1,12 @@
import * as fs from 'fs'
import { join } from 'path'
import { flattenDepth } from 'lodash'
import * as Sequelize from 'sequelize'
import { each } from 'async'
import * as Promise from 'bluebird'
import { CONFIG } from './constants'
// Do not use barrel, we need to load database first
import { logger } from '../helpers/logger'
import { isTestInstance } from '../helpers/core-utils'
import { isTestInstance, readdirPromise } from '../helpers/core-utils'
import {
ApplicationModel,
AuthorModel,
@ -33,7 +33,7 @@ const password = CONFIG.DATABASE.PASSWORD
const database: {
sequelize?: Sequelize.Sequelize,
init?: (silent: any, callback: any) => void,
init?: (silent: boolean) => Promise<void>,
Application?: ApplicationModel,
Author?: AuthorModel,
@ -72,19 +72,17 @@ const sequelize = new Sequelize(dbname, username, password, {
database.sequelize = sequelize
database.init = function (silent: boolean, callback: (err: Error) => void) {
database.init = function (silent: boolean) {
const modelDirectory = join(__dirname, '..', 'models')
getModelFiles(modelDirectory, function (err, filePaths) {
if (err) throw err
filePaths.forEach(function (filePath) {
return getModelFiles(modelDirectory).then(filePaths => {
filePaths.forEach(filePath => {
const model = sequelize.import(filePath)
database[model['name']] = model
})
Object.keys(database).forEach(function (modelName) {
Object.keys(database).forEach(modelName => {
if ('associate' in database[modelName]) {
database[modelName].associate(database)
}
@ -92,7 +90,7 @@ database.init = function (silent: boolean, callback: (err: Error) => void) {
if (!silent) logger.info('Database %s is ready.', dbname)
return callback(null)
return undefined
})
}
@ -104,49 +102,50 @@ export {
// ---------------------------------------------------------------------------
function getModelFiles (modelDirectory: string, callback: (err: Error, filePaths: string[]) => void) {
fs.readdir(modelDirectory, function (err, files) {
if (err) throw err
function getModelFiles (modelDirectory: string) {
return readdirPromise(modelDirectory)
.then(files => {
const directories: string[] = files.filter(function (directory) {
// Find directories
if (
directory.endsWith('.js.map') ||
directory === 'index.js' || directory === 'index.ts' ||
directory === 'utils.js' || directory === 'utils.ts'
) return false
const directories = files.filter(function (directory) {
// Find directories
if (
directory.endsWith('.js.map') ||
directory === 'index.js' || directory === 'index.ts' ||
directory === 'utils.js' || directory === 'utils.ts'
) return false
return true
})
return true
return directories
})
.then(directories => {
const tasks = []
let modelFilePaths: string[] = []
// For each directory we read it and append model in the modelFilePaths array
directories.forEach(directory => {
const modelDirectoryPath = join(modelDirectory, directory)
// For each directory we read it and append model in the modelFilePaths array
each(directories, function (directory: string, eachCallback: ErrorCallback<Error>) {
const modelDirectoryPath = join(modelDirectory, directory)
const promise = readdirPromise(modelDirectoryPath).then(files => {
const filteredFiles = files.filter(file => {
if (
file === 'index.js' || file === 'index.ts' ||
file === 'utils.js' || file === 'utils.ts' ||
file.endsWith('-interface.js') || file.endsWith('-interface.ts') ||
file.endsWith('.js.map')
) return false
fs.readdir(modelDirectoryPath, function (err, files) {
if (err) return eachCallback(err)
return true
}).map(file => join(modelDirectoryPath, file))
const filteredFiles = files.filter(file => {
if (
file === 'index.js' || file === 'index.ts' ||
file === 'utils.js' || file === 'utils.ts' ||
file.endsWith('-interface.js') || file.endsWith('-interface.ts') ||
file.endsWith('.js.map')
) return false
return true
}).map(file => {
return join(modelDirectoryPath, file)
return filteredFiles
})
modelFilePaths = modelFilePaths.concat(filteredFiles)
return eachCallback(null)
tasks.push(promise)
})
}, function (err: Error) {
return callback(err, modelFilePaths)
return Promise.all(tasks)
})
.then((filteredFiles: string[][]) => {
return flattenDepth<string>(filteredFiles, 1)
})
})
}

View file

@ -1,37 +1,19 @@
import { join } from 'path'
import * as config from 'config'
import { each, series } from 'async'
import * as mkdirp from 'mkdirp'
import * as passwordGenerator from 'password-generator'
import * as Promise from 'bluebird'
import { database as db } from './database'
import { USER_ROLES, CONFIG, LAST_MIGRATION_VERSION } from './constants'
import { clientsExist, usersExist } from './checker'
import { logger, createCertsIfNotExist, root } from '../helpers'
import { logger, createCertsIfNotExist, root, mkdirpPromise } from '../helpers'
function installApplication (callback: (err: Error) => void) {
series([
function createDatabase (callbackAsync) {
db.sequelize.sync().asCallback(callbackAsync)
// db.sequelize.sync({ force: true }).asCallback(callbackAsync)
},
function createDirectories (callbackAsync) {
createDirectoriesIfNotExist(callbackAsync)
},
function createCertificates (callbackAsync) {
createCertsIfNotExist(callbackAsync)
},
function createOAuthClient (callbackAsync) {
createOAuthClientIfNotExist(callbackAsync)
},
function createOAuthUser (callbackAsync) {
createOAuthAdminIfNotExist(callbackAsync)
}
], callback)
function installApplication () {
return db.sequelize.sync()
.then(() => createDirectoriesIfNotExist())
.then(() => createCertsIfNotExist())
.then(() => createOAuthClientIfNotExist())
.then(() => createOAuthAdminIfNotExist())
}
// ---------------------------------------------------------------------------
@ -42,21 +24,22 @@ export {
// ---------------------------------------------------------------------------
function createDirectoriesIfNotExist (callback: (err: Error) => void) {
function createDirectoriesIfNotExist () {
const storages = config.get('storage')
each(Object.keys(storages), function (key, callbackEach) {
const tasks = []
Object.keys(storages).forEach(key => {
const dir = storages[key]
mkdirp(join(root(), dir), callbackEach)
}, callback)
tasks.push(mkdirpPromise(join(root(), dir)))
})
return Promise.all(tasks)
}
function createOAuthClientIfNotExist (callback: (err: Error) => void) {
clientsExist(function (err, exist) {
if (err) return callback(err)
function createOAuthClientIfNotExist () {
return clientsExist().then(exist => {
// Nothing to do, clients already exist
if (exist === true) return callback(null)
if (exist === true) return undefined
logger.info('Creating a default OAuth Client.')
@ -69,23 +52,19 @@ function createOAuthClientIfNotExist (callback: (err: Error) => void) {
redirectUris: null
})
client.save().asCallback(function (err, createdClient) {
if (err) return callback(err)
return client.save().then(createdClient => {
logger.info('Client id: ' + createdClient.clientId)
logger.info('Client secret: ' + createdClient.clientSecret)
return callback(null)
return undefined
})
})
}
function createOAuthAdminIfNotExist (callback: (err: Error) => void) {
usersExist(function (err, exist) {
if (err) return callback(err)
function createOAuthAdminIfNotExist () {
return usersExist().then(exist => {
// Nothing to do, users already exist
if (exist === true) return callback(null)
if (exist === true) return undefined
logger.info('Creating the administrator.')
@ -116,14 +95,12 @@ function createOAuthAdminIfNotExist (callback: (err: Error) => void) {
role
}
db.User.create(userData, createOptions).asCallback(function (err, createdUser) {
if (err) return callback(err)
return db.User.create(userData, createOptions).then(createdUser => {
logger.info('Username: ' + username)
logger.info('User password: ' + password)
logger.info('Creating Application table.')
db.Application.create({ migrationVersion: LAST_MIGRATION_VERSION }).asCallback(callback)
return db.Application.create({ migrationVersion: LAST_MIGRATION_VERSION })
})
})
}

View file

@ -1,9 +1,12 @@
import { waterfall } from 'async'
import * as Sequelize from 'sequelize'
import * as Promise from 'bluebird'
// utils = { transaction, queryInterface, sequelize, Sequelize }
function up (utils, finalCallback) {
function up (utils: {
transaction: Sequelize.Transaction,
queryInterface: Sequelize.QueryInterface,
sequelize: Sequelize.Sequelize
}): Promise<void> {
const q = utils.queryInterface
const Sequelize = utils.Sequelize
const data = {
type: Sequelize.STRING(400),
@ -11,27 +14,16 @@ function up (utils, finalCallback) {
defaultValue: ''
}
waterfall([
function addEmailColumn (callback) {
q.addColumn('Pods', 'email', data, { transaction: utils.transaction }).asCallback(function (err) {
return callback(err)
})
},
function updateWithFakeEmails (callback) {
return q.addColumn('Pods', 'email', data)
.then(() => {
const query = 'UPDATE "Pods" SET "email" = \'dummy@example.com\''
utils.sequelize.query(query, { transaction: utils.transaction }).asCallback(function (err) {
return callback(err)
})
},
function nullOnDefault (callback) {
return utils.sequelize.query(query, { transaction: utils.transaction })
})
.then(() => {
data.defaultValue = null
q.changeColumn('Pods', 'email', data, { transaction: utils.transaction }).asCallback(callback)
}
], finalCallback)
return q.changeColumn('Pods', 'email', data)
})
}
function down (options, callback) {

View file

@ -1,37 +1,28 @@
import { waterfall } from 'async'
import * as Sequelize from 'sequelize'
import * as Promise from 'bluebird'
// utils = { transaction, queryInterface, sequelize, Sequelize }
function up (utils, finalCallback) {
function up (utils: {
transaction: Sequelize.Transaction,
queryInterface: Sequelize.QueryInterface,
sequelize: Sequelize.Sequelize
}): Promise<void> {
const q = utils.queryInterface
const Sequelize = utils.Sequelize
const data = {
type: Sequelize.STRING(400),
allowNull: false,
defaultValue: ''
}
waterfall([
function addEmailColumn (callback) {
q.addColumn('Users', 'email', data, { transaction: utils.transaction }).asCallback(function (err) {
return callback(err)
})
},
function updateWithFakeEmails (callback) {
return q.addColumn('Users', 'email', data)
.then(() => {
const query = 'UPDATE "Users" SET "email" = CONCAT("username", \'@example.com\')'
utils.sequelize.query(query, { transaction: utils.transaction }).asCallback(function (err) {
return callback(err)
})
},
function nullOnDefault (callback) {
return utils.sequelize.query(query, { transaction: utils.transaction })
})
.then(() => {
data.defaultValue = null
q.changeColumn('Users', 'email', data, { transaction: utils.transaction }).asCallback(callback)
}
], finalCallback)
return q.changeColumn('Users', 'email', data)
})
}
function down (options, callback) {

View file

@ -1,7 +1,12 @@
// utils = { transaction, queryInterface, sequelize, Sequelize }
function up (utils, finalCallback) {
import * as Sequelize from 'sequelize'
import * as Promise from 'bluebird'
function up (utils: {
transaction: Sequelize.Transaction,
queryInterface: Sequelize.QueryInterface,
sequelize: Sequelize.Sequelize
}): Promise<void> {
const q = utils.queryInterface
const Sequelize = utils.Sequelize
const data = {
type: Sequelize.INTEGER,
@ -9,7 +14,7 @@ function up (utils, finalCallback) {
defaultValue: 0
}
q.addColumn('Videos', 'views', data, { transaction: utils.transaction }).asCallback(finalCallback)
return q.addColumn('Videos', 'views', data)
}
function down (options, callback) {

View file

@ -1,7 +1,12 @@
// utils = { transaction, queryInterface, sequelize, Sequelize }
function up (utils, finalCallback) {
import * as Sequelize from 'sequelize'
import * as Promise from 'bluebird'
function up (utils: {
transaction: Sequelize.Transaction,
queryInterface: Sequelize.QueryInterface,
sequelize: Sequelize.Sequelize
}): Promise<void> {
const q = utils.queryInterface
const Sequelize = utils.Sequelize
const data = {
type: Sequelize.INTEGER,
@ -9,7 +14,7 @@ function up (utils, finalCallback) {
defaultValue: 0
}
q.addColumn('Videos', 'likes', data, { transaction: utils.transaction }).asCallback(finalCallback)
return q.addColumn('Videos', 'likes', data)
}
function down (options, callback) {

View file

@ -1,7 +1,12 @@
// utils = { transaction, queryInterface, sequelize, Sequelize }
function up (utils, finalCallback) {
import * as Sequelize from 'sequelize'
import * as Promise from 'bluebird'
function up (utils: {
transaction: Sequelize.Transaction,
queryInterface: Sequelize.QueryInterface,
sequelize: Sequelize.Sequelize
}): Promise<void> {
const q = utils.queryInterface
const Sequelize = utils.Sequelize
const data = {
type: Sequelize.INTEGER,
@ -9,7 +14,7 @@ function up (utils, finalCallback) {
defaultValue: 0
}
q.addColumn('Videos', 'dislikes', data, { transaction: utils.transaction }).asCallback(finalCallback)
return q.addColumn('Videos', 'dislikes', data)
}
function down (options, callback) {

View file

@ -1,9 +1,12 @@
import { waterfall } from 'async'
import * as Sequelize from 'sequelize'
import * as Promise from 'bluebird'
// utils = { transaction, queryInterface, sequelize, Sequelize }
function up (utils, finalCallback) {
function up (utils: {
transaction: Sequelize.Transaction,
queryInterface: Sequelize.QueryInterface,
sequelize: Sequelize.Sequelize
}): Promise<void> {
const q = utils.queryInterface
const Sequelize = utils.Sequelize
const data = {
type: Sequelize.INTEGER,
@ -11,20 +14,12 @@ function up (utils, finalCallback) {
defaultValue: 0
}
waterfall([
function addCategoryColumn (callback) {
q.addColumn('Videos', 'category', data, { transaction: utils.transaction }).asCallback(function (err) {
return callback(err)
})
},
function nullOnDefault (callback) {
return q.addColumn('Videos', 'category', data)
.then(() => {
data.defaultValue = null
q.changeColumn('Videos', 'category', data, { transaction: utils.transaction }).asCallback(callback)
}
], finalCallback)
return q.changeColumn('Videos', 'category', data)
})
}
function down (options, callback) {

View file

@ -1,9 +1,12 @@
import { waterfall } from 'async'
import * as Sequelize from 'sequelize'
import * as Promise from 'bluebird'
// utils = { transaction, queryInterface, sequelize, Sequelize }
function up (utils, finalCallback) {
function up (utils: {
transaction: Sequelize.Transaction,
queryInterface: Sequelize.QueryInterface,
sequelize: Sequelize.Sequelize
}): Promise<void> {
const q = utils.queryInterface
const Sequelize = utils.Sequelize
const data = {
type: Sequelize.INTEGER,
@ -11,20 +14,11 @@ function up (utils, finalCallback) {
defaultValue: 0
}
waterfall([
function addLicenceColumn (callback) {
q.addColumn('Videos', 'licence', data, { transaction: utils.transaction }).asCallback(function (err) {
return callback(err)
})
},
function nullOnDefault (callback) {
return q.addColumn('Videos', 'licence', data)
.then(() => {
data.defaultValue = null
q.changeColumn('Videos', 'licence', data, { transaction: utils.transaction }).asCallback(callback)
}
], finalCallback)
return q.changeColumn('Videos', 'licence', data)
})
}
function down (options, callback) {

View file

@ -1,9 +1,12 @@
import { waterfall } from 'async'
import * as Sequelize from 'sequelize'
import * as Promise from 'bluebird'
// utils = { transaction, queryInterface, sequelize, Sequelize }
function up (utils, finalCallback) {
function up (utils: {
transaction: Sequelize.Transaction,
queryInterface: Sequelize.QueryInterface,
sequelize: Sequelize.Sequelize
}): Promise<void> {
const q = utils.queryInterface
const Sequelize = utils.Sequelize
const data = {
type: Sequelize.BOOLEAN,
@ -11,20 +14,12 @@ function up (utils, finalCallback) {
defaultValue: false
}
waterfall([
function addNSFWColumn (callback) {
q.addColumn('Videos', 'nsfw', data, { transaction: utils.transaction }).asCallback(function (err) {
return callback(err)
})
},
function nullOnDefault (callback) {
return q.addColumn('Videos', 'nsfw', data)
.then(() => {
data.defaultValue = null
q.changeColumn('Videos', 'nsfw', data, { transaction: utils.transaction }).asCallback(callback)
}
], finalCallback)
return q.changeColumn('Videos', 'nsfw', data)
})
}
function down (options, callback) {

View file

@ -1,7 +1,12 @@
// utils = { transaction, queryInterface, sequelize, Sequelize }
function up (utils, finalCallback) {
import * as Sequelize from 'sequelize'
import * as Promise from 'bluebird'
function up (utils: {
transaction: Sequelize.Transaction,
queryInterface: Sequelize.QueryInterface,
sequelize: Sequelize.Sequelize
}): Promise<void> {
const q = utils.queryInterface
const Sequelize = utils.Sequelize
const data = {
type: Sequelize.BOOLEAN,
@ -9,7 +14,7 @@ function up (utils, finalCallback) {
defaultValue: false
}
q.addColumn('Users', 'displayNSFW', data, { transaction: utils.transaction }).asCallback(finalCallback)
return q.addColumn('Users', 'displayNSFW', data)
}
function down (options, callback) {

View file

@ -1,7 +1,12 @@
// utils = { transaction, queryInterface, sequelize, Sequelize }
function up (utils, finalCallback) {
import * as Sequelize from 'sequelize'
import * as Promise from 'bluebird'
function up (utils: {
transaction: Sequelize.Transaction,
queryInterface: Sequelize.QueryInterface,
sequelize: Sequelize.Sequelize
}): Promise<void> {
const q = utils.queryInterface
const Sequelize = utils.Sequelize
const data = {
type: Sequelize.INTEGER,
@ -9,7 +14,7 @@ function up (utils, finalCallback) {
defaultValue: null
}
q.addColumn('Videos', 'language', data, { transaction: utils.transaction }).asCallback(finalCallback)
return q.addColumn('Videos', 'language', data)
}
function down (options, callback) {

View file

@ -1,70 +1,54 @@
import { waterfall, eachSeries } from 'async'
import * as fs from 'fs'
import * as path from 'path'
import * as Sequelize from 'sequelize'
import * as Promise from 'bluebird'
import { database as db } from './database'
import { LAST_MIGRATION_VERSION } from './constants'
import { logger } from '../helpers'
import { logger, readdirPromise } from '../helpers'
function migrate (finalCallback: (err: Error) => void) {
waterfall([
function checkApplicationTableExists (callback) {
db.sequelize.getQueryInterface().showAllTables().asCallback(function (err, tables) {
if (err) return callback(err)
// No tables, we don't need to migrate anything
// The installer will do that
if (tables.length === 0) return finalCallback(null)
return callback(null)
})
},
function loadMigrationVersion (callback) {
db.Application.loadMigrationVersion(callback)
},
function createMigrationRowIfNotExists (actualVersion, callback) {
function migrate () {
const p = db.sequelize.getQueryInterface().showAllTables()
.then(tables => {
// No tables, we don't need to migrate anything
// The installer will do that
if (tables.length === 0) throw null
})
.then(() => {
return db.Application.loadMigrationVersion()
})
.then(actualVersion => {
if (actualVersion === null) {
db.Application.create({
migrationVersion: 0
}, function (err) {
return callback(err, 0)
})
return db.Application.create({ migrationVersion: 0 }).then(() => 0)
}
return callback(null, actualVersion)
},
return actualVersion
})
.then(actualVersion => {
// No need migrations, abort
if (actualVersion >= LAST_MIGRATION_VERSION) throw null
function abortMigrationIfNotNeeded (actualVersion, callback) {
// No need migrations
if (actualVersion >= LAST_MIGRATION_VERSION) return finalCallback(null)
return callback(null, actualVersion)
},
function getMigrations (actualVersion, callback) {
return actualVersion
})
.then(actualVersion => {
// If there are a new migration scripts
logger.info('Begin migrations.')
getMigrationScripts(function (err, migrationScripts) {
return callback(err, actualVersion, migrationScripts)
return getMigrationScripts().then(migrationScripts => ({ actualVersion, migrationScripts }))
})
.then(({ actualVersion, migrationScripts }) => {
return Promise.mapSeries(migrationScripts, entity => {
return executeMigration(actualVersion, entity)
})
},
})
.then(() => {
logger.info('Migrations finished. New migration version schema: %s', LAST_MIGRATION_VERSION)
})
.catch(err => {
if (err === null) return undefined
function doMigrations (actualVersion, migrationScripts, callback) {
eachSeries(migrationScripts, function (entity: any, callbackEach) {
executeMigration(actualVersion, entity, callbackEach)
}, function (err) {
if (err) return callback(err)
throw err
})
logger.info('Migrations finished. New migration version schema: %s', LAST_MIGRATION_VERSION)
return callback(null)
})
}
], finalCallback)
return p
}
// ---------------------------------------------------------------------------
@ -75,12 +59,12 @@ export {
// ---------------------------------------------------------------------------
type GetMigrationScriptsCallback = (err: Error, filesToMigrate?: { version: string, script: string }[]) => void
function getMigrationScripts (callback: GetMigrationScriptsCallback) {
fs.readdir(path.join(__dirname, 'migrations'), function (err, files) {
if (err) return callback(err)
const filesToMigrate = []
function getMigrationScripts () {
return readdirPromise(path.join(__dirname, 'migrations')).then(files => {
const filesToMigrate: {
version: string,
script: string
}[] = []
files.forEach(function (file) {
// Filename is something like 'version-blabla.js'
@ -91,15 +75,15 @@ function getMigrationScripts (callback: GetMigrationScriptsCallback) {
})
})
return callback(err, filesToMigrate)
return filesToMigrate
})
}
function executeMigration (actualVersion: number, entity: { version: string, script: string }, callback: (err: Error) => void) {
function executeMigration (actualVersion: number, entity: { version: string, script: string }) {
const versionScript = parseInt(entity.version, 10)
// Do not execute old migration scripts
if (versionScript <= actualVersion) return callback(null)
if (versionScript <= actualVersion) return undefined
// Load the migration module and run it
const migrationScriptName = entity.script
@ -107,30 +91,17 @@ function executeMigration (actualVersion: number, entity: { version: string, scr
const migrationScript = require(path.join(__dirname, 'migrations', migrationScriptName))
db.sequelize.transaction().asCallback(function (err, t) {
if (err) return callback(err)
return db.sequelize.transaction(t => {
const options = {
transaction: t,
queryInterface: db.sequelize.getQueryInterface(),
sequelize: db.sequelize,
Sequelize: Sequelize
sequelize: db.sequelize
}
migrationScript.up(options, function (err) {
if (err) {
t.rollback()
return callback(err)
}
// Update the new migration version
db.Application.updateMigrationVersion(versionScript, t, function (err) {
if (err) {
t.rollback()
return callback(err)
}
t.commit().asCallback(callback)
migrationScript.up(options)
.then(() => {
// Update the new migration version
db.Application.updateMigrationVersion(versionScript, t)
})
})
})
}

View file

@ -1,6 +1,6 @@
import { each, eachLimit, eachSeries, series, waterfall } from 'async'
import * as request from 'request'
import * as Sequelize from 'sequelize'
import * as Promise from 'bluebird'
import { database as db } from '../initializers/database'
import {
@ -15,8 +15,7 @@ import {
logger,
getMyPublicCert,
makeSecureRequest,
makeRetryRequest,
createEmptyCallback
makeRetryRequest
} from '../helpers'
import {
RequestScheduler,
@ -53,24 +52,24 @@ function activateSchedulers () {
requestVideoEventScheduler.activate()
}
function addVideoToFriends (videoData: Object, transaction: Sequelize.Transaction, callback: (err: Error) => void) {
function addVideoToFriends (videoData: Object, transaction: Sequelize.Transaction) {
const options = {
type: ENDPOINT_ACTIONS.ADD,
endpoint: REQUEST_ENDPOINTS.VIDEOS,
data: videoData,
transaction
}
createRequest(options, callback)
return createRequest(options)
}
function updateVideoToFriends (videoData: Object, transaction: Sequelize.Transaction, callback: (err: Error) => void) {
function updateVideoToFriends (videoData: Object, transaction: Sequelize.Transaction) {
const options = {
type: ENDPOINT_ACTIONS.UPDATE,
endpoint: REQUEST_ENDPOINTS.VIDEOS,
data: videoData,
transaction
}
createRequest(options, callback)
return createRequest(options)
}
function removeVideoToFriends (videoParams: Object) {
@ -80,121 +79,93 @@ function removeVideoToFriends (videoParams: Object) {
data: videoParams,
transaction: null
}
createRequest(options)
return createRequest(options)
}
function reportAbuseVideoToFriend (reportData: Object, video: VideoInstance) {
function reportAbuseVideoToFriend (reportData: Object, video: VideoInstance, transaction: Sequelize.Transaction) {
const options = {
type: ENDPOINT_ACTIONS.REPORT_ABUSE,
endpoint: REQUEST_ENDPOINTS.VIDEOS,
data: reportData,
toIds: [ video.Author.podId ],
transaction: null
transaction
}
createRequest(options)
return createRequest(options)
}
function quickAndDirtyUpdateVideoToFriends (qaduParam: QaduParam, transaction?: Sequelize.Transaction, callback?: (err: Error) => void) {
function quickAndDirtyUpdateVideoToFriends (qaduParam: QaduParam, transaction?: Sequelize.Transaction) {
const options = {
videoId: qaduParam.videoId,
type: qaduParam.type,
transaction
}
return createVideoQaduRequest(options, callback)
return createVideoQaduRequest(options)
}
function quickAndDirtyUpdatesVideoToFriends (
qadusParams: QaduParam[],
transaction: Sequelize.Transaction,
finalCallback: (err: Error) => void
) {
function quickAndDirtyUpdatesVideoToFriends (qadusParams: QaduParam[], transaction: Sequelize.Transaction) {
const tasks = []
qadusParams.forEach(function (qaduParams) {
const fun = function (callback) {
quickAndDirtyUpdateVideoToFriends(qaduParams, transaction, callback)
}
tasks.push(fun)
tasks.push(quickAndDirtyUpdateVideoToFriends(qaduParams, transaction))
})
series(tasks, finalCallback)
return Promise.all(tasks)
}
function addEventToRemoteVideo (eventParam: EventParam, transaction?: Sequelize.Transaction, callback?: (err: Error) => void) {
function addEventToRemoteVideo (eventParam: EventParam, transaction?: Sequelize.Transaction) {
const options = {
videoId: eventParam.videoId,
type: eventParam.type,
transaction
}
createVideoEventRequest(options, callback)
return createVideoEventRequest(options)
}
function addEventsToRemoteVideo (eventsParams: EventParam[], transaction: Sequelize.Transaction, finalCallback: (err: Error) => void) {
function addEventsToRemoteVideo (eventsParams: EventParam[], transaction: Sequelize.Transaction) {
const tasks = []
eventsParams.forEach(function (eventParams) {
const fun = function (callback) {
addEventToRemoteVideo(eventParams, transaction, callback)
}
tasks.push(fun)
tasks.push(addEventToRemoteVideo(eventParams, transaction))
})
series(tasks, finalCallback)
return Promise.all(tasks)
}
function hasFriends (callback: (err: Error, hasFriends?: boolean) => void) {
db.Pod.countAll(function (err, count) {
if (err) return callback(err)
const hasFriends = (count !== 0)
callback(null, hasFriends)
})
function hasFriends () {
return db.Pod.countAll().then(count => count !== 0)
}
function makeFriends (hosts: string[], callback: (err: Error) => void) {
function makeFriends (hosts: string[]) {
const podsScore = {}
logger.info('Make friends!')
getMyPublicCert(function (err, cert) {
if (err) {
logger.error('Cannot read public cert.')
return callback(err)
}
eachSeries(hosts, function (host, callbackEach) {
computeForeignPodsList(host, podsScore, callbackEach)
}, function (err: Error) {
if (err) return callback(err)
return getMyPublicCert()
.then(cert => {
return Promise.mapSeries(hosts, host => {
return computeForeignPodsList(host, podsScore)
}).then(() => cert)
})
.then(cert => {
logger.debug('Pods scores computed.', { podsScore: podsScore })
const podsList = computeWinningPods(hosts, podsScore)
logger.debug('Pods that we keep.', { podsToKeep: podsList })
makeRequestsToWinningPods(cert, podsList, callback)
return makeRequestsToWinningPods(cert, podsList)
})
})
}
function quitFriends (callback: (err: Error) => void) {
function quitFriends () {
// Stop pool requests
requestScheduler.deactivate()
waterfall([
function flushRequests (callbackAsync) {
requestScheduler.flush(err => callbackAsync(err))
},
function flushVideoQaduRequests (callbackAsync) {
requestVideoQaduScheduler.flush(err => callbackAsync(err))
},
function getPodsList (callbackAsync) {
return db.Pod.list(callbackAsync)
},
function announceIQuitMyFriends (pods, callbackAsync) {
return requestScheduler.flush()
.then(() => {
return requestVideoQaduScheduler.flush()
})
.then(() => {
return db.Pod.list()
})
.then(pods => {
const requestParams = {
method: 'POST' as 'POST',
path: '/api/' + API_VERSION + '/remote/pods/remove',
@ -205,61 +176,57 @@ function quitFriends (callback: (err: Error) => void) {
// Announce we quit them
// We don't care if the request fails
// The other pod will exclude us automatically after a while
eachLimit(pods, REQUESTS_IN_PARALLEL, function (pod, callbackEach) {
return Promise.map(pods, pod => {
requestParams.toPod = pod
makeSecureRequest(requestParams, callbackEach)
}, function (err) {
if (err) {
logger.error('Some errors while quitting friends.', { err: err })
// Don't stop the process
}
return callbackAsync(null, pods)
return makeSecureRequest(requestParams)
}, { concurrency: REQUESTS_IN_PARALLEL })
.then(() => pods)
.catch(err => {
logger.error('Some errors while quitting friends.', { err: err })
// Don't stop the process
})
},
})
.then(pods => {
const tasks = []
pods.forEach(pod => tasks.push(pod.destroy()))
function removePodsFromDB (pods, callbackAsync) {
each(pods, function (pod: any, callbackEach) {
pod.destroy().asCallback(callbackEach)
}, callbackAsync)
}
], function (err: Error) {
// Don't forget to re activate the scheduler, even if there was an error
requestScheduler.activate()
if (err) return callback(err)
logger.info('Removed all remote videos.')
return callback(null)
})
return Promise.all(pods)
})
.then(() => {
logger.info('Removed all remote videos.')
// Don't forget to re activate the scheduler, even if there was an error
return requestScheduler.activate()
})
.finally(() => requestScheduler.activate())
}
function sendOwnedVideosToPod (podId: number) {
db.Video.listOwnedAndPopulateAuthorAndTags(function (err, videosList) {
if (err) {
logger.error('Cannot get the list of videos we own.')
return
}
db.Video.listOwnedAndPopulateAuthorAndTags()
.then(videosList => {
const tasks = []
videosList.forEach(video => {
const promise = video.toAddRemoteJSON()
.then(remoteVideo => {
const options = {
type: 'add',
endpoint: REQUEST_ENDPOINTS.VIDEOS,
data: remoteVideo,
toIds: [ podId ],
transaction: null
}
return createRequest(options)
})
.catch(err => {
logger.error('Cannot convert video to remote.', { error: err })
// Don't break the process
return undefined
})
videosList.forEach(function (video) {
video.toAddRemoteJSON(function (err, remoteVideo) {
if (err) {
logger.error('Cannot convert video to remote.', { error: err })
// Don't break the process
return
}
const options = {
type: 'add',
endpoint: REQUEST_ENDPOINTS.VIDEOS,
data: remoteVideo,
toIds: [ podId ],
transaction: null
}
createRequest(options)
tasks.push(promise)
})
return Promise.all(tasks)
})
})
}
function getRequestScheduler () {
@ -297,23 +264,22 @@ export {
// ---------------------------------------------------------------------------
function computeForeignPodsList (host: string, podsScore: { [ host: string ]: number }, callback: (err: Error) => void) {
getForeignPodsList(host, function (err, res) {
if (err) return callback(err)
function computeForeignPodsList (host: string, podsScore: { [ host: string ]: number }) {
// TODO: type res
return getForeignPodsList(host).then((res: any) => {
const foreignPodsList = res.data
// Let's give 1 point to the pod we ask the friends list
foreignPodsList.push({ host })
foreignPodsList.forEach(function (foreignPod) {
foreignPodsList.forEach(foreignPod => {
const foreignPodHost = foreignPod.host
if (podsScore[foreignPodHost]) podsScore[foreignPodHost]++
else podsScore[foreignPodHost] = 1
})
return callback(null)
return undefined
})
}
@ -323,7 +289,7 @@ function computeWinningPods (hosts: string[], podsScore: { [ host: string ]: num
const podsList = []
const baseScore = hosts.length / 2
Object.keys(podsScore).forEach(function (podHost) {
Object.keys(podsScore).forEach(podHost => {
// 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 })
@ -333,28 +299,30 @@ function computeWinningPods (hosts: string[], podsScore: { [ host: string ]: num
return podsList
}
function getForeignPodsList (host: string, callback: (err: Error, foreignPodsList?: any) => void) {
const path = '/api/' + API_VERSION + '/pods'
function getForeignPodsList (host: string) {
return new Promise((res, rej) => {
const path = '/api/' + API_VERSION + '/pods'
request.get(REMOTE_SCHEME.HTTP + '://' + host + path, function (err, response, body) {
if (err) return callback(err)
request.get(REMOTE_SCHEME.HTTP + '://' + host + path, function (err, response, body) {
if (err) return rej(err)
try {
const json = JSON.parse(body)
return callback(null, json)
} catch (err) {
return callback(err)
}
try {
const json = JSON.parse(body)
return res(json)
} catch (err) {
return rej(err)
}
})
})
}
function makeRequestsToWinningPods (cert: string, podsList: PodInstance[], callback: (err: Error) => void) {
function makeRequestsToWinningPods (cert: string, podsList: PodInstance[]) {
// Stop pool requests
requestScheduler.deactivate()
// Flush pool requests
requestScheduler.forceSend()
eachLimit(podsList, REQUESTS_IN_PARALLEL, function (pod: PodInstance, callbackEach) {
return Promise.map(podsList, pod => {
const params = {
url: REMOTE_SCHEME.HTTP + '://' + pod.host + '/api/' + API_VERSION + '/pods/',
method: 'POST' as 'POST',
@ -365,38 +333,35 @@ function makeRequestsToWinningPods (cert: string, podsList: PodInstance[], callb
}
}
makeRetryRequest(params, function (err, res, body: { cert: string, email: string }) {
if (err) {
logger.error('Error with adding %s pod.', pod.host, { error: err })
return makeRetryRequest(params)
.then(({ response, body }) => {
body = body as { cert: string, email: string }
if (response.statusCode === 200) {
const podObj = db.Pod.build({ host: pod.host, publicKey: body.cert, email: body.email })
return podObj.save()
.then(podCreated => {
// Add our videos to the request scheduler
sendOwnedVideosToPod(podCreated.id)
})
.catch(err => {
logger.error('Cannot add friend %s pod.', pod.host, { error: err })
})
} else {
logger.error('Status not 200 for %s pod.', pod.host)
}
})
.catch(err => {
logger.error('Error with adding %s pod.', pod.host, { error: err.stack })
// Don't break the process
return callbackEach()
}
if (res.statusCode === 200) {
const podObj = db.Pod.build({ host: pod.host, publicKey: body.cert, email: body.email })
podObj.save().asCallback(function (err, podCreated) {
if (err) {
logger.error('Cannot add friend %s pod.', pod.host, { error: err })
return callbackEach()
}
// Add our videos to the request scheduler
sendOwnedVideosToPod(podCreated.id)
return callbackEach()
})
} else {
logger.error('Status not 200 for %s pod.', pod.host)
return callbackEach()
}
})
}, function endRequests () {
})
}, { concurrency: REQUESTS_IN_PARALLEL })
.then(() => logger.debug('makeRequestsToWinningPods finished.'))
.finally(() => {
// Final callback, we've ended all the requests
// Now we made new friends, we can re activate the pool of requests
requestScheduler.activate()
logger.debug('makeRequestsToWinningPods finished.')
return callback(null)
})
}
@ -408,33 +373,22 @@ type CreateRequestOptions = {
toIds?: number[]
transaction: Sequelize.Transaction
}
function createRequest (options: CreateRequestOptions, callback?: (err: Error) => void) {
if (!callback) callback = function () { /* empty */ }
if (options.toIds !== undefined) return requestScheduler.createRequest(options as RequestSchedulerOptions, callback)
function createRequest (options: CreateRequestOptions) {
if (options.toIds !== undefined) return requestScheduler.createRequest(options as RequestSchedulerOptions)
// If the "toIds" pods is not specified, we send the request to all our friends
db.Pod.listAllIds(options.transaction, function (err, podIds) {
if (err) {
logger.error('Cannot get pod ids', { error: err })
return
}
return db.Pod.listAllIds(options.transaction).then(podIds => {
const newOptions = Object.assign(options, { toIds: podIds })
return requestScheduler.createRequest(newOptions, callback)
return requestScheduler.createRequest(newOptions)
})
}
function createVideoQaduRequest (options: RequestVideoQaduSchedulerOptions, callback: (err: Error) => void) {
if (!callback) callback = createEmptyCallback()
requestVideoQaduScheduler.createRequest(options, callback)
function createVideoQaduRequest (options: RequestVideoQaduSchedulerOptions) {
return requestVideoQaduScheduler.createRequest(options)
}
function createVideoEventRequest (options: RequestVideoEventSchedulerOptions, callback: (err: Error) => void) {
if (!callback) callback = createEmptyCallback()
requestVideoEventScheduler.createRequest(options, callback)
function createVideoEventRequest (options: RequestVideoEventSchedulerOptions) {
return requestVideoEventScheduler.createRequest(options)
}
function isMe (host: string) {

View file

@ -1,11 +1,9 @@
import * as videoTranscoder from './video-transcoder'
import { VideoInstance } from '../../../models'
export interface JobHandler<T> {
process (data: object, callback: (err: Error, videoInstance?: T) => void)
onError (err: Error, jobId: number, video: T, callback: (err: Error) => void)
onSuccess (data: any, jobId: number, video: T, callback: (err: Error) => void)
process (data: object): T
onError (err: Error, jobId: number)
onSuccess (jobId: number, jobResult: T)
}
const jobHandlers: { [ handlerName: string ]: JobHandler<any> } = {

View file

@ -3,29 +3,23 @@ import { logger } from '../../../helpers'
import { addVideoToFriends } from '../../../lib'
import { VideoInstance } from '../../../models'
function process (data: { id: string }, callback: (err: Error, videoInstance?: VideoInstance) => void) {
db.Video.loadAndPopulateAuthorAndPodAndTags(data.id, function (err, video) {
if (err) return callback(err)
video.transcodeVideofile(function (err) {
return callback(err, video)
})
function process (data: { id: string }) {
return db.Video.loadAndPopulateAuthorAndPodAndTags(data.id).then(video => {
return video.transcodeVideofile().then(() => video)
})
}
function onError (err: Error, jobId: number, video: VideoInstance, callback: (err: Error) => void) {
function onError (err: Error, jobId: number) {
logger.error('Error when transcoding video file in job %d.', jobId, { error: err })
return callback(null)
return Promise.resolve()
}
function onSuccess (data: any, jobId: number, video: VideoInstance, callback: (err: Error) => void) {
function onSuccess (jobId: number, video: VideoInstance) {
logger.info('Job %d is a success.', jobId)
video.toAddRemoteJSON(function (err, remoteVideo) {
if (err) return callback(err)
video.toAddRemoteJSON().then(remoteVideo => {
// Now we'll add the video's meta data to our friends
addVideoToFriends(remoteVideo, null, callback)
return addVideoToFriends(remoteVideo, null)
})
}

View file

@ -32,37 +32,35 @@ class JobScheduler {
// Finish processing jobs from a previous start
const state = JOB_STATES.PROCESSING
db.Job.listWithLimit(limit, state, (err, jobs) => {
this.enqueueJobs(err, jobsQueue, jobs)
db.Job.listWithLimit(limit, state)
.then(jobs => {
this.enqueueJobs(jobsQueue, jobs)
forever(
next => {
if (jobsQueue.length() !== 0) {
// Finish processing the queue first
return setTimeout(next, JOBS_FETCHING_INTERVAL)
}
const state = JOB_STATES.PENDING
db.Job.listWithLimit(limit, state, (err, jobs) => {
if (err) {
logger.error('Cannot list pending jobs.', { error: err })
} else {
jobs.forEach(job => {
jobsQueue.push(job)
})
forever(
next => {
if (jobsQueue.length() !== 0) {
// Finish processing the queue first
return setTimeout(next, JOBS_FETCHING_INTERVAL)
}
// Optimization: we could use "drain" from queue object
return setTimeout(next, JOBS_FETCHING_INTERVAL)
})
},
const state = JOB_STATES.PENDING
db.Job.listWithLimit(limit, state)
.then(jobs => {
this.enqueueJobs(jobsQueue, jobs)
err => { logger.error('Error in job scheduler queue.', { error: err }) }
)
})
// Optimization: we could use "drain" from queue object
return setTimeout(next, JOBS_FETCHING_INTERVAL)
})
.catch(err => logger.error('Cannot list pending jobs.', { error: err }))
},
err => logger.error('Error in job scheduler queue.', { error: err })
)
})
.catch(err => logger.error('Cannot list pending jobs.', { error: err }))
}
createJob (transaction: Sequelize.Transaction, handlerName: string, handlerInputData: object, callback: (err: Error) => void) {
createJob (transaction: Sequelize.Transaction, handlerName: string, handlerInputData: object) {
const createQuery = {
state: JOB_STATES.PENDING,
handlerName,
@ -70,67 +68,62 @@ class JobScheduler {
}
const options = { transaction }
db.Job.create(createQuery, options).asCallback(callback)
return db.Job.create(createQuery, options)
}
private enqueueJobs (err: Error, jobsQueue: AsyncQueue<JobInstance>, jobs: JobInstance[]) {
if (err) {
logger.error('Cannot list pending jobs.', { error: err })
} else {
jobs.forEach(job => {
jobsQueue.push(job)
})
}
private enqueueJobs (jobsQueue: AsyncQueue<JobInstance>, jobs: JobInstance[]) {
jobs.forEach(job => jobsQueue.push(job))
}
private processJob (job: JobInstance, callback: (err: Error) => void) {
const jobHandler = jobHandlers[job.handlerName]
if (jobHandler === undefined) {
logger.error('Unknown job handler for job %s.', job.handlerName)
return callback(null)
}
logger.info('Processing job %d with handler %s.', job.id, job.handlerName)
job.state = JOB_STATES.PROCESSING
job.save().asCallback(err => {
if (err) return this.cannotSaveJobError(err, callback)
if (jobHandler === undefined) {
logger.error('Unknown job handler for job %s.', job.handlerName)
return callback(null)
}
return jobHandler.process(job.handlerInputData, (err, result) => {
if (err) {
logger.error('Error in job handler %s.', job.handlerName, { error: err })
return this.onJobError(jobHandler, job, result, callback)
}
return this.onJobSuccess(jobHandler, job, result, callback)
return job.save()
.then(() => {
return jobHandler.process(job.handlerInputData)
})
.then(
result => {
return this.onJobSuccess(jobHandler, job, result)
},
err => {
logger.error('Error in job handler %s.', job.handlerName, { error: err })
return this.onJobError(jobHandler, job, err)
}
)
.then(() => callback(null))
.catch(err => {
this.cannotSaveJobError(err)
return callback(err)
})
})
}
private onJobError (jobHandler: JobHandler<any>, job: JobInstance, jobResult: any, callback: (err: Error) => void) {
private onJobError (jobHandler: JobHandler<any>, job: JobInstance, err: Error) {
job.state = JOB_STATES.ERROR
job.save().asCallback(err => {
if (err) return this.cannotSaveJobError(err, callback)
return jobHandler.onError(err, job.id, jobResult, callback)
})
return job.save()
.then(() => jobHandler.onError(err, job.id))
.catch(err => this.cannotSaveJobError(err))
}
private onJobSuccess (jobHandler: JobHandler<any>, job: JobInstance, jobResult: any, callback: (err: Error) => void) {
private onJobSuccess (jobHandler: JobHandler<any>, job: JobInstance, jobResult: any) {
job.state = JOB_STATES.SUCCESS
job.save().asCallback(err => {
if (err) return this.cannotSaveJobError(err, callback)
return jobHandler.onSuccess(err, job.id, jobResult, callback)
})
return job.save()
.then(() => jobHandler.onSuccess(job.id, jobResult))
.catch(err => this.cannotSaveJobError(err))
}
private cannotSaveJobError (err: Error, callback: (err: Error) => void) {
private cannotSaveJobError (err: Error) {
logger.error('Cannot save new job state.', { error: err })
return callback(err)
}
}

View file

@ -30,17 +30,10 @@ function getUser (username: string, password: string) {
return db.User.getByUsername(username).then(function (user) {
if (!user) return null
// We need to return a promise
return new Promise(function (resolve, reject) {
return user.isPasswordMatch(password, function (err, isPasswordMatch) {
if (err) return reject(err)
return user.isPasswordMatch(password).then(passwordMatch => {
if (passwordMatch === false) return null
if (isPasswordMatch === true) {
return resolve(user)
}
return resolve(null)
})
return user
})
})
}
@ -80,8 +73,6 @@ function saveToken (token: TokenInfo, client: OAuthClientInstance, user: UserIns
tokenCreated.user = user
return tokenCreated
}).catch(function (err) {
throw err
})
}

View file

@ -1,15 +1,16 @@
import * as eachLimit from 'async/eachLimit'
import { isEmpty } from 'lodash'
import * as Promise from 'bluebird'
import { database as db } from '../../initializers/database'
import { logger, makeSecureRequest } from '../../helpers'
import { PodInstance } from '../../models'
import { AbstractRequestClass, AbstractRequestToPodClass, PodInstance } from '../../models'
import {
API_VERSION,
REQUESTS_IN_PARALLEL,
REQUESTS_INTERVAL
} from '../../initializers'
abstract class AbstractRequestScheduler {
abstract class AbstractRequestScheduler <T> {
requestInterval: number
limitPods: number
limitPerPod: number
@ -24,9 +25,9 @@ abstract class AbstractRequestScheduler {
this.requestInterval = REQUESTS_INTERVAL
}
abstract getRequestModel ()
abstract getRequestToPodModel ()
abstract buildRequestObjects (requests: any)
abstract getRequestModel (): AbstractRequestClass<T>
abstract getRequestToPodModel (): AbstractRequestToPodClass
abstract buildRequestObjects (requestsGrouped: T): {}
activate () {
logger.info('Requests scheduler activated.')
@ -55,20 +56,18 @@ abstract class AbstractRequestScheduler {
return REQUESTS_INTERVAL - (Date.now() - this.lastRequestTimestamp)
}
remainingRequestsCount (callback: (err: Error, total: number) => void) {
return this.getRequestModel().countTotalRequests(callback)
remainingRequestsCount () {
return this.getRequestModel().countTotalRequests()
}
flush (callback: (err: Error) => void) {
this.getRequestModel().removeAll(callback)
flush () {
return this.getRequestModel().removeAll()
}
// ---------------------------------------------------------------------------
// Make a requests to friends of a certain type
protected makeRequest (toPod: PodInstance, requestEndpoint: string, requestsToMake: Object, callback) {
if (!callback) callback = function () { /* empty */ }
protected makeRequest (toPod: PodInstance, requestEndpoint: string, requestsToMake: Object) {
const params = {
toPod: toPod,
sign: true, // Prove our identity
@ -79,65 +78,64 @@ abstract class AbstractRequestScheduler {
// Make multiple retry requests to all of pods
// The function fire some useful callbacks
makeSecureRequest(params, (err, res) => {
if (err || (res.statusCode !== 200 && res.statusCode !== 201 && res.statusCode !== 204)) {
err = err ? err.message : 'Status code not 20x : ' + res.statusCode
return makeSecureRequest(params)
.then(({ response, body }) => {
if (response.statusCode !== 200 && response.statusCode !== 201 && response.statusCode !== 204) {
throw new Error('Status code not 20x : ' + response.statusCode)
}
})
.catch(err => {
logger.error('Error sending secure request to %s pod.', toPod.host, { error: err })
return callback(err)
}
return callback(null)
})
throw err
})
}
// Make all the requests of the scheduler
protected makeRequests () {
this.getRequestModel().listWithLimitAndRandom(this.limitPods, this.limitPerPod, (err, requests) => {
if (err) {
logger.error('Cannot get the list of "%s".', this.description, { err: err })
return // Abort
}
return this.getRequestModel().listWithLimitAndRandom(this.limitPods, this.limitPerPod)
.then((requestsGrouped: T) => {
// We want to group requests by destinations pod and endpoint
const requestsToMake = this.buildRequestObjects(requestsGrouped)
// If there are no requests, abort
if (requests.length === 0) {
logger.info('No "%s" to make.', this.description)
return
}
// If there are no requests, abort
if (isEmpty(requestsToMake) === true) {
logger.info('No "%s" to make.', this.description)
return { goodPods: [], badPods: [] }
}
// We want to group requests by destinations pod and endpoint
const requestsToMakeGrouped = this.buildRequestObjects(requests)
logger.info('Making "%s" to friends.', this.description)
logger.info('Making "%s" to friends.', this.description)
const goodPods = []
const badPods = []
const goodPods = []
const badPods = []
return Promise.map(Object.keys(requestsToMake), hashKey => {
const requestToMake = requestsToMake[hashKey]
const toPod: PodInstance = requestToMake.toPod
eachLimit(Object.keys(requestsToMakeGrouped), REQUESTS_IN_PARALLEL, (hashKey, callbackEach) => {
const requestToMake = requestsToMakeGrouped[hashKey]
const toPod = requestToMake.toPod
return this.makeRequest(toPod, requestToMake.endpoint, requestToMake.datas)
.then(() => {
logger.debug('Removing requests for pod %s.', requestToMake.toPod.id, { requestsIds: requestToMake.ids })
goodPods.push(requestToMake.toPod.id)
this.makeRequest(toPod, requestToMake.endpoint, requestToMake.datas, (err) => {
if (err) {
badPods.push(requestToMake.toPod.id)
return callbackEach()
}
this.afterRequestHook()
logger.debug('Removing requests for pod %s.', requestToMake.toPod.id, { requestsIds: requestToMake.ids })
goodPods.push(requestToMake.toPod.id)
// Remove the pod id of these request ids
this.getRequestToPodModel().removeByRequestIdsAndPod(requestToMake.ids, requestToMake.toPod.id, callbackEach)
this.afterRequestHook()
})
}, () => {
// All the requests were made, we update the pods score
db.Pod.updatePodsScore(goodPods, badPods)
this.afterRequestsHook()
// Remove the pod id of these request ids
return this.getRequestToPodModel().removeByRequestIdsAndPod(requestToMake.ids, requestToMake.toPod.id)
})
.catch(err => {
badPods.push(requestToMake.toPod.id)
logger.info('Cannot make request to %s.', toPod.host, { error: err })
})
}, { concurrency: REQUESTS_IN_PARALLEL }).then(() => ({ goodPods, badPods }))
})
})
.then(({ goodPods, badPods }) => {
this.afterRequestsHook()
// All the requests were made, we update the pods score
return db.Pod.updatePodsScore(goodPods, badPods)
})
.catch(err => logger.error('Cannot get the list of "%s".', this.description, { error: err.stack }))
}
protected afterRequestHook () {

View file

@ -3,10 +3,8 @@ import * as Sequelize from 'sequelize'
import { database as db } from '../../initializers/database'
import { AbstractRequestScheduler } from './abstract-request-scheduler'
import { logger } from '../../helpers'
import {
REQUESTS_LIMIT_PODS,
REQUESTS_LIMIT_PER_POD
} from '../../initializers'
import { REQUESTS_LIMIT_PODS, REQUESTS_LIMIT_PER_POD } from '../../initializers'
import { RequestsGrouped } from '../../models'
import { RequestEndpoint } from '../../../shared'
export type RequestSchedulerOptions = {
@ -17,7 +15,7 @@ export type RequestSchedulerOptions = {
transaction: Sequelize.Transaction
}
class RequestScheduler extends AbstractRequestScheduler {
class RequestScheduler extends AbstractRequestScheduler<RequestsGrouped> {
constructor () {
super()
@ -36,11 +34,11 @@ class RequestScheduler extends AbstractRequestScheduler {
return db.RequestToPod
}
buildRequestObjects (requests: { [ toPodId: number ]: any }) {
buildRequestObjects (requestsGrouped: RequestsGrouped) {
const requestsToMakeGrouped = {}
Object.keys(requests).forEach(toPodId => {
requests[toPodId].forEach(data => {
Object.keys(requestsGrouped).forEach(toPodId => {
requestsGrouped[toPodId].forEach(data => {
const request = data.request
const pod = data.pod
const hashKey = toPodId + request.endpoint
@ -62,12 +60,12 @@ class RequestScheduler extends AbstractRequestScheduler {
return requestsToMakeGrouped
}
createRequest ({ type, endpoint, data, toIds, transaction }: RequestSchedulerOptions, callback: (err: Error) => void) {
createRequest ({ type, endpoint, data, toIds, transaction }: RequestSchedulerOptions) {
// TODO: check the setPods works
const podIds = []
// If there are no destination pods abort
if (toIds.length === 0) return callback(null)
if (toIds.length === 0) return undefined
toIds.forEach(toPod => {
podIds.push(toPod)
@ -85,20 +83,18 @@ class RequestScheduler extends AbstractRequestScheduler {
transaction
}
return db.Request.create(createQuery, dbRequestOptions).asCallback((err, request) => {
if (err) return callback(err)
return request.setPods(podIds, dbRequestOptions).asCallback(callback)
})
return db.Request.create(createQuery, dbRequestOptions)
.then(request => {
return request.setPods(podIds, dbRequestOptions)
})
}
// ---------------------------------------------------------------------------
afterRequestsHook () {
// Flush requests with no pod
this.getRequestModel().removeWithEmptyTo(err => {
if (err) logger.error('Error when removing requests with no pods.', { error: err })
})
this.getRequestModel().removeWithEmptyTo()
.catch(err => logger.error('Error when removing requests with no pods.', { error: err }))
}
}

View file

@ -7,6 +7,7 @@ import {
REQUESTS_VIDEO_EVENT_LIMIT_PER_POD,
REQUEST_VIDEO_EVENT_ENDPOINT
} from '../../initializers'
import { RequestsVideoEventGrouped } from '../../models'
import { RequestVideoEventType } from '../../../shared'
export type RequestVideoEventSchedulerOptions = {
@ -16,7 +17,7 @@ export type RequestVideoEventSchedulerOptions = {
transaction?: Sequelize.Transaction
}
class RequestVideoEventScheduler extends AbstractRequestScheduler {
class RequestVideoEventScheduler extends AbstractRequestScheduler<RequestsVideoEventGrouped> {
constructor () {
super()
@ -35,7 +36,7 @@ class RequestVideoEventScheduler extends AbstractRequestScheduler {
return db.RequestVideoEvent
}
buildRequestObjects (eventsToProcess: { [ toPodId: number ]: any }[]) {
buildRequestObjects (eventRequests: RequestsVideoEventGrouped) {
const requestsToMakeGrouped = {}
/* Example:
@ -50,8 +51,8 @@ class RequestVideoEventScheduler extends AbstractRequestScheduler {
// We group video events per video and per pod
// We add the counts of the same event types
Object.keys(eventsToProcess).forEach(toPodId => {
eventsToProcess[toPodId].forEach(eventToProcess => {
Object.keys(eventRequests).forEach(toPodId => {
eventRequests[toPodId].forEach(eventToProcess => {
if (!eventsPerVideoPerPod[toPodId]) eventsPerVideoPerPod[toPodId] = {}
if (!requestsToMakeGrouped[toPodId]) {
@ -97,7 +98,7 @@ class RequestVideoEventScheduler extends AbstractRequestScheduler {
return requestsToMakeGrouped
}
createRequest ({ type, videoId, count, transaction }: RequestVideoEventSchedulerOptions, callback: (err: Error) => void) {
createRequest ({ type, videoId, count, transaction }: RequestVideoEventSchedulerOptions) {
if (count === undefined) count = 1
const dbRequestOptions: Sequelize.CreateOptions = {}
@ -109,7 +110,7 @@ class RequestVideoEventScheduler extends AbstractRequestScheduler {
videoId
}
return db.RequestVideoEvent.create(createQuery, dbRequestOptions).asCallback(callback)
return db.RequestVideoEvent.create(createQuery, dbRequestOptions)
}
}

View file

@ -9,6 +9,7 @@ import {
REQUEST_VIDEO_QADU_ENDPOINT,
REQUEST_VIDEO_QADU_TYPES
} from '../../initializers'
import { RequestsVideoQaduGrouped } from '../../models'
import { RequestVideoQaduType } from '../../../shared'
export type RequestVideoQaduSchedulerOptions = {
@ -17,7 +18,7 @@ export type RequestVideoQaduSchedulerOptions = {
transaction?: Sequelize.Transaction
}
class RequestVideoQaduScheduler extends AbstractRequestScheduler {
class RequestVideoQaduScheduler extends AbstractRequestScheduler<RequestsVideoQaduGrouped> {
constructor () {
super()
@ -36,7 +37,7 @@ class RequestVideoQaduScheduler extends AbstractRequestScheduler {
return db.RequestVideoQadu
}
buildRequestObjects (requests: { [ toPodId: number ]: any }[]) {
buildRequestObjects (requests: RequestsVideoQaduGrouped) {
const requestsToMakeGrouped = {}
Object.keys(requests).forEach(toPodId => {
@ -105,20 +106,18 @@ class RequestVideoQaduScheduler extends AbstractRequestScheduler {
return requestsToMakeGrouped
}
createRequest ({ type, videoId, transaction }: RequestVideoQaduSchedulerOptions, callback: (err: Error) => void) {
createRequest ({ type, videoId, transaction }: RequestVideoQaduSchedulerOptions) {
const dbRequestOptions: Sequelize.BulkCreateOptions = {}
if (transaction) dbRequestOptions.transaction = transaction
// Send the update to all our friends
db.Pod.listAllIds(transaction, function (err, podIds) {
if (err) return callback(err)
return db.Pod.listAllIds(transaction).then(podIds => {
const queries = []
podIds.forEach(podId => {
queries.push({ type, videoId, podId })
})
return db.RequestVideoQadu.bulkCreate(queries, dbRequestOptions).asCallback(callback)
return db.RequestVideoQadu.bulkCreate(queries, dbRequestOptions)
})
}
}

View file

@ -9,41 +9,41 @@ import {
function checkSignature (req: express.Request, res: express.Response, next: express.NextFunction) {
const host = req.body.signature.host
db.Pod.loadByHost(host, function (err, pod) {
if (err) {
logger.error('Cannot get signed host in body.', { error: err })
return res.sendStatus(500)
}
if (pod === null) {
logger.error('Unknown pod %s.', host)
return res.sendStatus(403)
}
logger.debug('Checking signature from %s.', host)
let signatureShouldBe
// If there is data in the body the sender used it for its signature
// If there is no data we just use its host as signature
if (req.body.data) {
signatureShouldBe = req.body.data
} else {
signatureShouldBe = host
}
const signatureOk = peertubeCryptoCheckSignature(pod.publicKey, signatureShouldBe, req.body.signature.signature)
if (signatureOk === true) {
res.locals.secure = {
pod
db.Pod.loadByHost(host)
.then(pod => {
if (pod === null) {
logger.error('Unknown pod %s.', host)
return res.sendStatus(403)
}
return next()
}
logger.debug('Checking signature from %s.', host)
logger.error('Signature is not okay in body for %s.', req.body.signature.host)
return res.sendStatus(403)
})
let signatureShouldBe
// If there is data in the body the sender used it for its signature
// If there is no data we just use its host as signature
if (req.body.data) {
signatureShouldBe = req.body.data
} else {
signatureShouldBe = host
}
const signatureOk = peertubeCryptoCheckSignature(pod.publicKey, signatureShouldBe, req.body.signature.signature)
if (signatureOk === true) {
res.locals.secure = {
pod
}
return next()
}
logger.error('Signature is not okay in body for %s.', req.body.signature.host)
return res.sendStatus(403)
})
.catch(err => {
logger.error('Cannot get signed host in body.', { error: err })
return res.sendStatus(500)
})
}
// ---------------------------------------------------------------------------

View file

@ -19,19 +19,19 @@ function makeFriendsValidator (req: express.Request, res: express.Response, next
logger.debug('Checking makeFriends parameters', { parameters: req.body })
checkErrors(req, res, function () {
hasFriends(function (err, heHasFriends) {
if (err) {
hasFriends()
.then(heHasFriends => {
if (heHasFriends === true) {
// We need to quit our friends before make new ones
return res.sendStatus(409)
}
return next()
})
.catch(err => {
logger.error('Cannot know if we have friends.', { error: err })
res.sendStatus(500)
}
if (heHasFriends === true) {
// We need to quit our friends before make new ones
return res.sendStatus(409)
}
return next()
})
})
})
}
@ -42,19 +42,19 @@ function podsAddValidator (req: express.Request, res: express.Response, next: ex
logger.debug('Checking podsAdd parameters', { parameters: req.body })
checkErrors(req, res, function () {
db.Pod.loadByHost(req.body.host, function (err, pod) {
if (err) {
db.Pod.loadByHost(req.body.host)
.then(pod => {
// Pod with this host already exists
if (pod) {
return res.sendStatus(409)
}
return next()
})
.catch(err => {
logger.error('Cannot load pod by host.', { error: err })
res.sendStatus(500)
}
// Pod with this host already exists
if (pod) {
return res.sendStatus(409)
}
return next()
})
})
})
}

View file

@ -13,16 +13,16 @@ function usersAddValidator (req: express.Request, res: express.Response, next: e
logger.debug('Checking usersAdd parameters', { parameters: req.body })
checkErrors(req, res, function () {
db.User.loadByUsernameOrEmail(req.body.username, req.body.email, function (err, user) {
if (err) {
db.User.loadByUsernameOrEmail(req.body.username, req.body.email)
.then(user => {
if (user) return res.status(409).send('User already exists.')
next()
})
.catch(err => {
logger.error('Error in usersAdd request validator.', { error: err })
return res.sendStatus(500)
}
if (user) return res.status(409).send('User already exists.')
next()
})
})
})
}
@ -32,18 +32,18 @@ function usersRemoveValidator (req: express.Request, res: express.Response, next
logger.debug('Checking usersRemove parameters', { parameters: req.params })
checkErrors(req, res, function () {
db.User.loadById(req.params.id, function (err, user) {
if (err) {
db.User.loadById(req.params.id)
.then(user => {
if (!user) return res.status(404).send('User not found')
if (user.username === 'root') return res.status(400).send('Cannot remove the root user')
next()
})
.catch(err => {
logger.error('Error in usersRemove request validator.', { error: err })
return res.sendStatus(500)
}
if (!user) return res.status(404).send('User not found')
if (user.username === 'root') return res.status(400).send('Cannot remove the root user')
next()
})
})
})
}
@ -64,16 +64,16 @@ function usersVideoRatingValidator (req: express.Request, res: express.Response,
logger.debug('Checking usersVideoRating parameters', { parameters: req.params })
checkErrors(req, res, function () {
db.Video.load(req.params.videoId, function (err, video) {
if (err) {
db.Video.load(req.params.videoId)
.then(video => {
if (!video) return res.status(404).send('Video not found')
next()
})
.catch(err => {
logger.error('Error in user request validator.', { error: err })
return res.sendStatus(500)
}
if (!video) return res.status(404).send('Video not found')
next()
})
})
})
}

View file

@ -1,5 +1,4 @@
import 'express-validator'
import * as multer from 'multer'
import * as express from 'express'
import { database as db } from '../../initializers/database'
@ -24,18 +23,19 @@ function videosAddValidator (req: express.Request, res: express.Response, next:
checkErrors(req, res, function () {
const videoFile = req.files.videofile[0]
db.Video.getDurationFromFile(videoFile.path, function (err, duration) {
if (err) {
return res.status(400).send('Cannot retrieve metadata of the file.')
}
db.Video.getDurationFromFile(videoFile.path)
.then(duration => {
if (!isVideoDurationValid('' + duration)) {
return res.status(400).send('Duration of the video file is too big (max: ' + CONSTRAINTS_FIELDS.VIDEOS.DURATION.max + 's).')
}
if (!isVideoDurationValid(duration)) {
return res.status(400).send('Duration of the video file is too big (max: ' + CONSTRAINTS_FIELDS.VIDEOS.DURATION.max + 's).')
}
videoFile['duration'] = duration
next()
})
videoFile['duration'] = duration
next()
})
.catch(err => {
logger.error('Error in getting duration from file.', { error: err })
res.status(400).send('Cannot retrieve metadata of the file.')
})
})
}
@ -157,43 +157,42 @@ export {
// ---------------------------------------------------------------------------
function checkVideoExists (id: string, res: express.Response, callback: () => void) {
db.Video.loadAndPopulateAuthorAndPodAndTags(id, function (err, video) {
if (err) {
logger.error('Error in video request validator.', { error: err })
return res.sendStatus(500)
}
db.Video.loadAndPopulateAuthorAndPodAndTags(id).then(video => {
if (!video) return res.status(404).send('Video not found')
res.locals.video = video
callback()
})
.catch(err => {
logger.error('Error in video request validator.', { error: err })
return res.sendStatus(500)
})
}
function checkUserCanDeleteVideo (userId: number, res: express.Response, callback: () => void) {
// Retrieve the user who did the request
db.User.loadById(userId, function (err, user) {
if (err) {
db.User.loadById(userId)
.then(user => {
// Check if the user can delete the video
// The user can delete it if s/he is an admin
// Or if s/he is the video's author
if (user.isAdmin() === false) {
if (res.locals.video.isOwned() === false) {
return res.status(403).send('Cannot remove video of another pod')
}
if (res.locals.video.Author.userId !== res.locals.oauth.token.User.id) {
return res.status(403).send('Cannot remove video of another user')
}
}
// If we reach this comment, we can delete the video
callback()
})
.catch(err => {
logger.error('Error in video request validator.', { error: err })
return res.sendStatus(500)
}
// Check if the user can delete the video
// The user can delete it if s/he is an admin
// Or if s/he is the video's author
if (user.isAdmin() === false) {
if (res.locals.video.isOwned() === false) {
return res.status(403).send('Cannot remove video of another pod')
}
if (res.locals.video.Author.userId !== res.locals.oauth.token.User.id) {
return res.status(403).send('Cannot remove video of another user')
}
}
// If we reach this comment, we can delete the video
callback()
})
})
}
function checkVideoIsBlacklistable (req: express.Request, res: express.Response, callback: () => void) {

View file

@ -1,11 +1,13 @@
import * as Sequelize from 'sequelize'
import * as Promise from 'bluebird'
export namespace ApplicationMethods {
export type LoadMigrationVersionCallback = (err: Error, version: number) => void
export type LoadMigrationVersion = (callback: LoadMigrationVersionCallback) => void
export type LoadMigrationVersion = () => Promise<number>
export type UpdateMigrationVersionCallback = (err: Error, applicationInstance: ApplicationAttributes) => void
export type UpdateMigrationVersion = (newVersion: number, transaction: Sequelize.Transaction, callback: UpdateMigrationVersionCallback) => void
export type UpdateMigrationVersion = (
newVersion: number,
transaction: Sequelize.Transaction
) => Promise<[ number, ApplicationInstance[] ]>
}
export interface ApplicationClass {

View file

@ -2,7 +2,6 @@ import * as Sequelize from 'sequelize'
import { addMethodsToModel } from '../utils'
import {
ApplicationClass,
ApplicationAttributes,
ApplicationInstance,
@ -35,23 +34,19 @@ export default function defineApplication (sequelize: Sequelize.Sequelize, DataT
// ---------------------------------------------------------------------------
loadMigrationVersion = function (callback: ApplicationMethods.LoadMigrationVersionCallback) {
loadMigrationVersion = function () {
const query = {
attributes: [ 'migrationVersion' ]
}
return Application.findOne(query).asCallback(function (err, data) {
const version = data ? data.migrationVersion : null
return callback(err, version)
})
return Application.findOne(query).then(data => data ? data.migrationVersion : null)
}
updateMigrationVersion = function (newVersion: number, transaction: Sequelize.Transaction, callback: ApplicationMethods.UpdateMigrationVersionCallback) {
updateMigrationVersion = function (newVersion: number, transaction: Sequelize.Transaction) {
const options: Sequelize.UpdateOptions = {
where: {},
transaction: transaction
}
return Application.update({ migrationVersion: newVersion }, options).asCallback(callback)
return Application.update({ migrationVersion: newVersion }, options)
}

View file

@ -1,10 +1,10 @@
import * as Sequelize from 'sequelize'
import * as Promise from 'bluebird'
import { JobState } from '../../../shared/models/job.model'
export namespace JobMethods {
export type ListWithLimitCallback = (err: Error, jobInstances: JobInstance[]) => void
export type ListWithLimit = (limit: number, state: JobState, callback: ListWithLimitCallback) => void
export type ListWithLimit = (limit: number, state: JobState) => Promise<JobInstance[]>
}
export interface JobClass {

View file

@ -5,7 +5,6 @@ import { JOB_STATES } from '../../initializers'
import { addMethodsToModel } from '../utils'
import {
JobClass,
JobInstance,
JobAttributes,
@ -49,7 +48,7 @@ export default function defineJob (sequelize: Sequelize.Sequelize, DataTypes: Se
// ---------------------------------------------------------------------------
listWithLimit = function (limit: number, state: JobState, callback: JobMethods.ListWithLimitCallback) {
listWithLimit = function (limit: number, state: JobState) {
const query = {
order: [
[ 'id', 'ASC' ]
@ -60,5 +59,5 @@ listWithLimit = function (limit: number, state: JobState, callback: JobMethods.L
}
}
return Job.findAll(query).asCallback(callback)
return Job.findAll(query)
}

View file

@ -1,13 +1,12 @@
import * as Sequelize from 'sequelize'
import * as Promise from 'bluebird'
export namespace OAuthClientMethods {
export type CountTotalCallback = (err: Error, total: number) => void
export type CountTotal = (callback: CountTotalCallback) => void
export type CountTotal = () => Promise<number>
export type LoadFirstClientCallback = (err: Error, client: OAuthClientInstance) => void
export type LoadFirstClient = (callback: LoadFirstClientCallback) => void
export type LoadFirstClient = () => Promise<OAuthClientInstance>
export type GetByIdAndSecret = (clientId, clientSecret) => void
export type GetByIdAndSecret = (clientId: string, clientSecret: string) => Promise<OAuthClientInstance>
}
export interface OAuthClientClass {

View file

@ -2,7 +2,6 @@ import * as Sequelize from 'sequelize'
import { addMethodsToModel } from '../utils'
import {
OAuthClientClass,
OAuthClientInstance,
OAuthClientAttributes,
@ -67,12 +66,12 @@ function associate (models) {
})
}
countTotal = function (callback: OAuthClientMethods.CountTotalCallback) {
return OAuthClient.count().asCallback(callback)
countTotal = function () {
return OAuthClient.count()
}
loadFirstClient = function (callback: OAuthClientMethods.LoadFirstClientCallback) {
return OAuthClient.findOne().asCallback(callback)
loadFirstClient = function () {
return OAuthClient.findOne()
}
getByIdAndSecret = function (clientId: string, clientSecret: string) {

View file

@ -1,5 +1,5 @@
import * as Sequelize from 'sequelize'
import * as Bluebird from 'bluebird'
import * as Promise from 'bluebird'
import { UserModel } from '../user'
@ -15,12 +15,11 @@ export type OAuthTokenInfo = {
}
export namespace OAuthTokenMethods {
export type GetByRefreshTokenAndPopulateClient = (refreshToken: string) => Bluebird<OAuthTokenInfo>
export type GetByTokenAndPopulateUser = (bearerToken: string) => Bluebird<OAuthTokenInstance>
export type GetByRefreshTokenAndPopulateUser = (refreshToken: string) => Bluebird<OAuthTokenInstance>
export type GetByRefreshTokenAndPopulateClient = (refreshToken: string) => Promise<OAuthTokenInfo>
export type GetByTokenAndPopulateUser = (bearerToken: string) => Promise<OAuthTokenInstance>
export type GetByRefreshTokenAndPopulateUser = (refreshToken: string) => Promise<OAuthTokenInstance>
export type RemoveByUserIdCallback = (err: Error) => void
export type RemoveByUserId = (userId, callback) => void
export type RemoveByUserId = (userId) => Promise<number>
}
export interface OAuthTokenClass {

View file

@ -4,7 +4,6 @@ import { logger } from '../../helpers'
import { addMethodsToModel } from '../utils'
import {
OAuthTokenClass,
OAuthTokenInstance,
OAuthTokenAttributes,
@ -149,12 +148,12 @@ getByRefreshTokenAndPopulateUser = function (refreshToken: string) {
})
}
removeByUserId = function (userId, callback) {
removeByUserId = function (userId: number) {
const query = {
where: {
userId: userId
}
}
return OAuthToken.destroy(query).asCallback(callback)
return OAuthToken.destroy(query)
}

View file

@ -1,4 +1,5 @@
import * as Sequelize from 'sequelize'
import * as Promise from 'bluebird'
// Don't use barrel, import just what we need
import { Pod as FormatedPod } from '../../../shared/models/pod.model'
@ -6,32 +7,23 @@ import { Pod as FormatedPod } from '../../../shared/models/pod.model'
export namespace PodMethods {
export type ToFormatedJSON = (this: PodInstance) => FormatedPod
export type CountAllCallback = (err: Error, total: number) => void
export type CountAll = (callback) => void
export type CountAll = () => Promise<number>
export type IncrementScoresCallback = (err: Error) => void
export type IncrementScores = (ids: number[], value: number, callback?: IncrementScoresCallback) => void
export type IncrementScores = (ids: number[], value: number) => Promise<[ number, PodInstance[] ]>
export type ListCallback = (err: Error, podInstances?: PodInstance[]) => void
export type List = (callback: ListCallback) => void
export type List = () => Promise<PodInstance[]>
export type ListAllIdsCallback = (err: Error, ids?: number[]) => void
export type ListAllIds = (transaction: Sequelize.Transaction, callback: ListAllIdsCallback) => void
export type ListAllIds = (transaction: Sequelize.Transaction) => Promise<number[]>
export type ListRandomPodIdsWithRequestCallback = (err: Error, podInstanceIds?: number[]) => void
export type ListRandomPodIdsWithRequest = (limit: number, tableWithPods: string, tableWithPodsJoins: string, callback: ListRandomPodIdsWithRequestCallback) => void
export type ListRandomPodIdsWithRequest = (limit: number, tableWithPods: string, tableWithPodsJoins: string) => Promise<number[]>
export type ListBadPodsCallback = (err: Error, podInstances?: PodInstance[]) => void
export type ListBadPods = (callback: ListBadPodsCallback) => void
export type ListBadPods = () => Promise<PodInstance[]>
export type LoadCallback = (err: Error, podInstance: PodInstance) => void
export type Load = (id: number, callback: LoadCallback) => void
export type Load = (id: number) => Promise<PodInstance>
export type LoadByHostCallback = (err: Error, podInstance: PodInstance) => void
export type LoadByHost = (host: string, callback: LoadByHostCallback) => void
export type LoadByHost = (host: string) => Promise<PodInstance>
export type RemoveAllCallback = (err: Error) => void
export type RemoveAll = (callback: RemoveAllCallback) => void
export type RemoveAll = () => Promise<number>
export type UpdatePodsScore = (goodPods: number[], badPods: number[]) => void
}

View file

@ -1,4 +1,3 @@
import { each, waterfall } from 'async'
import { map } from 'lodash'
import * as Sequelize from 'sequelize'
@ -7,7 +6,6 @@ import { logger, isHostValid } from '../../helpers'
import { addMethodsToModel } from '../utils'
import {
PodClass,
PodInstance,
PodAttributes,
@ -118,13 +116,11 @@ function associate (models) {
})
}
countAll = function (callback: PodMethods.CountAllCallback) {
return Pod.count().asCallback(callback)
countAll = function () {
return Pod.count()
}
incrementScores = function (ids: number[], value: number, callback?: PodMethods.IncrementScoresCallback) {
if (!callback) callback = function () { /* empty */ }
incrementScores = function (ids: number[], value: number) {
const update = {
score: Sequelize.literal('score +' + value)
}
@ -139,33 +135,28 @@ incrementScores = function (ids: number[], value: number, callback?: PodMethods.
validate: false
}
return Pod.update(update, options).asCallback(callback)
return Pod.update(update, options)
}
list = function (callback: PodMethods.ListCallback) {
return Pod.findAll().asCallback(callback)
list = function () {
return Pod.findAll()
}
listAllIds = function (transaction: Sequelize.Transaction, callback: PodMethods.ListAllIdsCallback) {
const query: any = {
attributes: [ 'id' ]
listAllIds = function (transaction: Sequelize.Transaction) {
const query: Sequelize.FindOptions = {
attributes: [ 'id' ],
transaction
}
if (transaction !== null) query.transaction = transaction
return Pod.findAll(query).asCallback(function (err: Error, pods) {
if (err) return callback(err)
return callback(null, map(pods, 'id'))
return Pod.findAll(query).then(pods => {
return map(pods, 'id')
})
}
listRandomPodIdsWithRequest = function (limit: number, tableWithPods: string, tableWithPodsJoins: string, callback: PodMethods.ListRandomPodIdsWithRequestCallback) {
Pod.count().asCallback(function (err, count) {
if (err) return callback(err)
listRandomPodIdsWithRequest = function (limit: number, tableWithPods: string, tableWithPodsJoins: string) {
return Pod.count().then(count => {
// Optimization...
if (count === 0) return callback(null, [])
if (count === 0) return []
let start = Math.floor(Math.random() * count) - limit
if (start < 0) start = 0
@ -186,56 +177,55 @@ listRandomPodIdsWithRequest = function (limit: number, tableWithPods: string, ta
}
}
return Pod.findAll(query).asCallback(function (err, pods) {
if (err) return callback(err)
return callback(null, map(pods, 'id'))
return Pod.findAll(query).then(pods => {
return map(pods, 'id')
})
})
}
listBadPods = function (callback: PodMethods.ListBadPodsCallback) {
listBadPods = function () {
const query = {
where: {
score: { $lte: 0 }
}
}
return Pod.findAll(query).asCallback(callback)
return Pod.findAll(query)
}
load = function (id: number, callback: PodMethods.LoadCallback) {
return Pod.findById(id).asCallback(callback)
load = function (id: number) {
return Pod.findById(id)
}
loadByHost = function (host: string, callback: PodMethods.LoadByHostCallback) {
loadByHost = function (host: string) {
const query = {
where: {
host: host
}
}
return Pod.findOne(query).asCallback(callback)
return Pod.findOne(query)
}
removeAll = function (callback: PodMethods.RemoveAllCallback) {
return Pod.destroy().asCallback(callback)
removeAll = function () {
return Pod.destroy()
}
updatePodsScore = function (goodPods: number[], badPods: number[]) {
logger.info('Updating %d good pods and %d bad pods scores.', goodPods.length, badPods.length)
if (goodPods.length !== 0) {
incrementScores(goodPods, PODS_SCORE.BONUS, function (err) {
if (err) logger.error('Cannot increment scores of good pods.', { error: err })
incrementScores(goodPods, PODS_SCORE.BONUS).catch(err => {
logger.error('Cannot increment scores of good pods.', { error: err })
})
}
if (badPods.length !== 0) {
incrementScores(badPods, PODS_SCORE.MALUS, function (err) {
if (err) logger.error('Cannot decrement scores of bad pods.', { error: err })
removeBadPods()
})
incrementScores(badPods, PODS_SCORE.MALUS)
.then(() => removeBadPods())
.catch(err => {
if (err) logger.error('Cannot decrement scores of bad pods.', { error: err })
})
}
}
@ -243,32 +233,19 @@ updatePodsScore = function (goodPods: number[], badPods: number[]) {
// Remove pods with a score of 0 (too many requests where they were unreachable)
function removeBadPods () {
waterfall([
function findBadPods (callback) {
listBadPods(function (err, pods) {
if (err) {
logger.error('Cannot find bad pods.', { error: err })
return callback(err)
}
return callback(null, pods)
})
},
function removeTheseBadPods (pods, callback) {
each(pods, function (pod: any, callbackEach) {
pod.destroy().asCallback(callbackEach)
}, function (err) {
return callback(err, pods.length)
})
}
], function (err, numberOfPodsRemoved) {
if (err) {
return listBadPods()
.then(pods => {
const podsRemovePromises = pods.map(pod => pod.destroy())
return Promise.all(podsRemovePromises).then(() => pods.length)
})
.then(numberOfPodsRemoved => {
if (numberOfPodsRemoved) {
logger.info('Removed %d pods.', numberOfPodsRemoved)
} else {
logger.info('No need to remove bad pods.')
}
})
.catch(err => {
logger.error('Cannot remove bad pods.', { error: err })
} else if (numberOfPodsRemoved) {
logger.info('Removed %d pods.', numberOfPodsRemoved)
} else {
logger.info('No need to remove bad pods.')
}
})
})
}

View file

@ -0,0 +1,12 @@
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>
}

View file

@ -1,3 +1,4 @@
export * from './abstract-request-interface'
export * from './request-interface'
export * from './request-to-pod-interface'
export * from './request-video-event-interface'

View file

@ -1,5 +1,7 @@
import * as Sequelize from 'sequelize'
import * as Promise from 'bluebird'
import { AbstractRequestClass } from './abstract-request-interface'
import { PodInstance, PodAttributes } from '../pod'
import { RequestEndpoint } from '../../../shared/models/request-scheduler.model'
@ -11,20 +13,16 @@ export type RequestsGrouped = {
}
export namespace RequestMethods {
export type CountTotalRequestsCallback = (err: Error, total: number) => void
export type CountTotalRequests = (callback: CountTotalRequestsCallback) => void
export type CountTotalRequests = () => Promise<number>
export type ListWithLimitAndRandomCallback = (err: Error, requestsGrouped?: RequestsGrouped) => void
export type ListWithLimitAndRandom = (limitPods, limitRequestsPerPod, callback: ListWithLimitAndRandomCallback) => void
export type ListWithLimitAndRandom = (limitPods: number, limitRequestsPerPod: number) => Promise<RequestsGrouped>
export type RemoveWithEmptyToCallback = (err: Error) => void
export type RemoveWithEmptyTo = (callback: RemoveWithEmptyToCallback) => void
export type RemoveWithEmptyTo = () => Promise<number>
export type RemoveAllCallback = (err: Error) => void
export type RemoveAll = (callback: RemoveAllCallback) => void
export type RemoveAll = () => Promise<void>
}
export interface RequestClass {
export interface RequestClass extends AbstractRequestClass<RequestsGrouped> {
countTotalRequests: RequestMethods.CountTotalRequests
listWithLimitAndRandom: RequestMethods.ListWithLimitAndRandom
removeWithEmptyTo: RequestMethods.RemoveWithEmptyTo

View file

@ -1,11 +1,13 @@
import * as Sequelize from 'sequelize'
import * as Promise from 'bluebird'
import { AbstractRequestToPodClass } from './abstract-request-interface'
export namespace RequestToPodMethods {
export type RemoveByRequestIdsAndPodCallback = (err: Error) => void
export type RemoveByRequestIdsAndPod = (requestsIds: number[], podId: number, callback?: RemoveByRequestIdsAndPodCallback) => void
export type RemoveByRequestIdsAndPod = (requestsIds: number[], podId: number) => Promise<number>
}
export interface RequestToPodClass {
export interface RequestToPodClass extends AbstractRequestToPodClass {
removeByRequestIdsAndPod: RequestToPodMethods.RemoveByRequestIdsAndPod
}

View file

@ -2,7 +2,6 @@ import * as Sequelize from 'sequelize'
import { addMethodsToModel } from '../utils'
import {
RequestToPodClass,
RequestToPodInstance,
RequestToPodAttributes,
@ -38,9 +37,7 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
// ---------------------------------------------------------------------------
removeByRequestIdsAndPod = function (requestsIds: number[], podId: number, callback?: RequestToPodMethods.RemoveByRequestIdsAndPodCallback) {
if (!callback) callback = function () { /* empty */ }
removeByRequestIdsAndPod = function (requestsIds: number[], podId: number) {
const query = {
where: {
requestId: {
@ -50,5 +47,5 @@ removeByRequestIdsAndPod = function (requestsIds: number[], podId: number, callb
}
}
RequestToPod.destroy(query).asCallback(callback)
return RequestToPod.destroy(query)
}

View file

@ -1,5 +1,7 @@
import * as Sequelize from 'sequelize'
import * as Promise from 'bluebird'
import { AbstractRequestClass, AbstractRequestToPodClass } from './abstract-request-interface'
import { VideoInstance } from '../video'
import { PodInstance } from '../pod'
@ -16,20 +18,16 @@ export type RequestsVideoEventGrouped = {
}
export namespace RequestVideoEventMethods {
export type CountTotalRequestsCallback = (err: Error, total: number) => void
export type CountTotalRequests = (callback: CountTotalRequestsCallback) => void
export type CountTotalRequests = () => Promise<number>
export type ListWithLimitAndRandomCallback = (err: Error, requestsGrouped?: RequestsVideoEventGrouped) => void
export type ListWithLimitAndRandom = (limitPods: number, limitRequestsPerPod: number, callback: ListWithLimitAndRandomCallback) => void
export type ListWithLimitAndRandom = (limitPods: number, limitRequestsPerPod: number) => Promise<RequestsVideoEventGrouped>
export type RemoveByRequestIdsAndPodCallback = () => void
export type RemoveByRequestIdsAndPod = (ids: number[], podId: number, callback: RemoveByRequestIdsAndPodCallback) => void
export type RemoveByRequestIdsAndPod = (ids: number[], podId: number) => Promise<number>
export type RemoveAllCallback = () => void
export type RemoveAll = (callback: RemoveAllCallback) => void
export type RemoveAll = () => Promise<void>
}
export interface RequestVideoEventClass {
export interface RequestVideoEventClass extends AbstractRequestClass<RequestsVideoEventGrouped>, AbstractRequestToPodClass {
countTotalRequests: RequestVideoEventMethods.CountTotalRequests
listWithLimitAndRandom: RequestVideoEventMethods.ListWithLimitAndRandom
removeByRequestIdsAndPod: RequestVideoEventMethods.RemoveByRequestIdsAndPod
@ -41,10 +39,12 @@ export interface RequestVideoEventAttributes {
count: number
}
export interface RequestVideoEventInstance extends RequestVideoEventClass, RequestVideoEventAttributes, Sequelize.Instance<RequestVideoEventAttributes> {
export interface RequestVideoEventInstance
extends RequestVideoEventClass, RequestVideoEventAttributes, Sequelize.Instance<RequestVideoEventAttributes> {
id: number
Video: VideoInstance
}
export interface RequestVideoEventModel extends RequestVideoEventClass, Sequelize.Model<RequestVideoEventInstance, RequestVideoEventAttributes> {}
export interface RequestVideoEventModel
extends RequestVideoEventClass, Sequelize.Model<RequestVideoEventInstance, RequestVideoEventAttributes> {}

View file

@ -10,7 +10,6 @@ import { REQUEST_VIDEO_EVENT_TYPES } from '../../initializers'
import { isVideoEventCountValid } from '../../helpers'
import { addMethodsToModel } from '../utils'
import {
RequestVideoEventClass,
RequestVideoEventInstance,
RequestVideoEventAttributes,
@ -77,23 +76,21 @@ function associate (models) {
})
}
countTotalRequests = function (callback: RequestVideoEventMethods.CountTotalRequestsCallback) {
countTotalRequests = function () {
const query = {}
return RequestVideoEvent.count(query).asCallback(callback)
return RequestVideoEvent.count(query)
}
listWithLimitAndRandom = function (limitPods: number, limitRequestsPerPod: number, callback: RequestVideoEventMethods.ListWithLimitAndRandomCallback) {
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 "Videos" ON "Videos"."authorId" = "Authors"."id" ' +
'INNER JOIN "RequestVideoEvents" ON "RequestVideoEvents"."videoId" = "Videos"."id"'
Pod.listRandomPodIdsWithRequest(limitPods, 'Authors', podJoins, function (err, podIds) {
if (err) return callback(err)
return Pod.listRandomPodIdsWithRequest(limitPods, 'Authors', podJoins).then(podIds => {
// We don't have friends that have requests
if (podIds.length === 0) return callback(null, [])
if (podIds.length === 0) return []
const query = {
order: [
@ -121,16 +118,14 @@ listWithLimitAndRandom = function (limitPods: number, limitRequestsPerPod: numbe
]
}
RequestVideoEvent.findAll(query).asCallback(function (err, requests) {
if (err) return callback(err)
return RequestVideoEvent.findAll(query).then(requests => {
const requestsGrouped = groupAndTruncateRequests(requests, limitRequestsPerPod)
return callback(err, requestsGrouped)
return requestsGrouped
})
})
}
removeByRequestIdsAndPod = function (ids: number[], podId: number, callback: RequestVideoEventMethods.RemoveByRequestIdsAndPodCallback) {
removeByRequestIdsAndPod = function (ids: number[], podId: number) {
const query = {
where: {
id: {
@ -152,12 +147,12 @@ removeByRequestIdsAndPod = function (ids: number[], podId: number, callback: Req
]
}
RequestVideoEvent.destroy(query).asCallback(callback)
return RequestVideoEvent.destroy(query)
}
removeAll = function (callback: RequestVideoEventMethods.RemoveAllCallback) {
removeAll = function () {
// Delete all requests
RequestVideoEvent.truncate({ cascade: true }).asCallback(callback)
return RequestVideoEvent.truncate({ cascade: true })
}
// ---------------------------------------------------------------------------

View file

@ -1,5 +1,7 @@
import * as Sequelize from 'sequelize'
import * as Promise from 'bluebird'
import { AbstractRequestClass, AbstractRequestToPodClass } from './abstract-request-interface'
import { VideoInstance } from '../video'
import { PodInstance } from '../pod'
@ -14,20 +16,16 @@ export type RequestsVideoQaduGrouped = {
}
export namespace RequestVideoQaduMethods {
export type CountTotalRequestsCallback = (err: Error, total: number) => void
export type CountTotalRequests = (callback: CountTotalRequestsCallback) => void
export type CountTotalRequests = () => Promise<number>
export type ListWithLimitAndRandomCallback = (err: Error, requestsGrouped?: RequestsVideoQaduGrouped) => void
export type ListWithLimitAndRandom = (limitPods: number, limitRequestsPerPod: number, callback: ListWithLimitAndRandomCallback) => void
export type ListWithLimitAndRandom = (limitPods: number, limitRequestsPerPod: number) => Promise<RequestsVideoQaduGrouped>
export type RemoveByRequestIdsAndPodCallback = () => void
export type RemoveByRequestIdsAndPod = (ids: number[], podId: number, callback: RemoveByRequestIdsAndPodCallback) => void
export type RemoveByRequestIdsAndPod = (ids: number[], podId: number) => Promise<number>
export type RemoveAllCallback = () => void
export type RemoveAll = (callback: RemoveAllCallback) => void
export type RemoveAll = () => Promise<void>
}
export interface RequestVideoQaduClass {
export interface RequestVideoQaduClass extends AbstractRequestClass<RequestsVideoQaduGrouped>, AbstractRequestToPodClass {
countTotalRequests: RequestVideoQaduMethods.CountTotalRequests
listWithLimitAndRandom: RequestVideoQaduMethods.ListWithLimitAndRandom
removeByRequestIdsAndPod: RequestVideoQaduMethods.RemoveByRequestIdsAndPod
@ -38,11 +36,13 @@ export interface RequestVideoQaduAttributes {
type: RequestVideoQaduType
}
export interface RequestVideoQaduInstance extends RequestVideoQaduClass, RequestVideoQaduAttributes, Sequelize.Instance<RequestVideoQaduAttributes> {
export interface RequestVideoQaduInstance
extends RequestVideoQaduClass, RequestVideoQaduAttributes, Sequelize.Instance<RequestVideoQaduAttributes> {
id: number
Pod: PodInstance
Video: VideoInstance
}
export interface RequestVideoQaduModel extends RequestVideoQaduClass, Sequelize.Model<RequestVideoQaduInstance, RequestVideoQaduAttributes> {}
export interface RequestVideoQaduModel
extends RequestVideoQaduClass, Sequelize.Model<RequestVideoQaduInstance, RequestVideoQaduAttributes> {}

View file

@ -16,7 +16,6 @@ import { database as db } from '../../initializers/database'
import { REQUEST_VIDEO_QADU_TYPES } from '../../initializers'
import { addMethodsToModel } from '../utils'
import {
RequestVideoQaduClass,
RequestVideoQaduInstance,
RequestVideoQaduAttributes,
@ -83,20 +82,18 @@ function associate (models) {
})
}
countTotalRequests = function (callback: RequestVideoQaduMethods.CountTotalRequestsCallback) {
countTotalRequests = function () {
const query = {}
return RequestVideoQadu.count(query).asCallback(callback)
return RequestVideoQadu.count(query)
}
listWithLimitAndRandom = function (limitPods: number, limitRequestsPerPod: number, callback: RequestVideoQaduMethods.ListWithLimitAndRandomCallback) {
listWithLimitAndRandom = function (limitPods: number, limitRequestsPerPod: number) {
const Pod = db.Pod
const tableJoin = ''
Pod.listRandomPodIdsWithRequest(limitPods, 'RequestVideoQadus', tableJoin, function (err, podIds) {
if (err) return callback(err)
return Pod.listRandomPodIdsWithRequest(limitPods, 'RequestVideoQadus', tableJoin).then(podIds => {
// We don't have friends that have requests
if (podIds.length === 0) return callback(null, [])
if (podIds.length === 0) return []
const query = {
include: [
@ -114,16 +111,14 @@ listWithLimitAndRandom = function (limitPods: number, limitRequestsPerPod: numbe
]
}
RequestVideoQadu.findAll(query).asCallback(function (err, requests) {
if (err) return callback(err)
return RequestVideoQadu.findAll(query).then(requests => {
const requestsGrouped = groupAndTruncateRequests(requests, limitRequestsPerPod)
return callback(err, requestsGrouped)
return requestsGrouped
})
})
}
removeByRequestIdsAndPod = function (ids: number[], podId: number, callback: RequestVideoQaduMethods.RemoveByRequestIdsAndPodCallback) {
removeByRequestIdsAndPod = function (ids: number[], podId: number) {
const query = {
where: {
id: {
@ -133,12 +128,12 @@ removeByRequestIdsAndPod = function (ids: number[], podId: number, callback: Req
}
}
RequestVideoQadu.destroy(query).asCallback(callback)
return RequestVideoQadu.destroy(query)
}
removeAll = function (callback: RequestVideoQaduMethods.RemoveAllCallback) {
removeAll = function () {
// Delete all requests
RequestVideoQadu.truncate({ cascade: true }).asCallback(callback)
return RequestVideoQadu.truncate({ cascade: true })
}
// ---------------------------------------------------------------------------

View file

@ -5,7 +5,6 @@ import { database as db } from '../../initializers/database'
import { REQUEST_ENDPOINTS } from '../../initializers'
import { addMethodsToModel } from '../utils'
import {
RequestClass,
RequestInstance,
RequestAttributes,
@ -60,25 +59,23 @@ function associate (models) {
})
}
countTotalRequests = function (callback: RequestMethods.CountTotalRequestsCallback) {
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).asCallback(callback)
return Request.count(query)
}
listWithLimitAndRandom = function (limitPods: number, limitRequestsPerPod: number, callback: RequestMethods.ListWithLimitAndRandomCallback) {
listWithLimitAndRandom = function (limitPods: number, limitRequestsPerPod: number) {
const Pod = db.Pod
const tableJoin = ''
Pod.listRandomPodIdsWithRequest(limitPods, 'RequestToPods', '', function (err, podIds) {
if (err) return callback(err)
return Pod.listRandomPodIdsWithRequest(limitPods, 'RequestToPods', tableJoin).then(podIds => {
// We don't have friends that have requests
if (podIds.length === 0) return callback(null, [])
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!
@ -98,23 +95,20 @@ listWithLimitAndRandom = function (limitPods: number, limitRequestsPerPod: numbe
]
}
Request.findAll(query).asCallback(function (err, requests) {
if (err) return callback(err)
return Request.findAll(query).then(requests => {
const requestsGrouped = groupAndTruncateRequests(requests, limitRequestsPerPod)
return callback(err, requestsGrouped)
return requestsGrouped
})
})
}
removeAll = function (callback: RequestMethods.RemoveAllCallback) {
removeAll = function () {
// Delete all requests
Request.truncate({ cascade: true }).asCallback(callback)
return Request.truncate({ cascade: true })
}
removeWithEmptyTo = function (callback?: RequestMethods.RemoveWithEmptyToCallback) {
if (!callback) callback = function () { /* empty */ }
removeWithEmptyTo = function () {
const query = {
where: {
id: {
@ -125,7 +119,7 @@ removeWithEmptyTo = function (callback?: RequestMethods.RemoveWithEmptyToCallbac
}
}
Request.destroy(query).asCallback(callback)
return Request.destroy(query)
}
// ---------------------------------------------------------------------------

View file

@ -1,35 +1,29 @@
import * as Sequelize from 'sequelize'
import * as Bluebird from 'bluebird'
import * as Promise from 'bluebird'
// Don't use barrel, import just what we need
import { UserRole, User as FormatedUser } from '../../../shared/models/user.model'
import { ResultList } from '../../../shared/models/result-list.model'
export namespace UserMethods {
export type IsPasswordMatchCallback = (err: Error, same: boolean) => void
export type IsPasswordMatch = (this: UserInstance, password: string, callback: IsPasswordMatchCallback) => void
export type IsPasswordMatch = (this: UserInstance, password: string) => Promise<boolean>
export type ToFormatedJSON = (this: UserInstance) => FormatedUser
export type IsAdmin = (this: UserInstance) => boolean
export type CountTotalCallback = (err: Error, total: number) => void
export type CountTotal = (callback: CountTotalCallback) => void
export type CountTotal = () => Promise<number>
export type GetByUsername = (username: string) => Bluebird<UserInstance>
export type GetByUsername = (username: string) => Promise<UserInstance>
export type ListCallback = (err: Error, userInstances: UserInstance[]) => void
export type List = (callback: ListCallback) => void
export type List = () => Promise<UserInstance[]>
export type ListForApiCallback = (err: Error, userInstances?: UserInstance[], total?: number) => void
export type ListForApi = (start: number, count: number, sort: string, callback: ListForApiCallback) => void
export type ListForApi = (start: number, count: number, sort: string) => Promise< ResultList<UserInstance> >
export type LoadByIdCallback = (err: Error, userInstance: UserInstance) => void
export type LoadById = (id: number, callback: LoadByIdCallback) => void
export type LoadById = (id: number) => Promise<UserInstance>
export type LoadByUsernameCallback = (err: Error, userInstance: UserInstance) => void
export type LoadByUsername = (username: string, callback: LoadByUsernameCallback) => void
export type LoadByUsername = (username: string) => Promise<UserInstance>
export type LoadByUsernameOrEmailCallback = (err: Error, userInstance: UserInstance) => void
export type LoadByUsernameOrEmail = (username: string, email: string, callback: LoadByUsernameOrEmailCallback) => void
export type LoadByUsernameOrEmail = (username: string, email: string) => Promise<UserInstance>
}
export interface UserClass {

View file

@ -1,10 +1,10 @@
import * as Sequelize from 'sequelize'
import * as Promise from 'bluebird'
import { VideoRateType } from '../../../shared/models/user-video-rate.model'
export namespace UserVideoRateMethods {
export type LoadCallback = (err: Error, userVideoRateInstance: UserVideoRateInstance) => void
export type Load = (userId: number, videoId: string, transaction: Sequelize.Transaction, callback: LoadCallback) => void
export type Load = (userId: number, videoId: string, transaction: Sequelize.Transaction) => Promise<UserVideoRateInstance>
}
export interface UserVideoRateClass {

View file

@ -8,7 +8,6 @@ import { VIDEO_RATE_TYPES } from '../../initializers'
import { addMethodsToModel } from '../utils'
import {
UserVideoRateClass,
UserVideoRateInstance,
UserVideoRateAttributes,
@ -66,7 +65,7 @@ function associate (models) {
})
}
load = function (userId: number, videoId: string, transaction: Sequelize.Transaction, callback: UserVideoRateMethods.LoadCallback) {
load = function (userId: number, videoId: string, transaction: Sequelize.Transaction) {
const options: Sequelize.FindOptions = {
where: {
userId,
@ -75,5 +74,5 @@ load = function (userId: number, videoId: string, transaction: Sequelize.Transac
}
if (transaction) options.transaction = transaction
return UserVideoRate.findOne(options).asCallback(callback)
return UserVideoRate.findOne(options)
}

View file

@ -13,7 +13,6 @@ import {
import { addMethodsToModel } from '../utils'
import {
UserClass,
UserInstance,
UserAttributes,
@ -118,21 +117,16 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
}
function beforeCreateOrUpdate (user: UserInstance) {
return new Promise(function (resolve, reject) {
cryptPassword(user.password, function (err, hash) {
if (err) return reject(err)
user.password = hash
return resolve()
})
return cryptPassword(user.password).then(hash => {
user.password = hash
return undefined
})
}
// ------------------------------ METHODS ------------------------------
isPasswordMatch = function (this: UserInstance, password: string, callback: UserMethods.IsPasswordMatchCallback) {
return comparePassword(password, this.password, callback)
isPasswordMatch = function (this: UserInstance, password: string) {
return comparePassword(password, this.password)
}
toFormatedJSON = function (this: UserInstance) {
@ -164,8 +158,8 @@ function associate (models) {
})
}
countTotal = function (callback: UserMethods.CountTotalCallback) {
return this.count().asCallback(callback)
countTotal = function () {
return this.count()
}
getByUsername = function (username: string) {
@ -178,44 +172,45 @@ getByUsername = function (username: string) {
return User.findOne(query)
}
list = function (callback: UserMethods.ListCallback) {
return User.find().asCallback(callback)
list = function () {
return User.findAll()
}
listForApi = function (start: number, count: number, sort: string, callback: UserMethods.ListForApiCallback) {
listForApi = function (start: number, count: number, sort: string) {
const query = {
offset: start,
limit: count,
order: [ getSort(sort) ]
}
return User.findAndCountAll(query).asCallback(function (err, result) {
if (err) return callback(err)
return callback(null, result.rows, result.count)
return User.findAndCountAll(query).then(({ rows, count }) => {
return {
data: rows,
total: count
}
})
}
loadById = function (id: number, callback: UserMethods.LoadByIdCallback) {
return User.findById(id).asCallback(callback)
loadById = function (id: number) {
return User.findById(id)
}
loadByUsername = function (username: string, callback: UserMethods.LoadByUsernameCallback) {
loadByUsername = function (username: string) {
const query = {
where: {
username: username
}
}
return User.findOne(query).asCallback(callback)
return User.findOne(query)
}
loadByUsernameOrEmail = function (username: string, email: string, callback: UserMethods.LoadByUsernameOrEmailCallback) {
loadByUsernameOrEmail = function (username: string, email: string) {
const query = {
where: {
$or: [ { username }, { email } ]
}
}
return User.findOne(query).asCallback(callback)
return User.findOne(query)
}

View file

@ -1,10 +1,15 @@
import * as Sequelize from 'sequelize'
import * as Promise from 'bluebird'
import { PodInstance } from '../pod'
export namespace AuthorMethods {
export type FindOrCreateAuthorCallback = (err: Error, authorInstance?: AuthorInstance) => void
export type FindOrCreateAuthor = (name: string, podId: number, userId: number, transaction: Sequelize.Transaction, callback: FindOrCreateAuthorCallback) => void
export type FindOrCreateAuthor = (
name: string,
podId: number,
userId: number,
transaction: Sequelize.Transaction
) => Promise<AuthorInstance>
}
export interface AuthorClass {

View file

@ -4,7 +4,6 @@ import { isUserUsernameValid } from '../../helpers'
import { addMethodsToModel } from '../utils'
import {
AuthorClass,
AuthorInstance,
AuthorAttributes,
@ -74,30 +73,18 @@ function associate (models) {
})
}
findOrCreateAuthor = function (
name: string,
podId: number,
userId: number,
transaction: Sequelize.Transaction,
callback: AuthorMethods.FindOrCreateAuthorCallback
) {
findOrCreateAuthor = function (name: string, podId: number, userId: number, transaction: Sequelize.Transaction) {
const author = {
name,
podId,
userId
}
const query: any = {
const query: Sequelize.FindOrInitializeOptions<AuthorAttributes> = {
where: author,
defaults: author
defaults: author,
transaction
}
if (transaction !== null) query.transaction = transaction
Author.findOrCreate(query).asCallback(function (err, result) {
if (err) return callback(err)
// [ instance, wasCreated ]
return callback(null, result[0])
})
return Author.findOrCreate(query).then(([ authorInstance ]) => authorInstance)
}

View file

@ -1,8 +1,8 @@
import * as Sequelize from 'sequelize'
import * as Promise from 'bluebird'
export namespace TagMethods {
export type FindOrCreateTagsCallback = (err: Error, tagInstances: TagInstance[]) => void
export type FindOrCreateTags = (tags: string[], transaction: Sequelize.Transaction, callback: FindOrCreateTagsCallback) => void
export type FindOrCreateTags = (tags: string[], transaction: Sequelize.Transaction) => Promise<TagInstance[]>
}
export interface TagClass {

View file

@ -1,9 +1,8 @@
import { each } from 'async'
import * as Sequelize from 'sequelize'
import * as Promise from 'bluebird'
import { addMethodsToModel } from '../utils'
import {
TagClass,
TagInstance,
TagAttributes,
@ -52,10 +51,9 @@ function associate (models) {
})
}
findOrCreateTags = function (tags: string[], transaction: Sequelize.Transaction, callback: TagMethods.FindOrCreateTagsCallback) {
const tagInstances = []
each<string, Error>(tags, function (tag, callbackEach) {
findOrCreateTags = function (tags: string[], transaction: Sequelize.Transaction) {
const tasks: Promise<TagInstance>[] = []
tags.forEach(tag => {
const query: any = {
where: {
name: tag
@ -67,15 +65,9 @@ findOrCreateTags = function (tags: string[], transaction: Sequelize.Transaction,
if (transaction) query.transaction = transaction
Tag.findOrCreate(query).asCallback(function (err, res) {
if (err) return callbackEach(err)
// res = [ tag, isCreated ]
const tag = res[0]
tagInstances.push(tag)
return callbackEach()
})
}, function (err) {
return callback(err, tagInstances)
const promise = Tag.findOrCreate(query).then(([ tagInstance ]) => tagInstance)
tasks.push(promise)
})
return Promise.all(tasks)
}

View file

@ -1,6 +1,8 @@
import * as Sequelize from 'sequelize'
import * as Promise from 'bluebird'
import { PodInstance } from '../pod'
import { ResultList } from '../../../shared'
// Don't use barrel, import just what we need
import { VideoAbuse as FormatedVideoAbuse } from '../../../shared/models/video-abuse.model'
@ -8,8 +10,7 @@ import { VideoAbuse as FormatedVideoAbuse } from '../../../shared/models/video-a
export namespace VideoAbuseMethods {
export type ToFormatedJSON = (this: VideoAbuseInstance) => FormatedVideoAbuse
export type ListForApiCallback = (err: Error, videoAbuseInstances?: VideoAbuseInstance[], total?: number) => void
export type ListForApi = (start: number, count: number, sort: string, callback: ListForApiCallback) => void
export type ListForApi = (start: number, count: number, sort: string) => Promise< ResultList<VideoAbuseInstance> >
}
export interface VideoAbuseClass {

View file

@ -5,7 +5,6 @@ import { isVideoAbuseReporterUsernameValid, isVideoAbuseReasonValid } from '../.
import { addMethodsToModel, getSort } from '../utils'
import {
VideoAbuseClass,
VideoAbuseInstance,
VideoAbuseAttributes,
@ -109,7 +108,7 @@ function associate (models) {
})
}
listForApi = function (start: number, count: number, sort: string, callback: VideoAbuseMethods.ListForApiCallback) {
listForApi = function (start: number, count: number, sort: string) {
const query = {
offset: start,
limit: count,
@ -122,11 +121,7 @@ listForApi = function (start: number, count: number, sort: string, callback: Vid
]
}
return VideoAbuse.findAndCountAll(query).asCallback(function (err, result) {
if (err) return callback(err)
return callback(null, result.rows, result.count)
return VideoAbuse.findAndCountAll(query).then(({ rows, count }) => {
return { total: count, data: rows }
})
}

View file

@ -1,4 +1,7 @@
import * as Sequelize from 'sequelize'
import * as Promise from 'bluebird'
import { ResultList } from '../../../shared'
// Don't use barrel, import just what we need
import { BlacklistedVideo as FormatedBlacklistedVideo } from '../../../shared/models/video-blacklist.model'
@ -6,20 +9,15 @@ import { BlacklistedVideo as FormatedBlacklistedVideo } from '../../../shared/mo
export namespace BlacklistedVideoMethods {
export type ToFormatedJSON = (this: BlacklistedVideoInstance) => FormatedBlacklistedVideo
export type CountTotalCallback = (err: Error, total: number) => void
export type CountTotal = (callback: CountTotalCallback) => void
export type CountTotal = () => Promise<number>
export type ListCallback = (err: Error, backlistedVideoInstances: BlacklistedVideoInstance[]) => void
export type List = (callback: ListCallback) => void
export type List = () => Promise<BlacklistedVideoInstance[]>
export type ListForApiCallback = (err: Error, blacklistedVIdeoInstances?: BlacklistedVideoInstance[], total?: number) => void
export type ListForApi = (start: number, count: number, sort: string, callback: ListForApiCallback) => void
export type ListForApi = (start: number, count: number, sort: string) => Promise< ResultList<BlacklistedVideoInstance> >
export type LoadByIdCallback = (err: Error, blacklistedVideoInstance: BlacklistedVideoInstance) => void
export type LoadById = (id: number, callback: LoadByIdCallback) => void
export type LoadById = (id: number) => Promise<BlacklistedVideoInstance>
export type LoadByVideoIdCallback = (err: Error, blacklistedVideoInstance: BlacklistedVideoInstance) => void
export type LoadByVideoId = (id: string, callback: LoadByVideoIdCallback) => void
export type LoadByVideoId = (id: string) => Promise<BlacklistedVideoInstance>
}
export interface BlacklistedVideoClass {
@ -35,7 +33,8 @@ export interface BlacklistedVideoAttributes {
videoId: string
}
export interface BlacklistedVideoInstance extends BlacklistedVideoClass, BlacklistedVideoAttributes, Sequelize.Instance<BlacklistedVideoAttributes> {
export interface BlacklistedVideoInstance
extends BlacklistedVideoClass, BlacklistedVideoAttributes, Sequelize.Instance<BlacklistedVideoAttributes> {
id: number
createdAt: Date
updatedAt: Date
@ -43,4 +42,5 @@ export interface BlacklistedVideoInstance extends BlacklistedVideoClass, Blackli
toFormatedJSON: BlacklistedVideoMethods.ToFormatedJSON
}
export interface BlacklistedVideoModel extends BlacklistedVideoClass, Sequelize.Model<BlacklistedVideoInstance, BlacklistedVideoAttributes> {}
export interface BlacklistedVideoModel
extends BlacklistedVideoClass, Sequelize.Model<BlacklistedVideoInstance, BlacklistedVideoAttributes> {}

View file

@ -2,7 +2,6 @@ import * as Sequelize from 'sequelize'
import { addMethodsToModel, getSort } from '../utils'
import {
BlacklistedVideoClass,
BlacklistedVideoInstance,
BlacklistedVideoAttributes,
@ -66,38 +65,39 @@ function associate (models) {
})
}
countTotal = function (callback: BlacklistedVideoMethods.CountTotalCallback) {
return BlacklistedVideo.count().asCallback(callback)
countTotal = function () {
return BlacklistedVideo.count()
}
list = function (callback: BlacklistedVideoMethods.ListCallback) {
return BlacklistedVideo.findAll().asCallback(callback)
list = function () {
return BlacklistedVideo.findAll()
}
listForApi = function (start: number, count: number, sort: string, callback: BlacklistedVideoMethods.ListForApiCallback) {
listForApi = function (start: number, count: number, sort: string) {
const query = {
offset: start,
limit: count,
order: [ getSort(sort) ]
}
return BlacklistedVideo.findAndCountAll(query).asCallback(function (err, result) {
if (err) return callback(err)
return callback(null, result.rows, result.count)
return BlacklistedVideo.findAndCountAll(query).then(({ rows, count }) => {
return {
data: rows,
total: count
}
})
}
loadById = function (id: number, callback: BlacklistedVideoMethods.LoadByIdCallback) {
return BlacklistedVideo.findById(id).asCallback(callback)
loadById = function (id: number) {
return BlacklistedVideo.findById(id)
}
loadByVideoId = function (id: string, callback: BlacklistedVideoMethods.LoadByIdCallback) {
loadByVideoId = function (id: string) {
const query = {
where: {
videoId: id
}
}
return BlacklistedVideo.find(query).asCallback(callback)
return BlacklistedVideo.findOne(query)
}

View file

@ -1,10 +1,12 @@
import * as Sequelize from 'sequelize'
import * as Promise from 'bluebird'
import { AuthorInstance } from './author-interface'
import { VideoTagInstance } from './video-tag-interface'
import { TagAttributes, TagInstance } from './tag-interface'
// Don't use barrel, import just what we need
import { Video as FormatedVideo } from '../../../shared/models/video.model'
import { ResultList } from '../../../shared/models/result-list.model'
export type FormatedAddRemoteVideo = {
name: string
@ -56,46 +58,32 @@ export namespace VideoMethods {
export type IsOwned = (this: VideoInstance) => boolean
export type ToFormatedJSON = (this: VideoInstance) => FormatedVideo
export type ToAddRemoteJSONCallback = (err: Error, videoFormated?: FormatedAddRemoteVideo) => void
export type ToAddRemoteJSON = (this: VideoInstance, callback: ToAddRemoteJSONCallback) => void
export type ToAddRemoteJSON = (this: VideoInstance) => Promise<FormatedAddRemoteVideo>
export type ToUpdateRemoteJSON = (this: VideoInstance) => FormatedUpdateRemoteVideo
export type TranscodeVideofileCallback = (err: Error) => void
export type TranscodeVideofile = (this: VideoInstance, callback: TranscodeVideofileCallback) => void
export type TranscodeVideofile = (this: VideoInstance) => Promise<void>
export type GenerateThumbnailFromDataCallback = (err: Error, thumbnailName?: string) => void
export type GenerateThumbnailFromData = (video: VideoInstance, thumbnailData: string, callback: GenerateThumbnailFromDataCallback) => void
// Return thumbnail name
export type GenerateThumbnailFromData = (video: VideoInstance, thumbnailData: string) => Promise<string>
export type GetDurationFromFile = (videoPath: string) => Promise<number>
export type GetDurationFromFileCallback = (err: Error, duration?: number) => void
export type GetDurationFromFile = (videoPath, callback) => void
export type List = () => Promise<VideoInstance[]>
export type ListOwnedAndPopulateAuthorAndTags = () => Promise<VideoInstance[]>
export type ListOwnedByAuthor = (author: string) => Promise<VideoInstance[]>
export type ListCallback = (err: Error, videoInstances: VideoInstance[]) => void
export type List = (callback: ListCallback) => void
export type ListForApi = (start: number, count: number, sort: string) => Promise< ResultList<VideoInstance> >
export type SearchAndPopulateAuthorAndPodAndTags = (
value: string,
field: string,
start: number,
count: number,
sort: string
) => Promise< ResultList<VideoInstance> >
export type ListForApiCallback = (err: Error, videoInstances?: VideoInstance[], total?: number) => void
export type ListForApi = (start: number, count: number, sort: string, callback: ListForApiCallback) => void
export type LoadByHostAndRemoteIdCallback = (err: Error, videoInstance: VideoInstance) => void
export type LoadByHostAndRemoteId = (fromHost: string, remoteId: string, callback: LoadByHostAndRemoteIdCallback) => void
export type ListOwnedAndPopulateAuthorAndTagsCallback = (err: Error, videoInstances: VideoInstance[]) => void
export type ListOwnedAndPopulateAuthorAndTags = (callback: ListOwnedAndPopulateAuthorAndTagsCallback) => void
export type ListOwnedByAuthorCallback = (err: Error, videoInstances: VideoInstance[]) => void
export type ListOwnedByAuthor = (author: string, callback: ListOwnedByAuthorCallback) => void
export type LoadCallback = (err: Error, videoInstance: VideoInstance) => void
export type Load = (id: string, callback: LoadCallback) => void
export type LoadAndPopulateAuthorCallback = (err: Error, videoInstance: VideoInstance) => void
export type LoadAndPopulateAuthor = (id: string, callback: LoadAndPopulateAuthorCallback) => void
export type LoadAndPopulateAuthorAndPodAndTagsCallback = (err: Error, videoInstance: VideoInstance) => void
export type LoadAndPopulateAuthorAndPodAndTags = (id: string, callback: LoadAndPopulateAuthorAndPodAndTagsCallback) => void
export type SearchAndPopulateAuthorAndPodAndTagsCallback = (err: Error, videoInstances?: VideoInstance[], total?: number) => void
export type SearchAndPopulateAuthorAndPodAndTags = (value: string, field: string, start: number, count: number, sort: string, callback: SearchAndPopulateAuthorAndPodAndTagsCallback) => void
export type Load = (id: string) => Promise<VideoInstance>
export type LoadByHostAndRemoteId = (fromHost: string, remoteId: string) => Promise<VideoInstance>
export type LoadAndPopulateAuthor = (id: string) => Promise<VideoInstance>
export type LoadAndPopulateAuthorAndPodAndTags = (id: string) => Promise<VideoInstance>
}
export interface VideoClass {
@ -139,7 +127,7 @@ export interface VideoAttributes {
dislikes?: number
Author?: AuthorInstance
Tags?: VideoTagInstance[]
Tags?: TagInstance[]
}
export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.Instance<VideoAttributes> {
@ -157,6 +145,8 @@ export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.In
toAddRemoteJSON: VideoMethods.ToAddRemoteJSON
toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON
transcodeVideofile: VideoMethods.TranscodeVideofile
setTags: Sequelize.HasManySetAssociationsMixin<TagAttributes, string>
}
export interface VideoModel extends VideoClass, Sequelize.Model<VideoInstance, VideoAttributes> {}

View file

@ -1,12 +1,8 @@
import * as Sequelize from 'sequelize'
import { addMethodsToModel } from '../utils'
import {
VideoTagClass,
VideoTagInstance,
VideoTagAttributes,
VideoTagMethods
VideoTagAttributes
} from './video-tag-interface'
let VideoTag: Sequelize.Model<VideoTagInstance, VideoTagAttributes>

View file

@ -1,17 +1,15 @@
import * as safeBuffer from 'safe-buffer'
const Buffer = safeBuffer.Buffer
import * as createTorrent from 'create-torrent'
import * as ffmpeg from 'fluent-ffmpeg'
import * as fs from 'fs'
import * as magnetUtil from 'magnet-uri'
import { map, values } from 'lodash'
import { parallel, series } from 'async'
import * as parseTorrent from 'parse-torrent'
import { join } from 'path'
import * as Sequelize from 'sequelize'
import * as Promise from 'bluebird'
import { database as db } from '../../initializers/database'
import { VideoTagInstance } from './video-tag-interface'
import { TagInstance } from './tag-interface'
import {
logger,
isVideoNameValid,
@ -21,7 +19,12 @@ import {
isVideoNSFWValid,
isVideoDescriptionValid,
isVideoInfoHashValid,
isVideoDurationValid
isVideoDurationValid,
readFileBufferPromise,
unlinkPromise,
renamePromise,
writeFilePromise,
createTorrentPromise
} from '../../helpers'
import {
CONSTRAINTS_FIELDS,
@ -37,7 +40,6 @@ import { JobScheduler, removeVideoToFriends } from '../../lib'
import { addMethodsToModel, getSort } from '../utils'
import {
VideoClass,
VideoInstance,
VideoAttributes,
@ -260,7 +262,7 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
toFormatedJSON,
toAddRemoteJSON,
toUpdateRemoteJSON,
transcodeVideofile,
transcodeVideofile
]
addMethodsToModel(Video, classMethods, instanceMethods)
@ -276,91 +278,53 @@ function beforeValidate (video: VideoInstance) {
}
function beforeCreate (video: VideoInstance, options: { transaction: Sequelize.Transaction }) {
return new Promise(function (resolve, reject) {
const tasks = []
if (video.isOwned()) {
const videoPath = join(CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename())
tasks.push(
function createVideoTorrent (callback) {
createTorrentFromVideo(video, videoPath, callback)
},
function createVideoThumbnail (callback) {
createThumbnail(video, videoPath, callback)
},
function createVideoPreview (callback) {
createPreview(video, videoPath, callback)
}
)
if (CONFIG.TRANSCODING.ENABLED === true) {
tasks.push(
function createVideoTranscoderJob (callback) {
const dataInput = {
id: video.id
}
JobScheduler.Instance.createJob(options.transaction, 'videoTranscoder', dataInput, callback)
}
)
}
return parallel(tasks, function (err) {
if (err) return reject(err)
return resolve()
})
}
return resolve()
})
}
function afterDestroy (video: VideoInstance) {
return new Promise(function (resolve, reject) {
if (video.isOwned()) {
const videoPath = join(CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename())
const tasks = []
tasks.push(
function (callback) {
removeThumbnail(video, callback)
}
createTorrentFromVideo(video, videoPath),
createThumbnail(video, videoPath),
createPreview(video, videoPath)
)
if (video.isOwned()) {
if (CONFIG.TRANSCODING.ENABLED === true) {
const dataInput = {
id: video.id
}
tasks.push(
function removeVideoFile (callback) {
removeFile(video, callback)
},
function removeVideoTorrent (callback) {
removeTorrent(video, callback)
},
function removeVideoPreview (callback) {
removePreview(video, callback)
},
function notifyFriends (callback) {
const params = {
remoteId: video.id
}
removeVideoToFriends(params)
return callback()
}
JobScheduler.Instance.createJob(options.transaction, 'videoTranscoder', dataInput)
)
}
parallel(tasks, function (err) {
if (err) return reject(err)
return Promise.all(tasks)
}
return resolve()
})
})
return Promise.resolve()
}
function afterDestroy (video: VideoInstance) {
const tasks = []
tasks.push(
removeThumbnail(video)
)
if (video.isOwned()) {
const removeVideoToFriendsParams = {
remoteId: video.id
}
tasks.push(
removeFile(video),
removeTorrent(video),
removePreview(video),
removeVideoToFriends(removeVideoToFriendsParams)
)
}
return Promise.all(tasks)
}
// ------------------------------ METHODS ------------------------------
@ -488,7 +452,7 @@ toFormatedJSON = function (this: VideoInstance) {
views: this.views,
likes: this.likes,
dislikes: this.dislikes,
tags: map<VideoTagInstance, string>(this.Tags, 'name'),
tags: map<TagInstance, string>(this.Tags, 'name'),
thumbnailPath: join(STATIC_PATHS.THUMBNAILS, this.getThumbnailName()),
createdAt: this.createdAt,
updatedAt: this.updatedAt
@ -497,15 +461,11 @@ toFormatedJSON = function (this: VideoInstance) {
return json
}
toAddRemoteJSON = function (this: VideoInstance, callback: VideoMethods.ToAddRemoteJSONCallback) {
toAddRemoteJSON = function (this: VideoInstance) {
// Get thumbnail data to send to the other pod
const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName())
fs.readFile(thumbnailPath, (err, thumbnailData) => {
if (err) {
logger.error('Cannot read the thumbnail of the video')
return callback(err)
}
return readFileBufferPromise(thumbnailPath).then(thumbnailData => {
const remoteVideo = {
name: this.name,
category: this.category,
@ -518,7 +478,7 @@ toAddRemoteJSON = function (this: VideoInstance, callback: VideoMethods.ToAddRem
author: this.Author.name,
duration: this.duration,
thumbnailData: thumbnailData.toString('binary'),
tags: map<VideoTagInstance, string>(this.Tags, 'name'),
tags: map<TagInstance, string>(this.Tags, 'name'),
createdAt: this.createdAt,
updatedAt: this.updatedAt,
extname: this.extname,
@ -527,7 +487,7 @@ toAddRemoteJSON = function (this: VideoInstance, callback: VideoMethods.ToAddRem
dislikes: this.dislikes
}
return callback(null, remoteVideo)
return remoteVideo
})
}
@ -543,7 +503,7 @@ toUpdateRemoteJSON = function (this: VideoInstance) {
remoteId: this.id,
author: this.Author.name,
duration: this.duration,
tags: map<VideoTagInstance, string>(this.Tags, 'name'),
tags: map<TagInstance, string>(this.Tags, 'name'),
createdAt: this.createdAt,
updatedAt: this.updatedAt,
extname: this.extname,
@ -555,7 +515,7 @@ toUpdateRemoteJSON = function (this: VideoInstance) {
return json
}
transcodeVideofile = function (this: VideoInstance, finalCallback: VideoMethods.TranscodeVideofileCallback) {
transcodeVideofile = function (this: VideoInstance) {
const video = this
const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
@ -563,78 +523,73 @@ transcodeVideofile = function (this: VideoInstance, finalCallback: VideoMethods.
const videoInputPath = join(videosDirectory, video.getVideoFilename())
const videoOutputPath = join(videosDirectory, video.id + '-transcoded' + newExtname)
ffmpeg(videoInputPath)
.output(videoOutputPath)
.videoCodec('libx264')
.outputOption('-threads ' + CONFIG.TRANSCODING.THREADS)
.outputOption('-movflags faststart')
.on('error', finalCallback)
.on('end', function () {
series([
function removeOldFile (callback) {
fs.unlink(videoInputPath, callback)
},
return new Promise<void>((res, rej) => {
ffmpeg(videoInputPath)
.output(videoOutputPath)
.videoCodec('libx264')
.outputOption('-threads ' + CONFIG.TRANSCODING.THREADS)
.outputOption('-movflags faststart')
.on('error', rej)
.on('end', () => {
function moveNewFile (callback) {
// Important to do this before getVideoFilename() to take in account the new file extension
video.set('extname', newExtname)
return unlinkPromise(videoInputPath)
.then(() => {
// Important to do this before getVideoFilename() to take in account the new file extension
video.set('extname', newExtname)
const newVideoPath = join(videosDirectory, video.getVideoFilename())
fs.rename(videoOutputPath, newVideoPath, callback)
},
function torrent (callback) {
const newVideoPath = join(videosDirectory, video.getVideoFilename())
createTorrentFromVideo(video, newVideoPath, callback)
},
function videoExtension (callback) {
video.save().asCallback(callback)
}
], function (err: Error) {
if (err) {
// Autodesctruction...
video.destroy().asCallback(function (err) {
if (err) logger.error('Cannot destruct video after transcoding failure.', { error: err })
const newVideoPath = join(videosDirectory, video.getVideoFilename())
return renamePromise(videoOutputPath, newVideoPath)
})
.then(() => {
const newVideoPath = join(videosDirectory, video.getVideoFilename())
return createTorrentFromVideo(video, newVideoPath)
})
.then(() => {
return video.save()
})
.then(() => {
return res()
})
.catch(err => {
// Autodesctruction...
video.destroy().asCallback(function (err) {
if (err) logger.error('Cannot destruct video after transcoding failure.', { error: err })
})
return finalCallback(err)
}
return finalCallback(null)
return rej(err)
})
})
})
.run()
.run()
})
}
// ------------------------------ STATICS ------------------------------
generateThumbnailFromData = function (video: VideoInstance, thumbnailData: string, callback: VideoMethods.GenerateThumbnailFromDataCallback) {
generateThumbnailFromData = function (video: VideoInstance, thumbnailData: string) {
// Creating the thumbnail for a remote video
const thumbnailName = video.getThumbnailName()
const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, thumbnailName)
fs.writeFile(thumbnailPath, Buffer.from(thumbnailData, 'binary'), function (err) {
if (err) return callback(err)
return callback(null, thumbnailName)
return writeFilePromise(thumbnailPath, Buffer.from(thumbnailData, 'binary')).then(() => {
return thumbnailName
})
}
getDurationFromFile = function (videoPath: string, callback: VideoMethods.GetDurationFromFileCallback) {
ffmpeg.ffprobe(videoPath, function (err, metadata) {
if (err) return callback(err)
getDurationFromFile = function (videoPath: string) {
return new Promise<number>((res, rej) => {
ffmpeg.ffprobe(videoPath, function (err, metadata) {
if (err) return rej(err)
return callback(null, Math.floor(metadata.format.duration))
return res(Math.floor(metadata.format.duration))
})
})
}
list = function (callback: VideoMethods.ListCallback) {
return Video.findAll().asCallback(callback)
list = function () {
return Video.findAll()
}
listForApi = function (start: number, count: number, sort: string, callback: VideoMethods.ListForApiCallback) {
listForApi = function (start: number, count: number, sort: string) {
// Exclude Blakclisted videos from the list
const query = {
distinct: true,
@ -652,14 +607,15 @@ listForApi = function (start: number, count: number, sort: string, callback: Vid
where: createBaseVideosWhere()
}
return Video.findAndCountAll(query).asCallback(function (err, result) {
if (err) return callback(err)
return callback(null, result.rows, result.count)
return Video.findAndCountAll(query).then(({ rows, count }) => {
return {
data: rows,
total: count
}
})
}
loadByHostAndRemoteId = function (fromHost: string, remoteId: string, callback: VideoMethods.LoadByHostAndRemoteIdCallback) {
loadByHostAndRemoteId = function (fromHost: string, remoteId: string) {
const query = {
where: {
remoteId: remoteId
@ -680,10 +636,10 @@ loadByHostAndRemoteId = function (fromHost: string, remoteId: string, callback:
]
}
return Video.findOne(query).asCallback(callback)
return Video.findOne(query)
}
listOwnedAndPopulateAuthorAndTags = function (callback: VideoMethods.ListOwnedAndPopulateAuthorAndTagsCallback) {
listOwnedAndPopulateAuthorAndTags = function () {
// If remoteId is null this is *our* video
const query = {
where: {
@ -692,10 +648,10 @@ listOwnedAndPopulateAuthorAndTags = function (callback: VideoMethods.ListOwnedAn
include: [ Video['sequelize'].models.Author, Video['sequelize'].models.Tag ]
}
return Video.findAll(query).asCallback(callback)
return Video.findAll(query)
}
listOwnedByAuthor = function (author: string, callback: VideoMethods.ListOwnedByAuthorCallback) {
listOwnedByAuthor = function (author: string) {
const query = {
where: {
remoteId: null
@ -710,22 +666,22 @@ listOwnedByAuthor = function (author: string, callback: VideoMethods.ListOwnedBy
]
}
return Video.findAll(query).asCallback(callback)
return Video.findAll(query)
}
load = function (id: string, callback: VideoMethods.LoadCallback) {
return Video.findById(id).asCallback(callback)
load = function (id: string) {
return Video.findById(id)
}
loadAndPopulateAuthor = function (id: string, callback: VideoMethods.LoadAndPopulateAuthorCallback) {
loadAndPopulateAuthor = function (id: string) {
const options = {
include: [ Video['sequelize'].models.Author ]
}
return Video.findById(id, options).asCallback(callback)
return Video.findById(id, options)
}
loadAndPopulateAuthorAndPodAndTags = function (id: string, callback: VideoMethods.LoadAndPopulateAuthorAndPodAndTagsCallback) {
loadAndPopulateAuthorAndPodAndTags = function (id: string) {
const options = {
include: [
{
@ -736,17 +692,10 @@ loadAndPopulateAuthorAndPodAndTags = function (id: string, callback: VideoMethod
]
}
return Video.findById(id, options).asCallback(callback)
return Video.findById(id, options)
}
searchAndPopulateAuthorAndPodAndTags = function (
value: string,
field: string,
start: number,
count: number,
sort: string,
callback: VideoMethods.SearchAndPopulateAuthorAndPodAndTagsCallback
) {
searchAndPopulateAuthorAndPodAndTags = function (value: string, field: string, start: number, count: number, sort: string) {
const podInclude: any = {
model: Video['sequelize'].models.Pod,
required: false
@ -778,7 +727,11 @@ searchAndPopulateAuthorAndPodAndTags = function (
} else if (field === 'tags') {
const escapedValue = Video['sequelize'].escape('%' + value + '%')
query.where.id.$in = Video['sequelize'].literal(
'(SELECT "VideoTags"."videoId" FROM "Tags" INNER JOIN "VideoTags" ON "Tags"."id" = "VideoTags"."tagId" WHERE name LIKE ' + escapedValue + ')'
`(SELECT "VideoTags"."videoId"
FROM "Tags"
INNER JOIN "VideoTags" ON "Tags"."id" = "VideoTags"."tagId"
WHERE name LIKE ${escapedValue}
)`
)
} else if (field === 'host') {
// FIXME: Include our pod? (not stored in the database)
@ -810,10 +763,11 @@ searchAndPopulateAuthorAndPodAndTags = function (
// query.include.push([ Video['sequelize'].models.Tag ])
}
return Video.findAndCountAll(query).asCallback(function (err, result) {
if (err) return callback(err)
return callback(null, result.rows, result.count)
return Video.findAndCountAll(query).then(({ rows, count }) => {
return {
data: rows,
total: count
}
})
}
@ -829,27 +783,27 @@ function createBaseVideosWhere () {
}
}
function removeThumbnail (video: VideoInstance, callback: (err: Error) => void) {
function removeThumbnail (video: VideoInstance) {
const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName())
fs.unlink(thumbnailPath, callback)
return unlinkPromise(thumbnailPath)
}
function removeFile (video: VideoInstance, callback: (err: Error) => void) {
function removeFile (video: VideoInstance) {
const filePath = join(CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename())
fs.unlink(filePath, callback)
return unlinkPromise(filePath)
}
function removeTorrent (video: VideoInstance, callback: (err: Error) => void) {
function removeTorrent (video: VideoInstance) {
const torrenPath = join(CONFIG.STORAGE.TORRENTS_DIR, video.getTorrentName())
fs.unlink(torrenPath, callback)
return unlinkPromise(torrenPath)
}
function removePreview (video: VideoInstance, callback: (err: Error) => void) {
function removePreview (video: VideoInstance) {
// Same name than video thumnail
fs.unlink(CONFIG.STORAGE.PREVIEWS_DIR + video.getPreviewName(), callback)
return unlinkPromise(CONFIG.STORAGE.PREVIEWS_DIR + video.getPreviewName())
}
function createTorrentFromVideo (video: VideoInstance, videoPath: string, callback: (err: Error) => void) {
function createTorrentFromVideo (video: VideoInstance, videoPath: string) {
const options = {
announceList: [
[ CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT + '/tracker/socket' ]
@ -859,30 +813,27 @@ function createTorrentFromVideo (video: VideoInstance, videoPath: string, callba
]
}
createTorrent(videoPath, options, function (err, torrent) {
if (err) return callback(err)
const filePath = join(CONFIG.STORAGE.TORRENTS_DIR, video.getTorrentName())
fs.writeFile(filePath, torrent, function (err) {
if (err) return callback(err)
return createTorrentPromise(videoPath, options)
.then(torrent => {
const filePath = join(CONFIG.STORAGE.TORRENTS_DIR, video.getTorrentName())
return writeFilePromise(filePath, torrent).then(() => torrent)
})
.then(torrent => {
const parsedTorrent = parseTorrent(torrent)
video.set('infoHash', parsedTorrent.infoHash)
video.validate().asCallback(callback)
return video.validate()
})
})
}
function createPreview (video: VideoInstance, videoPath: string, callback: (err: Error) => void) {
generateImage(video, videoPath, CONFIG.STORAGE.PREVIEWS_DIR, video.getPreviewName(), null, callback)
function createPreview (video: VideoInstance, videoPath: string) {
return generateImage(video, videoPath, CONFIG.STORAGE.PREVIEWS_DIR, video.getPreviewName(), null)
}
function createThumbnail (video: VideoInstance, videoPath: string, callback: (err: Error) => void) {
generateImage(video, videoPath, CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName(), THUMBNAILS_SIZE, callback)
function createThumbnail (video: VideoInstance, videoPath: string) {
return generateImage(video, videoPath, CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName(), THUMBNAILS_SIZE)
}
type GenerateImageCallback = (err: Error, imageName: string) => void
function generateImage (video: VideoInstance, videoPath: string, folder: string, imageName: string, size: string, callback?: GenerateImageCallback) {
function generateImage (video: VideoInstance, videoPath: string, folder: string, imageName: string, size: string) {
const options: any = {
filename: imageName,
count: 1,
@ -893,29 +844,25 @@ function generateImage (video: VideoInstance, videoPath: string, folder: string,
options.size = size
}
ffmpeg(videoPath)
.on('error', callback)
.on('end', function () {
callback(null, imageName)
})
.thumbnail(options)
return new Promise<string>((res, rej) => {
ffmpeg(videoPath)
.on('error', rej)
.on('end', function () {
return res(imageName)
})
.thumbnail(options)
})
}
function removeFromBlacklist (video: VideoInstance, callback: (err: Error) => void) {
function removeFromBlacklist (video: VideoInstance) {
// Find the blacklisted video
db.BlacklistedVideo.loadByVideoId(video.id, function (err, video) {
// If an error occured, stop here
if (err) {
logger.error('Error when fetching video from blacklist.', { error: err })
return callback(err)
return db.BlacklistedVideo.loadByVideoId(video.id).then(video => {
// Not found the video, skip
if (!video) {
return null
}
// If we found the video, remove it from the blacklist
if (video) {
video.destroy().asCallback(callback)
} else {
// If haven't found it, simply ignore it and do nothing
return callback(null)
}
return video.destroy()
})
}

View file

@ -171,6 +171,23 @@ describe('Test advanced friends', function () {
function (next) {
setTimeout(next, 22000)
},
// Check the pods 1, 2 expulsed pod 4
function (next) {
each([ 1, 2 ], function (i, callback) {
getFriendsList(i, function (err, res) {
if (err) throw err
// Pod 4 should not be our friend
const result = res.body.data
expect(result.length).to.equal(2)
for (const pod of result) {
expect(pod.host).not.equal(servers[3].host)
}
callback()
})
}, next)
},
// Rerun server 4
function (next) {
serversUtils.runServer(4, function (server) {
@ -187,7 +204,7 @@ describe('Test advanced friends', function () {
next()
})
},
// Pod 6 ask pod 1, 2 and 3
// Pod 6 asks pod 1, 2 and 3
function (next) {
makeFriends(6, next)
},

View file

@ -1,6 +1,7 @@
export * from './job.model'
export * from './oauth-client-local.model'
export * from './pod.model'
export * from './result-list.model'
export * from './request-scheduler.model'
export * from './user-video-rate.model'
export * from './user.model'

View file

@ -0,0 +1,4 @@
export interface ResultList<T> {
total: number
data: T[]
}

View file

@ -84,9 +84,9 @@
dependencies:
"@types/express" "*"
"@types/node@*", "@types/node@^7.0.18":
version "7.0.32"
resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.32.tgz#6afe6c66520a4c316623a14aef123908d01b4bba"
"@types/node@*", "@types/node@^8.0.3":
version "8.0.3"
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.3.tgz#fca61c26f83e5f453166114f57d53a47feb36d45"
"@types/request@^0.0.44":
version "0.0.44"
@ -453,7 +453,7 @@ bluebird@^2.10.0, bluebird@^2.9.13:
version "2.11.0"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1"
bluebird@^3.0.5, bluebird@^3.4.0, bluebird@^3.4.6:
bluebird@^3.0.5, bluebird@^3.4.0, bluebird@^3.4.6, bluebird@^3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c"
@ -3909,9 +3909,9 @@ typedarray@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
typescript@^2.3.4:
version "2.4.0"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.4.0.tgz#aef5a8d404beba36ad339abf079ddddfffba86dd"
typescript@^2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.4.1.tgz#c3ccb16ddaa0b2314de031e7e6fee89e5ba346bc"
uid-number@^0.0.6, uid-number@~0.0.6:
version "0.0.6"