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

View File

@ -1,25 +1,28 @@
import * as eachSeries from 'async/eachSeries'
import * as rimraf from 'rimraf' import * as rimraf from 'rimraf'
import * as Promise from 'bluebird'
import { CONFIG } from '../../../server/initializers/constants' import { CONFIG } from '../../../server/initializers/constants'
import { database as db } from '../../../server/initializers/database' import { database as db } from '../../../server/initializers/database'
db.init(true, function () { db.init(true)
db.sequelize.drop().asCallback(function (err) { .then(() => {
if (err) throw err return db.sequelize.drop()
})
.then(() => {
console.info('Tables of %s deleted.', CONFIG.DATABASE.DBNAME) console.info('Tables of %s deleted.', CONFIG.DATABASE.DBNAME)
const STORAGE = CONFIG.STORAGE const STORAGE = CONFIG.STORAGE
eachSeries(Object.keys(STORAGE), function (storage, callbackEach) { Promise.mapSeries(Object.keys(STORAGE), storage => {
const storageDir = STORAGE[storage] const storageDir = STORAGE[storage]
rimraf(storageDir, function (err) { return new Promise((res, rej) => {
console.info('%s deleted.', storageDir) rimraf(storageDir, function (err) {
return callbackEach(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) process.exit(-1)
} }
db.init(true, function () { db.init(true)
db.User.loadByUsername(program.user, function (err, user) { .then(() => {
if (err) { return db.User.loadByUsername(program.user)
console.error(err) })
return .then(user => {
}
if (!user) { if (!user) {
console.error('User unknown.') console.error('User unknown.')
return return
@ -40,15 +38,9 @@ db.init(true, function () {
rl.on('line', function (password) { rl.on('line', function (password) {
user.password = password user.password = password
user.save().asCallback(function (err) { user.save()
if (err) { .then(() => console.log('User password updated.'))
console.error(err) .catch(err => console.error(err))
} else { .finally(() => process.exit(0))
console.log('User password updated.')
}
process.exit(0)
})
}) })
}) })
})

View File

@ -1,4 +1,4 @@
#!/usr/bin/env sh #!/bin/bash
npm run build:server npm run build:server
@ -6,5 +6,5 @@ cd client || exit -1
npm test || exit -1 npm test || exit -1
cd .. || 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 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 { database as db } from '../server/initializers/database'
import { hasFriends } from '../server/lib/friends' import { hasFriends } from '../server/lib/friends'
db.init(true, function () { db.init(true)
hasFriends(function (err, itHasFriends) { .then(() => {
if (err) throw err return hasFriends()
})
.then(itHasFriends => {
if (itHasFriends === true) { if (itHasFriends === true) {
console.log('Cannot update host because you have friends!') console.log('Cannot update host because you have friends!')
process.exit(-1) process.exit(-1)
} }
console.log('Updating torrent files.') console.log('Updating torrent files.')
db.Video.list(function (err, videos) { return db.Video.list()
if (err) throw err })
.then(videos => {
videos.forEach(function (video) { videos.forEach(function (video) {
const torrentName = video.id + '.torrent' const torrentName = video.id + '.torrent'
const torrentPath = CONFIG.STORAGE.TORRENTS_DIR + torrentName const torrentPath = CONFIG.STORAGE.TORRENTS_DIR + torrentName
const filename = video.id + video.extname const filename = video.id + video.extname
const parsed = parseTorrent(readFileSync(torrentPath)) const parsed = parseTorrent(readFileSync(torrentPath))
parsed.announce = [ CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOST + '/tracker/socket' ] parsed.announce = [ CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOST + '/tracker/socket' ]
parsed.urlList = [ CONFIG.WEBSERVER.URL + STATIC_PATHS.WEBSEED + filename ] parsed.urlList = [ CONFIG.WEBSERVER.URL + STATIC_PATHS.WEBSEED + filename ]
const buf = parseTorrent.toTorrentFile(parsed) const buf = parseTorrent.toTorrentFile(parsed)
writeFileSync(torrentPath, buf) writeFileSync(torrentPath, buf)
}) })
process.exit(0) process.exit(0)
})
}) })
})

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,16 +1,11 @@
import * as express from 'express' import * as express from 'express'
import * as Sequelize from 'sequelize'
import { waterfall } from 'async'
import { database as db } from '../../../initializers/database' import { database as db } from '../../../initializers/database'
import * as friends from '../../../lib/friends' import * as friends from '../../../lib/friends'
import { import {
logger, logger,
getFormatedObjects, getFormatedObjects,
retryTransactionWrapper, retryTransactionWrapper
startSerializableTransaction,
commitTransaction,
rollbackTransaction
} from '../../../helpers' } from '../../../helpers'
import { import {
authenticate, authenticate,
@ -21,6 +16,7 @@ import {
setVideoAbusesSort, setVideoAbusesSort,
setPagination setPagination
} from '../../../middlewares' } from '../../../middlewares'
import { VideoInstance } from '../../../models'
const abuseVideoRouter = express.Router() const abuseVideoRouter = express.Router()
@ -48,11 +44,9 @@ export {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
function listVideoAbuses (req: express.Request, res: express.Response, next: express.NextFunction) { 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) { db.VideoAbuse.listForApi(req.query.start, req.query.count, req.query.sort)
if (err) return next(err) .then(result => res.json(getFormatedObjects(result.data, result.total)))
.catch(err => next(err))
res.json(getFormatedObjects(abusesList, abusesTotal))
})
} }
function reportVideoAbuseRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { 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.' errorMessage: 'Cannot report abuse to the video with many retries.'
} }
retryTransactionWrapper(reportVideoAbuse, options, function (err) { retryTransactionWrapper(reportVideoAbuse, options)
if (err) return next(err) .then(() => res.type('json').status(204).end())
.catch(err => next(err))
return res.type('json').status(204).end()
})
} }
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 videoInstance = res.locals.video
const reporterUsername = res.locals.oauth.token.User.username 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 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, return friends.reportAbuseVideoToFriend(reportData, videoInstance, t).then(() => videoInstance)
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
} }
friends.reportAbuseVideoToFriend(reportData, videoInstance) return videoInstance
} })
})
return callback(null, t) .then((videoInstance: VideoInstance) => logger.info('Abuse report for video %s created.', videoInstance.name))
}, .catch(err => {
logger.debug('Cannot update the video.', { error: err })
commitTransaction throw err
], 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)
}) })
} }

View File

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

View File

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

View File

@ -1,14 +1,9 @@
import * as express from 'express' import * as express from 'express'
import * as Sequelize from 'sequelize'
import { waterfall } from 'async'
import { database as db } from '../../../initializers/database' import { database as db } from '../../../initializers/database'
import { import {
logger, logger,
retryTransactionWrapper, retryTransactionWrapper
startSerializableTransaction,
commitTransaction,
rollbackTransaction
} from '../../../helpers' } from '../../../helpers'
import { import {
VIDEO_RATE_TYPES, VIDEO_RATE_TYPES,
@ -46,137 +41,109 @@ function rateVideoRetryWrapper (req: express.Request, res: express.Response, nex
errorMessage: 'Cannot update the user video rate.' errorMessage: 'Cannot update the user video rate.'
} }
retryTransactionWrapper(rateVideo, options, function (err) { retryTransactionWrapper(rateVideo, options)
if (err) return next(err) .then(() => res.type('json').status(204).end())
.catch(err => next(err))
return res.type('json').status(204).end()
})
} }
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 rateType = req.body.rating
const videoInstance = res.locals.video const videoInstance = res.locals.video
const userInstance = res.locals.oauth.token.User const userInstance = res.locals.oauth.token.User
waterfall([ return db.sequelize.transaction(t => {
startSerializableTransaction, return db.UserVideoRate.load(userInstance.id, videoInstance.id, t)
.then(previousRate => {
const options = { transaction: t }
function findPreviousRate (t, callback) { let likesToIncrement = 0
db.UserVideoRate.load(userInstance.id, videoInstance.id, t, function (err, previousRate) { let dislikesToIncrement = 0
return callback(err, t, previousRate)
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 }))
}
}) })
}, .then(({ likesToIncrement, dislikesToIncrement }) => {
const options = { transaction: t }
function insertUserRateIntoDB (t, previousRate, callback) { const incrementQuery = {
const options = { transaction: t } likes: likesToIncrement,
dislikes: dislikesToIncrement
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
} }
db.UserVideoRate.create(query, options).asCallback(function (err) { // Even if we do not own the video we increment the attributes
return callback(err, t, likesToIncrement, dislikesToIncrement) // It is usefull for the user to have a feedback
}) return videoInstance.increment(incrementQuery, options).then(() => ({ 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)
}) })
}, .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) { const eventsParams = []
// No need for an event type, we own the video
if (videoInstance.isOwned()) return callback(null, t, likesToIncrement, dislikesToIncrement)
const eventsParams = [] if (likesToIncrement !== 0) {
eventsParams.push({
videoId: videoInstance.id,
type: REQUEST_VIDEO_EVENT_TYPES.LIKES,
count: likesToIncrement
})
}
if (likesToIncrement !== 0) { if (dislikesToIncrement !== 0) {
eventsParams.push({ eventsParams.push({
videoId: videoInstance.id, videoId: videoInstance.id,
type: REQUEST_VIDEO_EVENT_TYPES.LIKES, type: REQUEST_VIDEO_EVENT_TYPES.DISLIKES,
count: likesToIncrement count: dislikesToIncrement
}) })
} }
if (dislikesToIncrement !== 0) { return addEventsToRemoteVideo(eventsParams, t).then(() => ({ likesToIncrement, dislikesToIncrement }))
eventsParams.push({
videoId: videoInstance.id,
type: REQUEST_VIDEO_EVENT_TYPES.DISLIKES,
count: dislikesToIncrement
})
}
addEventsToRemoteVideo(eventsParams, t, function (err) {
return callback(err, t, 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) { const qadusParams = []
// 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 = [] if (likesToIncrement !== 0) {
qadusParams.push({
videoId: videoInstance.id,
type: REQUEST_VIDEO_QADU_TYPES.LIKES
})
}
if (likesToIncrement !== 0) { if (dislikesToIncrement !== 0) {
qadusParams.push({ qadusParams.push({
videoId: videoInstance.id, videoId: videoInstance.id,
type: REQUEST_VIDEO_QADU_TYPES.LIKES type: REQUEST_VIDEO_QADU_TYPES.DISLIKES
}) })
} }
if (dislikesToIncrement !== 0) { return quickAndDirtyUpdatesVideoToFriends(qadusParams, t)
qadusParams.push({
videoId: videoInstance.id,
type: REQUEST_VIDEO_QADU_TYPES.DISLIKES
})
}
quickAndDirtyUpdatesVideoToFriends(qadusParams, t, function (err) {
return callback(err, t)
}) })
}, })
.then(() => logger.info('User video rate for video %s of user %s updated.', videoInstance.name, userInstance.username))
commitTransaction .catch(err => {
// This is just a debug because we will retry the insert
], function (err: Error, t: Sequelize.Transaction) { logger.debug('Cannot add the user video rate.', { error: err })
if (err) { throw 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)
}) })
} }

View File

@ -1,8 +1,7 @@
import { parallel } from 'async'
import * as express from 'express' import * as express from 'express'
import * as fs from 'fs'
import { join } from 'path' import { join } from 'path'
import * as validator from 'validator' import * as validator from 'validator'
import * as Promise from 'bluebird'
import { database as db } from '../initializers/database' import { database as db } from '../initializers/database'
import { import {
@ -11,7 +10,7 @@ import {
STATIC_PATHS, STATIC_PATHS,
STATIC_MAX_AGE STATIC_MAX_AGE
} from '../initializers' } from '../initializers'
import { root } from '../helpers' import { root, readFileBufferPromise } from '../helpers'
import { VideoInstance } from '../models' import { VideoInstance } from '../models'
const clientsRouter = express.Router() const clientsRouter = express.Router()
@ -95,19 +94,15 @@ function generateWatchHtmlPage (req: express.Request, res: express.Response, nex
// Let Angular application handle errors // Let Angular application handle errors
if (!validator.isUUID(videoId, 4)) return res.sendFile(indexPath) if (!validator.isUUID(videoId, 4)) return res.sendFile(indexPath)
parallel({ Promise.all([
file: function (callback) { readFileBufferPromise(indexPath),
fs.readFile(indexPath, callback) db.Video.loadAndPopulateAuthorAndPodAndTags(videoId)
}, ])
.then(([ file, video ]) => {
file = file as Buffer
video = video as VideoInstance
video: function (callback) { const html = file.toString()
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
// Let Angular application handle errors // Let Angular application handle errors
if (!video) return res.sendFile(indexPath) if (!video) return res.sendFile(indexPath)
@ -115,4 +110,5 @@ function generateWatchHtmlPage (req: express.Request, res: express.Response, nex
const htmlStringPageWithTags = addOpenGraphTags(html, video) const htmlStringPageWithTags = addOpenGraphTags(html, video)
res.set('Content-Type', 'text/html; charset=UTF-8').send(htmlStringPageWithTags) 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 { 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 () { function isTestInstance () {
return process.env.NODE_ENV === 'test' return process.env.NODE_ENV === 'test'
@ -14,9 +28,82 @@ function root () {
return join(__dirname, '..', '..', '..') 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 { export {
isTestInstance, 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 // TODO: import from ES6 when retry typing file will include errorFilter function
import * as retry from 'async/retry' import * as retry from 'async/retry'
import * as Promise from 'bluebird'
import { database as db } from '../initializers/database'
import { logger } from './logger' 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[] } 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 : [] const args = options.arguments ? options.arguments : []
transactionRetryer( return transactionRetryer(
function (callback) { function (callback) {
return functionToRetry.apply(this, args.concat([ callback ])) functionToRetry.apply(this, args)
}, .then(result => callback(null, result))
function (err) { .catch(err => callback(err))
if (err) {
logger.error(options.errorMessage, { error: err })
}
// Do not return the error, continue the process
return finalCallback(null)
} }
) )
.catch(err => {
// Do not throw the error, continue the process
logger.error(options.errorMessage, { error: err })
})
} }
function transactionRetryer (func: Function, callback: (err: Error) => void) { function transactionRetryer (func: Function) {
retry({ return new Promise((res, rej) => {
times: 5, retry({
times: 5,
errorFilter: function (err) { errorFilter: function (err) {
const willRetry = (err.name === 'SequelizeDatabaseError') const willRetry = (err.name === 'SequelizeDatabaseError')
logger.debug('Maybe retrying the transaction function.', { willRetry }) logger.debug('Maybe retrying the transaction function.', { willRetry })
return willRetry return willRetry
} }
}, func, callback) }, func, function (err) {
} err ? rej(err) : res()
})
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)
}) })
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
export { export {
commitTransaction,
retryTransactionWrapper, retryTransactionWrapper,
rollbackTransaction,
startSerializableTransaction,
transactionRetryer transactionRetryer
} }

View File

@ -1,7 +1,5 @@
import * as crypto from 'crypto' import * as crypto from 'crypto'
import * as bcrypt from 'bcrypt'
import * as fs from 'fs' import * as fs from 'fs'
import * as openssl from 'openssl-wrapper'
import { join } from 'path' import { join } from 'path'
import { import {
@ -12,6 +10,14 @@ import {
BCRYPT_SALT_SIZE, BCRYPT_SALT_SIZE,
PUBLIC_CERT_NAME PUBLIC_CERT_NAME
} from '../initializers' } from '../initializers'
import {
readFilePromise,
bcryptComparePromise,
bcryptGenSaltPromise,
bcryptHashPromise,
accessPromise,
opensslExecPromise
} from './core-utils'
import { logger } from './logger' import { logger } from './logger'
function checkSignature (publicKey: string, data: string, hexSignature: string) { function checkSignature (publicKey: string, data: string, hexSignature: string) {
@ -60,46 +66,32 @@ function sign (data: string|Object) {
return signature return signature
} }
function comparePassword (plainPassword: string, hashPassword: string, callback: (err: Error, match?: boolean) => void) { function comparePassword (plainPassword: string, hashPassword: string) {
bcrypt.compare(plainPassword, hashPassword, function (err, isPasswordMatch) { return bcryptComparePromise(plainPassword, hashPassword)
if (err) return callback(err)
return callback(null, isPasswordMatch)
})
} }
function createCertsIfNotExist (callback: (err: Error) => void) { function createCertsIfNotExist () {
certsExist(function (err, exist) { return certsExist().then(exist => {
if (err) return callback(err)
if (exist === true) { if (exist === true) {
return callback(null) return undefined
} }
createCerts(function (err) { return createCerts()
return callback(err)
})
}) })
} }
function cryptPassword (password: string, callback: (err: Error, hash?: string) => void) { function cryptPassword (password: string) {
bcrypt.genSalt(BCRYPT_SALT_SIZE, function (err, salt) { return bcryptGenSaltPromise(BCRYPT_SALT_SIZE).then(salt => bcryptHashPromise(password, salt))
if (err) return callback(err)
bcrypt.hash(password, salt, function (err, hash) {
return callback(err, hash)
})
})
} }
function getMyPrivateCert (callback: (err: Error, privateCert: string) => void) { function getMyPrivateCert () {
const certPath = join(CONFIG.STORAGE.CERT_DIR, PRIVATE_CERT_NAME) 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) 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) 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 // If there is an error the certificates do not exist
const exists = !err return accessPromise(certPath)
return callback(null, exists) .then(() => true)
}) .catch(() => false)
} }
function createCerts (callback: (err: Error) => void) { function createCerts () {
certsExist(function (err, exist) { return certsExist().then(exist => {
if (err) return callback(err)
if (exist === true) { if (exist === true) {
const errorMessage = 'Certs already exist.' const errorMessage = 'Certs already exist.'
logger.warning(errorMessage) logger.warning(errorMessage)
return callback(new Error(errorMessage)) throw new Error(errorMessage)
} }
logger.info('Generating a RSA key...') logger.info('Generating a RSA key...')
@ -142,30 +132,27 @@ function createCerts (callback: (err: Error) => void) {
'out': privateCertPath, 'out': privateCertPath,
'2048': false '2048': false
} }
openssl.exec('genrsa', genRsaOptions, function (err) { return opensslExecPromise('genrsa', genRsaOptions)
if (err) { .then(() => {
logger.error('Cannot create private key on this pod.') logger.info('RSA key generated.')
return callback(err) logger.info('Managing public key...')
}
logger.info('RSA key generated.') const publicCertPath = join(CONFIG.STORAGE.CERT_DIR, 'peertube.pub')
logger.info('Managing public key...') const rsaOptions = {
'in': privateCertPath,
const publicCertPath = join(CONFIG.STORAGE.CERT_DIR, 'peertube.pub') 'pubout': true,
const rsaOptions = { 'out': publicCertPath
'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)
} }
return opensslExecPromise('rsa', rsaOptions)
logger.info('Public key managed.') .then(() => logger.info('Public key managed.'))
return callback(null) .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 replay from 'request-replay'
import * as request from 'request' import * as request from 'request'
import * as Promise from 'bluebird'
import { import {
RETRY_REQUESTS, RETRY_REQUESTS,
@ -14,16 +15,18 @@ type MakeRetryRequestParams = {
method: 'GET'|'POST', method: 'GET'|'POST',
json: Object json: Object
} }
function makeRetryRequest (params: MakeRetryRequestParams, callback: request.RequestCallback) { function makeRetryRequest (params: MakeRetryRequestParams) {
replay( return new Promise<{ response: request.RequestResponse, body: any }>((res, rej) => {
request(params, callback), replay(
{ request(params, (err, response, body) => err ? rej(err) : res({ response, body })),
retries: RETRY_REQUESTS, {
factor: 3, retries: RETRY_REQUESTS,
maxTimeout: Infinity, factor: 3,
errorCodes: [ 'EADDRINFO', 'ETIMEDOUT', 'ECONNRESET', 'ESOCKETTIMEDOUT', 'ENOTFOUND', 'ECONNREFUSED' ] maxTimeout: Infinity,
} errorCodes: [ 'EADDRINFO', 'ETIMEDOUT', 'ECONNRESET', 'ESOCKETTIMEDOUT', 'ENOTFOUND', 'ECONNREFUSED' ]
) }
)
})
} }
type MakeSecureRequestParams = { type MakeSecureRequestParams = {
@ -33,41 +36,43 @@ type MakeSecureRequestParams = {
sign: boolean sign: boolean
data?: Object data?: Object
} }
function makeSecureRequest (params: MakeSecureRequestParams, callback: request.RequestCallback) { function makeSecureRequest (params: MakeSecureRequestParams) {
const requestParams = { return new Promise<{ response: request.RequestResponse, body: any }>((res, rej) => {
url: REMOTE_SCHEME.HTTP + '://' + params.toPod.host + params.path, const requestParams = {
json: {} url: REMOTE_SCHEME.HTTP + '://' + params.toPod.host + params.path,
} json: {}
}
if (params.method !== 'POST') { if (params.method !== 'POST') {
return callback(new Error('Cannot make a secure request with a non POST method.'), null, null) return rej(new Error('Cannot make a secure request with a non POST method.'))
} }
// Add signature if it is specified in the params // Add signature if it is specified in the params
if (params.sign === true) { if (params.sign === true) {
const host = CONFIG.WEBSERVER.HOST 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) { if (params.data) {
dataToSign = params.data requestParams.json['data'] = 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'] = { request.post(requestParams, (err, response, body) => err ? rej(err) : res({ response, body }))
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)
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View File

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

View File

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

View File

@ -1,12 +1,12 @@
import * as fs from 'fs'
import { join } from 'path' import { join } from 'path'
import { flattenDepth } from 'lodash'
import * as Sequelize from 'sequelize' import * as Sequelize from 'sequelize'
import { each } from 'async' import * as Promise from 'bluebird'
import { CONFIG } from './constants' import { CONFIG } from './constants'
// Do not use barrel, we need to load database first // Do not use barrel, we need to load database first
import { logger } from '../helpers/logger' import { logger } from '../helpers/logger'
import { isTestInstance } from '../helpers/core-utils' import { isTestInstance, readdirPromise } from '../helpers/core-utils'
import { import {
ApplicationModel, ApplicationModel,
AuthorModel, AuthorModel,
@ -33,7 +33,7 @@ const password = CONFIG.DATABASE.PASSWORD
const database: { const database: {
sequelize?: Sequelize.Sequelize, sequelize?: Sequelize.Sequelize,
init?: (silent: any, callback: any) => void, init?: (silent: boolean) => Promise<void>,
Application?: ApplicationModel, Application?: ApplicationModel,
Author?: AuthorModel, Author?: AuthorModel,
@ -72,19 +72,17 @@ const sequelize = new Sequelize(dbname, username, password, {
database.sequelize = sequelize database.sequelize = sequelize
database.init = function (silent: boolean, callback: (err: Error) => void) { database.init = function (silent: boolean) {
const modelDirectory = join(__dirname, '..', 'models') const modelDirectory = join(__dirname, '..', 'models')
getModelFiles(modelDirectory, function (err, filePaths) { return getModelFiles(modelDirectory).then(filePaths => {
if (err) throw err filePaths.forEach(filePath => {
filePaths.forEach(function (filePath) {
const model = sequelize.import(filePath) const model = sequelize.import(filePath)
database[model['name']] = model database[model['name']] = model
}) })
Object.keys(database).forEach(function (modelName) { Object.keys(database).forEach(modelName => {
if ('associate' in database[modelName]) { if ('associate' in database[modelName]) {
database[modelName].associate(database) 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) 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) { function getModelFiles (modelDirectory: string) {
fs.readdir(modelDirectory, function (err, files) { return readdirPromise(modelDirectory)
if (err) throw err .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) { return true
// Find directories })
if (
directory.endsWith('.js.map') ||
directory === 'index.js' || directory === 'index.ts' ||
directory === 'utils.js' || directory === 'utils.ts'
) return false
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 const promise = readdirPromise(modelDirectoryPath).then(files => {
each(directories, function (directory: string, eachCallback: ErrorCallback<Error>) { const filteredFiles = files.filter(file => {
const modelDirectoryPath = join(modelDirectory, directory) 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) { return true
if (err) return eachCallback(err) }).map(file => join(modelDirectoryPath, file))
const filteredFiles = files.filter(file => { return filteredFiles
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)
}) })
modelFilePaths = modelFilePaths.concat(filteredFiles) tasks.push(promise)
return eachCallback(null)
}) })
}, 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 { join } from 'path'
import * as config from 'config' import * as config from 'config'
import { each, series } from 'async'
import * as mkdirp from 'mkdirp'
import * as passwordGenerator from 'password-generator' import * as passwordGenerator from 'password-generator'
import * as Promise from 'bluebird'
import { database as db } from './database' import { database as db } from './database'
import { USER_ROLES, CONFIG, LAST_MIGRATION_VERSION } from './constants' import { USER_ROLES, CONFIG, LAST_MIGRATION_VERSION } from './constants'
import { clientsExist, usersExist } from './checker' import { clientsExist, usersExist } from './checker'
import { logger, createCertsIfNotExist, root } from '../helpers' import { logger, createCertsIfNotExist, root, mkdirpPromise } from '../helpers'
function installApplication (callback: (err: Error) => void) { function installApplication () {
series([ return db.sequelize.sync()
function createDatabase (callbackAsync) { .then(() => createDirectoriesIfNotExist())
db.sequelize.sync().asCallback(callbackAsync) .then(() => createCertsIfNotExist())
// db.sequelize.sync({ force: true }).asCallback(callbackAsync) .then(() => createOAuthClientIfNotExist())
}, .then(() => createOAuthAdminIfNotExist())
function createDirectories (callbackAsync) {
createDirectoriesIfNotExist(callbackAsync)
},
function createCertificates (callbackAsync) {
createCertsIfNotExist(callbackAsync)
},
function createOAuthClient (callbackAsync) {
createOAuthClientIfNotExist(callbackAsync)
},
function createOAuthUser (callbackAsync) {
createOAuthAdminIfNotExist(callbackAsync)
}
], callback)
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -42,21 +24,22 @@ export {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
function createDirectoriesIfNotExist (callback: (err: Error) => void) { function createDirectoriesIfNotExist () {
const storages = config.get('storage') const storages = config.get('storage')
each(Object.keys(storages), function (key, callbackEach) { const tasks = []
Object.keys(storages).forEach(key => {
const dir = storages[key] const dir = storages[key]
mkdirp(join(root(), dir), callbackEach) tasks.push(mkdirpPromise(join(root(), dir)))
}, callback) })
return Promise.all(tasks)
} }
function createOAuthClientIfNotExist (callback: (err: Error) => void) { function createOAuthClientIfNotExist () {
clientsExist(function (err, exist) { return clientsExist().then(exist => {
if (err) return callback(err)
// Nothing to do, clients already 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.') logger.info('Creating a default OAuth Client.')
@ -69,23 +52,19 @@ function createOAuthClientIfNotExist (callback: (err: Error) => void) {
redirectUris: null redirectUris: null
}) })
client.save().asCallback(function (err, createdClient) { return client.save().then(createdClient => {
if (err) return callback(err)
logger.info('Client id: ' + createdClient.clientId) logger.info('Client id: ' + createdClient.clientId)
logger.info('Client secret: ' + createdClient.clientSecret) logger.info('Client secret: ' + createdClient.clientSecret)
return callback(null) return undefined
}) })
}) })
} }
function createOAuthAdminIfNotExist (callback: (err: Error) => void) { function createOAuthAdminIfNotExist () {
usersExist(function (err, exist) { return usersExist().then(exist => {
if (err) return callback(err)
// Nothing to do, users already exist // Nothing to do, users already exist
if (exist === true) return callback(null) if (exist === true) return undefined
logger.info('Creating the administrator.') logger.info('Creating the administrator.')
@ -116,14 +95,12 @@ function createOAuthAdminIfNotExist (callback: (err: Error) => void) {
role role
} }
db.User.create(userData, createOptions).asCallback(function (err, createdUser) { return db.User.create(userData, createOptions).then(createdUser => {
if (err) return callback(err)
logger.info('Username: ' + username) logger.info('Username: ' + username)
logger.info('User password: ' + password) logger.info('User password: ' + password)
logger.info('Creating Application table.') 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: {
function up (utils, finalCallback) { transaction: Sequelize.Transaction,
queryInterface: Sequelize.QueryInterface,
sequelize: Sequelize.Sequelize
}): Promise<void> {
const q = utils.queryInterface const q = utils.queryInterface
const Sequelize = utils.Sequelize
const data = { const data = {
type: Sequelize.STRING(400), type: Sequelize.STRING(400),
@ -11,27 +14,16 @@ function up (utils, finalCallback) {
defaultValue: '' defaultValue: ''
} }
waterfall([ return q.addColumn('Pods', 'email', data)
.then(() => {
function addEmailColumn (callback) {
q.addColumn('Pods', 'email', data, { transaction: utils.transaction }).asCallback(function (err) {
return callback(err)
})
},
function updateWithFakeEmails (callback) {
const query = 'UPDATE "Pods" SET "email" = \'dummy@example.com\'' const query = 'UPDATE "Pods" SET "email" = \'dummy@example.com\''
utils.sequelize.query(query, { transaction: utils.transaction }).asCallback(function (err) { return utils.sequelize.query(query, { transaction: utils.transaction })
return callback(err) })
}) .then(() => {
},
function nullOnDefault (callback) {
data.defaultValue = null data.defaultValue = null
q.changeColumn('Pods', 'email', data, { transaction: utils.transaction }).asCallback(callback) return q.changeColumn('Pods', 'email', data)
} })
], finalCallback)
} }
function down (options, callback) { 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: {
function up (utils, finalCallback) { transaction: Sequelize.Transaction,
queryInterface: Sequelize.QueryInterface,
sequelize: Sequelize.Sequelize
}): Promise<void> {
const q = utils.queryInterface const q = utils.queryInterface
const Sequelize = utils.Sequelize
const data = { const data = {
type: Sequelize.STRING(400), type: Sequelize.STRING(400),
allowNull: false, allowNull: false,
defaultValue: '' defaultValue: ''
} }
return q.addColumn('Users', 'email', data)
waterfall([ .then(() => {
function addEmailColumn (callback) {
q.addColumn('Users', 'email', data, { transaction: utils.transaction }).asCallback(function (err) {
return callback(err)
})
},
function updateWithFakeEmails (callback) {
const query = 'UPDATE "Users" SET "email" = CONCAT("username", \'@example.com\')' const query = 'UPDATE "Users" SET "email" = CONCAT("username", \'@example.com\')'
utils.sequelize.query(query, { transaction: utils.transaction }).asCallback(function (err) { return utils.sequelize.query(query, { transaction: utils.transaction })
return callback(err) })
}) .then(() => {
},
function nullOnDefault (callback) {
data.defaultValue = null data.defaultValue = null
q.changeColumn('Users', 'email', data, { transaction: utils.transaction }).asCallback(callback) return q.changeColumn('Users', 'email', data)
} })
], finalCallback)
} }
function down (options, callback) { function down (options, callback) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,29 +3,23 @@ import { logger } from '../../../helpers'
import { addVideoToFriends } from '../../../lib' import { addVideoToFriends } from '../../../lib'
import { VideoInstance } from '../../../models' import { VideoInstance } from '../../../models'
function process (data: { id: string }, callback: (err: Error, videoInstance?: VideoInstance) => void) { function process (data: { id: string }) {
db.Video.loadAndPopulateAuthorAndPodAndTags(data.id, function (err, video) { return db.Video.loadAndPopulateAuthorAndPodAndTags(data.id).then(video => {
if (err) return callback(err) return video.transcodeVideofile().then(() => video)
video.transcodeVideofile(function (err) {
return callback(err, 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 }) 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) logger.info('Job %d is a success.', jobId)
video.toAddRemoteJSON(function (err, remoteVideo) { video.toAddRemoteJSON().then(remoteVideo => {
if (err) return callback(err)
// Now we'll add the video's meta data to our friends // 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 // Finish processing jobs from a previous start
const state = JOB_STATES.PROCESSING const state = JOB_STATES.PROCESSING
db.Job.listWithLimit(limit, state, (err, jobs) => { db.Job.listWithLimit(limit, state)
this.enqueueJobs(err, jobsQueue, jobs) .then(jobs => {
this.enqueueJobs(jobsQueue, jobs)
forever( forever(
next => { next => {
if (jobsQueue.length() !== 0) { if (jobsQueue.length() !== 0) {
// Finish processing the queue first // Finish processing the queue first
return setTimeout(next, JOBS_FETCHING_INTERVAL) 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)
})
} }
// Optimization: we could use "drain" from queue object const state = JOB_STATES.PENDING
return setTimeout(next, JOBS_FETCHING_INTERVAL) 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 = { const createQuery = {
state: JOB_STATES.PENDING, state: JOB_STATES.PENDING,
handlerName, handlerName,
@ -70,67 +68,62 @@ class JobScheduler {
} }
const options = { transaction } 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[]) { private enqueueJobs (jobsQueue: AsyncQueue<JobInstance>, jobs: JobInstance[]) {
if (err) { jobs.forEach(job => jobsQueue.push(job))
logger.error('Cannot list pending jobs.', { error: err })
} else {
jobs.forEach(job => {
jobsQueue.push(job)
})
}
} }
private processJob (job: JobInstance, callback: (err: Error) => void) { private processJob (job: JobInstance, callback: (err: Error) => void) {
const jobHandler = jobHandlers[job.handlerName] 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) logger.info('Processing job %d with handler %s.', job.id, job.handlerName)
job.state = JOB_STATES.PROCESSING job.state = JOB_STATES.PROCESSING
job.save().asCallback(err => { return job.save()
if (err) return this.cannotSaveJobError(err, callback) .then(() => {
return jobHandler.process(job.handlerInputData)
if (jobHandler === undefined) { })
logger.error('Unknown job handler for job %s.', job.handlerName) .then(
return callback(null) result => {
} return this.onJobSuccess(jobHandler, job, result)
},
return jobHandler.process(job.handlerInputData, (err, result) => {
if (err) { err => {
logger.error('Error in job handler %s.', job.handlerName, { error: err }) logger.error('Error in job handler %s.', job.handlerName, { error: err })
return this.onJobError(jobHandler, job, result, callback) return this.onJobError(jobHandler, job, err)
} }
)
return this.onJobSuccess(jobHandler, job, result, callback) .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.state = JOB_STATES.ERROR
job.save().asCallback(err => { return job.save()
if (err) return this.cannotSaveJobError(err, callback) .then(() => jobHandler.onError(err, job.id))
.catch(err => this.cannotSaveJobError(err))
return jobHandler.onError(err, job.id, jobResult, callback)
})
} }
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.state = JOB_STATES.SUCCESS
job.save().asCallback(err => { return job.save()
if (err) return this.cannotSaveJobError(err, callback) .then(() => jobHandler.onSuccess(job.id, jobResult))
.catch(err => this.cannotSaveJobError(err))
return jobHandler.onSuccess(err, job.id, jobResult, callback)
})
} }
private cannotSaveJobError (err: Error, callback: (err: Error) => void) { private cannotSaveJobError (err: Error) {
logger.error('Cannot save new job state.', { error: err }) 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) { return db.User.getByUsername(username).then(function (user) {
if (!user) return null if (!user) return null
// We need to return a promise return user.isPasswordMatch(password).then(passwordMatch => {
return new Promise(function (resolve, reject) { if (passwordMatch === false) return null
return user.isPasswordMatch(password, function (err, isPasswordMatch) {
if (err) return reject(err)
if (isPasswordMatch === true) { return user
return resolve(user)
}
return resolve(null)
})
}) })
}) })
} }
@ -80,8 +73,6 @@ function saveToken (token: TokenInfo, client: OAuthClientInstance, user: UserIns
tokenCreated.user = user tokenCreated.user = user
return tokenCreated 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 { database as db } from '../../initializers/database'
import { logger, makeSecureRequest } from '../../helpers' import { logger, makeSecureRequest } from '../../helpers'
import { PodInstance } from '../../models' import { AbstractRequestClass, AbstractRequestToPodClass, PodInstance } from '../../models'
import { import {
API_VERSION, API_VERSION,
REQUESTS_IN_PARALLEL, REQUESTS_IN_PARALLEL,
REQUESTS_INTERVAL REQUESTS_INTERVAL
} from '../../initializers' } from '../../initializers'
abstract class AbstractRequestScheduler { abstract class AbstractRequestScheduler <T> {
requestInterval: number requestInterval: number
limitPods: number limitPods: number
limitPerPod: number limitPerPod: number
@ -24,9 +25,9 @@ abstract class AbstractRequestScheduler {
this.requestInterval = REQUESTS_INTERVAL this.requestInterval = REQUESTS_INTERVAL
} }
abstract getRequestModel () abstract getRequestModel (): AbstractRequestClass<T>
abstract getRequestToPodModel () abstract getRequestToPodModel (): AbstractRequestToPodClass
abstract buildRequestObjects (requests: any) abstract buildRequestObjects (requestsGrouped: T): {}
activate () { activate () {
logger.info('Requests scheduler activated.') logger.info('Requests scheduler activated.')
@ -55,20 +56,18 @@ abstract class AbstractRequestScheduler {
return REQUESTS_INTERVAL - (Date.now() - this.lastRequestTimestamp) return REQUESTS_INTERVAL - (Date.now() - this.lastRequestTimestamp)
} }
remainingRequestsCount (callback: (err: Error, total: number) => void) { remainingRequestsCount () {
return this.getRequestModel().countTotalRequests(callback) return this.getRequestModel().countTotalRequests()
} }
flush (callback: (err: Error) => void) { flush () {
this.getRequestModel().removeAll(callback) return this.getRequestModel().removeAll()
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Make a requests to friends of a certain type // Make a requests to friends of a certain type
protected makeRequest (toPod: PodInstance, requestEndpoint: string, requestsToMake: Object, callback) { protected makeRequest (toPod: PodInstance, requestEndpoint: string, requestsToMake: Object) {
if (!callback) callback = function () { /* empty */ }
const params = { const params = {
toPod: toPod, toPod: toPod,
sign: true, // Prove our identity sign: true, // Prove our identity
@ -79,65 +78,64 @@ abstract class AbstractRequestScheduler {
// Make multiple retry requests to all of pods // Make multiple retry requests to all of pods
// The function fire some useful callbacks // The function fire some useful callbacks
makeSecureRequest(params, (err, res) => { return makeSecureRequest(params)
if (err || (res.statusCode !== 200 && res.statusCode !== 201 && res.statusCode !== 204)) { .then(({ response, body }) => {
err = err ? err.message : 'Status code not 20x : ' + res.statusCode 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 }) logger.error('Error sending secure request to %s pod.', toPod.host, { error: err })
return callback(err) throw err
} })
return callback(null)
})
} }
// Make all the requests of the scheduler // Make all the requests of the scheduler
protected makeRequests () { protected makeRequests () {
this.getRequestModel().listWithLimitAndRandom(this.limitPods, this.limitPerPod, (err, requests) => { return this.getRequestModel().listWithLimitAndRandom(this.limitPods, this.limitPerPod)
if (err) { .then((requestsGrouped: T) => {
logger.error('Cannot get the list of "%s".', this.description, { err: err }) // We want to group requests by destinations pod and endpoint
return // Abort const requestsToMake = this.buildRequestObjects(requestsGrouped)
}
// If there are no requests, abort // If there are no requests, abort
if (requests.length === 0) { if (isEmpty(requestsToMake) === true) {
logger.info('No "%s" to make.', this.description) logger.info('No "%s" to make.', this.description)
return return { goodPods: [], badPods: [] }
} }
// We want to group requests by destinations pod and endpoint logger.info('Making "%s" to friends.', this.description)
const requestsToMakeGrouped = this.buildRequestObjects(requests)
logger.info('Making "%s" to friends.', this.description) const goodPods = []
const badPods = []
const goodPods = [] return Promise.map(Object.keys(requestsToMake), hashKey => {
const badPods = [] const requestToMake = requestsToMake[hashKey]
const toPod: PodInstance = requestToMake.toPod
eachLimit(Object.keys(requestsToMakeGrouped), REQUESTS_IN_PARALLEL, (hashKey, callbackEach) => { return this.makeRequest(toPod, requestToMake.endpoint, requestToMake.datas)
const requestToMake = requestsToMakeGrouped[hashKey] .then(() => {
const toPod = requestToMake.toPod 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) => { this.afterRequestHook()
if (err) {
badPods.push(requestToMake.toPod.id)
return callbackEach()
}
logger.debug('Removing requests for pod %s.', requestToMake.toPod.id, { requestsIds: requestToMake.ids }) // Remove the pod id of these request ids
goodPods.push(requestToMake.toPod.id) return this.getRequestToPodModel().removeByRequestIdsAndPod(requestToMake.ids, requestToMake.toPod.id)
})
// Remove the pod id of these request ids .catch(err => {
this.getRequestToPodModel().removeByRequestIdsAndPod(requestToMake.ids, requestToMake.toPod.id, callbackEach) badPods.push(requestToMake.toPod.id)
logger.info('Cannot make request to %s.', toPod.host, { error: err })
this.afterRequestHook() })
}) }, { concurrency: REQUESTS_IN_PARALLEL }).then(() => ({ goodPods, badPods }))
}, () => {
// All the requests were made, we update the pods score
db.Pod.updatePodsScore(goodPods, badPods)
this.afterRequestsHook()
}) })
}) .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 () { protected afterRequestHook () {

View File

@ -3,10 +3,8 @@ import * as Sequelize from 'sequelize'
import { database as db } from '../../initializers/database' import { database as db } from '../../initializers/database'
import { AbstractRequestScheduler } from './abstract-request-scheduler' import { AbstractRequestScheduler } from './abstract-request-scheduler'
import { logger } from '../../helpers' import { logger } from '../../helpers'
import { import { REQUESTS_LIMIT_PODS, REQUESTS_LIMIT_PER_POD } from '../../initializers'
REQUESTS_LIMIT_PODS, import { RequestsGrouped } from '../../models'
REQUESTS_LIMIT_PER_POD
} from '../../initializers'
import { RequestEndpoint } from '../../../shared' import { RequestEndpoint } from '../../../shared'
export type RequestSchedulerOptions = { export type RequestSchedulerOptions = {
@ -17,7 +15,7 @@ export type RequestSchedulerOptions = {
transaction: Sequelize.Transaction transaction: Sequelize.Transaction
} }
class RequestScheduler extends AbstractRequestScheduler { class RequestScheduler extends AbstractRequestScheduler<RequestsGrouped> {
constructor () { constructor () {
super() super()
@ -36,11 +34,11 @@ class RequestScheduler extends AbstractRequestScheduler {
return db.RequestToPod return db.RequestToPod
} }
buildRequestObjects (requests: { [ toPodId: number ]: any }) { buildRequestObjects (requestsGrouped: RequestsGrouped) {
const requestsToMakeGrouped = {} const requestsToMakeGrouped = {}
Object.keys(requests).forEach(toPodId => { Object.keys(requestsGrouped).forEach(toPodId => {
requests[toPodId].forEach(data => { requestsGrouped[toPodId].forEach(data => {
const request = data.request const request = data.request
const pod = data.pod const pod = data.pod
const hashKey = toPodId + request.endpoint const hashKey = toPodId + request.endpoint
@ -62,12 +60,12 @@ class RequestScheduler extends AbstractRequestScheduler {
return requestsToMakeGrouped 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 // TODO: check the setPods works
const podIds = [] const podIds = []
// If there are no destination pods abort // If there are no destination pods abort
if (toIds.length === 0) return callback(null) if (toIds.length === 0) return undefined
toIds.forEach(toPod => { toIds.forEach(toPod => {
podIds.push(toPod) podIds.push(toPod)
@ -85,20 +83,18 @@ class RequestScheduler extends AbstractRequestScheduler {
transaction transaction
} }
return db.Request.create(createQuery, dbRequestOptions).asCallback((err, request) => { return db.Request.create(createQuery, dbRequestOptions)
if (err) return callback(err) .then(request => {
return request.setPods(podIds, dbRequestOptions)
return request.setPods(podIds, dbRequestOptions).asCallback(callback) })
})
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
afterRequestsHook () { afterRequestsHook () {
// Flush requests with no pod // Flush requests with no pod
this.getRequestModel().removeWithEmptyTo(err => { this.getRequestModel().removeWithEmptyTo()
if (err) logger.error('Error when removing requests with no pods.', { error: err }) .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, REQUESTS_VIDEO_EVENT_LIMIT_PER_POD,
REQUEST_VIDEO_EVENT_ENDPOINT REQUEST_VIDEO_EVENT_ENDPOINT
} from '../../initializers' } from '../../initializers'
import { RequestsVideoEventGrouped } from '../../models'
import { RequestVideoEventType } from '../../../shared' import { RequestVideoEventType } from '../../../shared'
export type RequestVideoEventSchedulerOptions = { export type RequestVideoEventSchedulerOptions = {
@ -16,7 +17,7 @@ export type RequestVideoEventSchedulerOptions = {
transaction?: Sequelize.Transaction transaction?: Sequelize.Transaction
} }
class RequestVideoEventScheduler extends AbstractRequestScheduler { class RequestVideoEventScheduler extends AbstractRequestScheduler<RequestsVideoEventGrouped> {
constructor () { constructor () {
super() super()
@ -35,7 +36,7 @@ class RequestVideoEventScheduler extends AbstractRequestScheduler {
return db.RequestVideoEvent return db.RequestVideoEvent
} }
buildRequestObjects (eventsToProcess: { [ toPodId: number ]: any }[]) { buildRequestObjects (eventRequests: RequestsVideoEventGrouped) {
const requestsToMakeGrouped = {} const requestsToMakeGrouped = {}
/* Example: /* Example:
@ -50,8 +51,8 @@ class RequestVideoEventScheduler extends AbstractRequestScheduler {
// We group video events per video and per pod // We group video events per video and per pod
// We add the counts of the same event types // We add the counts of the same event types
Object.keys(eventsToProcess).forEach(toPodId => { Object.keys(eventRequests).forEach(toPodId => {
eventsToProcess[toPodId].forEach(eventToProcess => { eventRequests[toPodId].forEach(eventToProcess => {
if (!eventsPerVideoPerPod[toPodId]) eventsPerVideoPerPod[toPodId] = {} if (!eventsPerVideoPerPod[toPodId]) eventsPerVideoPerPod[toPodId] = {}
if (!requestsToMakeGrouped[toPodId]) { if (!requestsToMakeGrouped[toPodId]) {
@ -97,7 +98,7 @@ class RequestVideoEventScheduler extends AbstractRequestScheduler {
return requestsToMakeGrouped return requestsToMakeGrouped
} }
createRequest ({ type, videoId, count, transaction }: RequestVideoEventSchedulerOptions, callback: (err: Error) => void) { createRequest ({ type, videoId, count, transaction }: RequestVideoEventSchedulerOptions) {
if (count === undefined) count = 1 if (count === undefined) count = 1
const dbRequestOptions: Sequelize.CreateOptions = {} const dbRequestOptions: Sequelize.CreateOptions = {}
@ -109,7 +110,7 @@ class RequestVideoEventScheduler extends AbstractRequestScheduler {
videoId 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_ENDPOINT,
REQUEST_VIDEO_QADU_TYPES REQUEST_VIDEO_QADU_TYPES
} from '../../initializers' } from '../../initializers'
import { RequestsVideoQaduGrouped } from '../../models'
import { RequestVideoQaduType } from '../../../shared' import { RequestVideoQaduType } from '../../../shared'
export type RequestVideoQaduSchedulerOptions = { export type RequestVideoQaduSchedulerOptions = {
@ -17,7 +18,7 @@ export type RequestVideoQaduSchedulerOptions = {
transaction?: Sequelize.Transaction transaction?: Sequelize.Transaction
} }
class RequestVideoQaduScheduler extends AbstractRequestScheduler { class RequestVideoQaduScheduler extends AbstractRequestScheduler<RequestsVideoQaduGrouped> {
constructor () { constructor () {
super() super()
@ -36,7 +37,7 @@ class RequestVideoQaduScheduler extends AbstractRequestScheduler {
return db.RequestVideoQadu return db.RequestVideoQadu
} }
buildRequestObjects (requests: { [ toPodId: number ]: any }[]) { buildRequestObjects (requests: RequestsVideoQaduGrouped) {
const requestsToMakeGrouped = {} const requestsToMakeGrouped = {}
Object.keys(requests).forEach(toPodId => { Object.keys(requests).forEach(toPodId => {
@ -105,20 +106,18 @@ class RequestVideoQaduScheduler extends AbstractRequestScheduler {
return requestsToMakeGrouped return requestsToMakeGrouped
} }
createRequest ({ type, videoId, transaction }: RequestVideoQaduSchedulerOptions, callback: (err: Error) => void) { createRequest ({ type, videoId, transaction }: RequestVideoQaduSchedulerOptions) {
const dbRequestOptions: Sequelize.BulkCreateOptions = {} const dbRequestOptions: Sequelize.BulkCreateOptions = {}
if (transaction) dbRequestOptions.transaction = transaction if (transaction) dbRequestOptions.transaction = transaction
// Send the update to all our friends // Send the update to all our friends
db.Pod.listAllIds(transaction, function (err, podIds) { return db.Pod.listAllIds(transaction).then(podIds => {
if (err) return callback(err)
const queries = [] const queries = []
podIds.forEach(podId => { podIds.forEach(podId => {
queries.push({ type, videoId, 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) { function checkSignature (req: express.Request, res: express.Response, next: express.NextFunction) {
const host = req.body.signature.host const host = req.body.signature.host
db.Pod.loadByHost(host, function (err, pod) { db.Pod.loadByHost(host)
if (err) { .then(pod => {
logger.error('Cannot get signed host in body.', { error: err }) if (pod === null) {
return res.sendStatus(500) logger.error('Unknown pod %s.', host)
} return res.sendStatus(403)
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
} }
return next() logger.debug('Checking signature from %s.', host)
}
logger.error('Signature is not okay in body for %s.', req.body.signature.host) let signatureShouldBe
return res.sendStatus(403) // 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 }) logger.debug('Checking makeFriends parameters', { parameters: req.body })
checkErrors(req, res, function () { checkErrors(req, res, function () {
hasFriends(function (err, heHasFriends) { hasFriends()
if (err) { .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 }) logger.error('Cannot know if we have friends.', { error: err })
res.sendStatus(500) 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 }) logger.debug('Checking podsAdd parameters', { parameters: req.body })
checkErrors(req, res, function () { checkErrors(req, res, function () {
db.Pod.loadByHost(req.body.host, function (err, pod) { db.Pod.loadByHost(req.body.host)
if (err) { .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 }) logger.error('Cannot load pod by host.', { error: err })
res.sendStatus(500) 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 }) logger.debug('Checking usersAdd parameters', { parameters: req.body })
checkErrors(req, res, function () { checkErrors(req, res, function () {
db.User.loadByUsernameOrEmail(req.body.username, req.body.email, function (err, user) { db.User.loadByUsernameOrEmail(req.body.username, req.body.email)
if (err) { .then(user => {
if (user) return res.status(409).send('User already exists.')
next()
})
.catch(err => {
logger.error('Error in usersAdd request validator.', { error: err }) logger.error('Error in usersAdd request validator.', { error: err })
return res.sendStatus(500) 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 }) logger.debug('Checking usersRemove parameters', { parameters: req.params })
checkErrors(req, res, function () { checkErrors(req, res, function () {
db.User.loadById(req.params.id, function (err, user) { db.User.loadById(req.params.id)
if (err) { .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 }) logger.error('Error in usersRemove request validator.', { error: err })
return res.sendStatus(500) 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 }) logger.debug('Checking usersVideoRating parameters', { parameters: req.params })
checkErrors(req, res, function () { checkErrors(req, res, function () {
db.Video.load(req.params.videoId, function (err, video) { db.Video.load(req.params.videoId)
if (err) { .then(video => {
if (!video) return res.status(404).send('Video not found')
next()
})
.catch(err => {
logger.error('Error in user request validator.', { error: err }) logger.error('Error in user request validator.', { error: err })
return res.sendStatus(500) 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 'express-validator'
import * as multer from 'multer'
import * as express from 'express' import * as express from 'express'
import { database as db } from '../../initializers/database' import { database as db } from '../../initializers/database'
@ -24,18 +23,19 @@ function videosAddValidator (req: express.Request, res: express.Response, next:
checkErrors(req, res, function () { checkErrors(req, res, function () {
const videoFile = req.files.videofile[0] const videoFile = req.files.videofile[0]
db.Video.getDurationFromFile(videoFile.path, function (err, duration) { db.Video.getDurationFromFile(videoFile.path)
if (err) { .then(duration => {
return res.status(400).send('Cannot retrieve metadata of the file.') 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)) { videoFile['duration'] = duration
return res.status(400).send('Duration of the video file is too big (max: ' + CONSTRAINTS_FIELDS.VIDEOS.DURATION.max + 's).') next()
} })
.catch(err => {
videoFile['duration'] = duration logger.error('Error in getting duration from file.', { error: err })
next() res.status(400).send('Cannot retrieve metadata of the file.')
}) })
}) })
} }
@ -157,43 +157,42 @@ export {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
function checkVideoExists (id: string, res: express.Response, callback: () => void) { function checkVideoExists (id: string, res: express.Response, callback: () => void) {
db.Video.loadAndPopulateAuthorAndPodAndTags(id, function (err, video) { db.Video.loadAndPopulateAuthorAndPodAndTags(id).then(video => {
if (err) {
logger.error('Error in video request validator.', { error: err })
return res.sendStatus(500)
}
if (!video) return res.status(404).send('Video not found') if (!video) return res.status(404).send('Video not found')
res.locals.video = video res.locals.video = video
callback() 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) { function checkUserCanDeleteVideo (userId: number, res: express.Response, callback: () => void) {
// Retrieve the user who did the request // Retrieve the user who did the request
db.User.loadById(userId, function (err, user) { db.User.loadById(userId)
if (err) { .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 }) logger.error('Error in video request validator.', { error: err })
return res.sendStatus(500) 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) { function checkVideoIsBlacklistable (req: express.Request, res: express.Response, callback: () => void) {

View File

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

View File

@ -2,7 +2,6 @@ import * as Sequelize from 'sequelize'
import { addMethodsToModel } from '../utils' import { addMethodsToModel } from '../utils'
import { import {
ApplicationClass,
ApplicationAttributes, ApplicationAttributes,
ApplicationInstance, ApplicationInstance,
@ -35,23 +34,19 @@ export default function defineApplication (sequelize: Sequelize.Sequelize, DataT
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
loadMigrationVersion = function (callback: ApplicationMethods.LoadMigrationVersionCallback) { loadMigrationVersion = function () {
const query = { const query = {
attributes: [ 'migrationVersion' ] attributes: [ 'migrationVersion' ]
} }
return Application.findOne(query).asCallback(function (err, data) { return Application.findOne(query).then(data => data ? data.migrationVersion : null)
const version = data ? data.migrationVersion : null
return callback(err, version)
})
} }
updateMigrationVersion = function (newVersion: number, transaction: Sequelize.Transaction, callback: ApplicationMethods.UpdateMigrationVersionCallback) { updateMigrationVersion = function (newVersion: number, transaction: Sequelize.Transaction) {
const options: Sequelize.UpdateOptions = { const options: Sequelize.UpdateOptions = {
where: {}, where: {},
transaction: transaction 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 Sequelize from 'sequelize'
import * as Promise from 'bluebird'
import { JobState } from '../../../shared/models/job.model' import { JobState } from '../../../shared/models/job.model'
export namespace JobMethods { export namespace JobMethods {
export type ListWithLimitCallback = (err: Error, jobInstances: JobInstance[]) => void export type ListWithLimit = (limit: number, state: JobState) => Promise<JobInstance[]>
export type ListWithLimit = (limit: number, state: JobState, callback: ListWithLimitCallback) => void
} }
export interface JobClass { export interface JobClass {

View File

@ -5,7 +5,6 @@ import { JOB_STATES } from '../../initializers'
import { addMethodsToModel } from '../utils' import { addMethodsToModel } from '../utils'
import { import {
JobClass,
JobInstance, JobInstance,
JobAttributes, 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 = { const query = {
order: [ order: [
[ 'id', 'ASC' ] [ '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 Sequelize from 'sequelize'
import * as Promise from 'bluebird'
export namespace OAuthClientMethods { export namespace OAuthClientMethods {
export type CountTotalCallback = (err: Error, total: number) => void export type CountTotal = () => Promise<number>
export type CountTotal = (callback: CountTotalCallback) => void
export type LoadFirstClientCallback = (err: Error, client: OAuthClientInstance) => void export type LoadFirstClient = () => Promise<OAuthClientInstance>
export type LoadFirstClient = (callback: LoadFirstClientCallback) => void
export type GetByIdAndSecret = (clientId, clientSecret) => void export type GetByIdAndSecret = (clientId: string, clientSecret: string) => Promise<OAuthClientInstance>
} }
export interface OAuthClientClass { export interface OAuthClientClass {

View File

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

View File

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

View File

@ -4,7 +4,6 @@ import { logger } from '../../helpers'
import { addMethodsToModel } from '../utils' import { addMethodsToModel } from '../utils'
import { import {
OAuthTokenClass,
OAuthTokenInstance, OAuthTokenInstance,
OAuthTokenAttributes, OAuthTokenAttributes,
@ -149,12 +148,12 @@ getByRefreshTokenAndPopulateUser = function (refreshToken: string) {
}) })
} }
removeByUserId = function (userId, callback) { removeByUserId = function (userId: number) {
const query = { const query = {
where: { where: {
userId: userId 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 Sequelize from 'sequelize'
import * as Promise from 'bluebird'
// Don't use barrel, import just what we need // Don't use barrel, import just what we need
import { Pod as FormatedPod } from '../../../shared/models/pod.model' 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 namespace PodMethods {
export type ToFormatedJSON = (this: PodInstance) => FormatedPod export type ToFormatedJSON = (this: PodInstance) => FormatedPod
export type CountAllCallback = (err: Error, total: number) => void export type CountAll = () => Promise<number>
export type CountAll = (callback) => void
export type IncrementScoresCallback = (err: Error) => void export type IncrementScores = (ids: number[], value: number) => Promise<[ number, PodInstance[] ]>
export type IncrementScores = (ids: number[], value: number, callback?: IncrementScoresCallback) => void
export type ListCallback = (err: Error, podInstances?: PodInstance[]) => void export type List = () => Promise<PodInstance[]>
export type List = (callback: ListCallback) => void
export type ListAllIdsCallback = (err: Error, ids?: number[]) => void export type ListAllIds = (transaction: Sequelize.Transaction) => Promise<number[]>
export type ListAllIds = (transaction: Sequelize.Transaction, callback: ListAllIdsCallback) => void
export type ListRandomPodIdsWithRequestCallback = (err: Error, podInstanceIds?: number[]) => void export type ListRandomPodIdsWithRequest = (limit: number, tableWithPods: string, tableWithPodsJoins: string) => Promise<number[]>
export type ListRandomPodIdsWithRequest = (limit: number, tableWithPods: string, tableWithPodsJoins: string, callback: ListRandomPodIdsWithRequestCallback) => void
export type ListBadPodsCallback = (err: Error, podInstances?: PodInstance[]) => void export type ListBadPods = () => Promise<PodInstance[]>
export type ListBadPods = (callback: ListBadPodsCallback) => void
export type LoadCallback = (err: Error, podInstance: PodInstance) => void export type Load = (id: number) => Promise<PodInstance>
export type Load = (id: number, callback: LoadCallback) => void
export type LoadByHostCallback = (err: Error, podInstance: PodInstance) => void export type LoadByHost = (host: string) => Promise<PodInstance>
export type LoadByHost = (host: string, callback: LoadByHostCallback) => void
export type RemoveAllCallback = (err: Error) => void export type RemoveAll = () => Promise<number>
export type RemoveAll = (callback: RemoveAllCallback) => void
export type UpdatePodsScore = (goodPods: number[], badPods: number[]) => void export type UpdatePodsScore = (goodPods: number[], badPods: number[]) => void
} }

View File

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

View File

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

View File

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

View File

@ -2,7 +2,6 @@ import * as Sequelize from 'sequelize'
import { addMethodsToModel } from '../utils' import { addMethodsToModel } from '../utils'
import { import {
RequestToPodClass,
RequestToPodInstance, RequestToPodInstance,
RequestToPodAttributes, RequestToPodAttributes,
@ -38,9 +37,7 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
removeByRequestIdsAndPod = function (requestsIds: number[], podId: number, callback?: RequestToPodMethods.RemoveByRequestIdsAndPodCallback) { removeByRequestIdsAndPod = function (requestsIds: number[], podId: number) {
if (!callback) callback = function () { /* empty */ }
const query = { const query = {
where: { where: {
requestId: { 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 Sequelize from 'sequelize'
import * as Promise from 'bluebird'
import { AbstractRequestClass, AbstractRequestToPodClass } from './abstract-request-interface'
import { VideoInstance } from '../video' import { VideoInstance } from '../video'
import { PodInstance } from '../pod' import { PodInstance } from '../pod'
@ -16,20 +18,16 @@ export type RequestsVideoEventGrouped = {
} }
export namespace RequestVideoEventMethods { export namespace RequestVideoEventMethods {
export type CountTotalRequestsCallback = (err: Error, total: number) => void export type CountTotalRequests = () => Promise<number>
export type CountTotalRequests = (callback: CountTotalRequestsCallback) => void
export type ListWithLimitAndRandomCallback = (err: Error, requestsGrouped?: RequestsVideoEventGrouped) => void export type ListWithLimitAndRandom = (limitPods: number, limitRequestsPerPod: number) => Promise<RequestsVideoEventGrouped>
export type ListWithLimitAndRandom = (limitPods: number, limitRequestsPerPod: number, callback: ListWithLimitAndRandomCallback) => void
export type RemoveByRequestIdsAndPodCallback = () => void export type RemoveByRequestIdsAndPod = (ids: number[], podId: number) => Promise<number>
export type RemoveByRequestIdsAndPod = (ids: number[], podId: number, callback: RemoveByRequestIdsAndPodCallback) => void
export type RemoveAllCallback = () => void export type RemoveAll = () => Promise<void>
export type RemoveAll = (callback: RemoveAllCallback) => void
} }
export interface RequestVideoEventClass { export interface RequestVideoEventClass extends AbstractRequestClass<RequestsVideoEventGrouped>, AbstractRequestToPodClass {
countTotalRequests: RequestVideoEventMethods.CountTotalRequests countTotalRequests: RequestVideoEventMethods.CountTotalRequests
listWithLimitAndRandom: RequestVideoEventMethods.ListWithLimitAndRandom listWithLimitAndRandom: RequestVideoEventMethods.ListWithLimitAndRandom
removeByRequestIdsAndPod: RequestVideoEventMethods.RemoveByRequestIdsAndPod removeByRequestIdsAndPod: RequestVideoEventMethods.RemoveByRequestIdsAndPod
@ -41,10 +39,12 @@ export interface RequestVideoEventAttributes {
count: number count: number
} }
export interface RequestVideoEventInstance extends RequestVideoEventClass, RequestVideoEventAttributes, Sequelize.Instance<RequestVideoEventAttributes> { export interface RequestVideoEventInstance
extends RequestVideoEventClass, RequestVideoEventAttributes, Sequelize.Instance<RequestVideoEventAttributes> {
id: number id: number
Video: VideoInstance 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 { isVideoEventCountValid } from '../../helpers'
import { addMethodsToModel } from '../utils' import { addMethodsToModel } from '../utils'
import { import {
RequestVideoEventClass,
RequestVideoEventInstance, RequestVideoEventInstance,
RequestVideoEventAttributes, RequestVideoEventAttributes,
@ -77,23 +76,21 @@ function associate (models) {
}) })
} }
countTotalRequests = function (callback: RequestVideoEventMethods.CountTotalRequestsCallback) { countTotalRequests = function () {
const query = {} 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 const Pod = db.Pod
// We make a join between videos and authors to find the podId of our video event requests // We make a join between videos and authors to find the podId of our video event requests
const podJoins = 'INNER JOIN "Videos" ON "Videos"."authorId" = "Authors"."id" ' + const podJoins = 'INNER JOIN "Videos" ON "Videos"."authorId" = "Authors"."id" ' +
'INNER JOIN "RequestVideoEvents" ON "RequestVideoEvents"."videoId" = "Videos"."id"' 'INNER JOIN "RequestVideoEvents" ON "RequestVideoEvents"."videoId" = "Videos"."id"'
Pod.listRandomPodIdsWithRequest(limitPods, 'Authors', podJoins, function (err, podIds) { return Pod.listRandomPodIdsWithRequest(limitPods, 'Authors', podJoins).then(podIds => {
if (err) return callback(err)
// We don't have friends that have requests // We don't have friends that have requests
if (podIds.length === 0) return callback(null, []) if (podIds.length === 0) return []
const query = { const query = {
order: [ order: [
@ -121,16 +118,14 @@ listWithLimitAndRandom = function (limitPods: number, limitRequestsPerPod: numbe
] ]
} }
RequestVideoEvent.findAll(query).asCallback(function (err, requests) { return RequestVideoEvent.findAll(query).then(requests => {
if (err) return callback(err)
const requestsGrouped = groupAndTruncateRequests(requests, limitRequestsPerPod) 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 = { const query = {
where: { where: {
id: { 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 // 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 Sequelize from 'sequelize'
import * as Promise from 'bluebird'
import { AbstractRequestClass, AbstractRequestToPodClass } from './abstract-request-interface'
import { VideoInstance } from '../video' import { VideoInstance } from '../video'
import { PodInstance } from '../pod' import { PodInstance } from '../pod'
@ -14,20 +16,16 @@ export type RequestsVideoQaduGrouped = {
} }
export namespace RequestVideoQaduMethods { export namespace RequestVideoQaduMethods {
export type CountTotalRequestsCallback = (err: Error, total: number) => void export type CountTotalRequests = () => Promise<number>
export type CountTotalRequests = (callback: CountTotalRequestsCallback) => void
export type ListWithLimitAndRandomCallback = (err: Error, requestsGrouped?: RequestsVideoQaduGrouped) => void export type ListWithLimitAndRandom = (limitPods: number, limitRequestsPerPod: number) => Promise<RequestsVideoQaduGrouped>
export type ListWithLimitAndRandom = (limitPods: number, limitRequestsPerPod: number, callback: ListWithLimitAndRandomCallback) => void
export type RemoveByRequestIdsAndPodCallback = () => void export type RemoveByRequestIdsAndPod = (ids: number[], podId: number) => Promise<number>
export type RemoveByRequestIdsAndPod = (ids: number[], podId: number, callback: RemoveByRequestIdsAndPodCallback) => void
export type RemoveAllCallback = () => void export type RemoveAll = () => Promise<void>
export type RemoveAll = (callback: RemoveAllCallback) => void
} }
export interface RequestVideoQaduClass { export interface RequestVideoQaduClass extends AbstractRequestClass<RequestsVideoQaduGrouped>, AbstractRequestToPodClass {
countTotalRequests: RequestVideoQaduMethods.CountTotalRequests countTotalRequests: RequestVideoQaduMethods.CountTotalRequests
listWithLimitAndRandom: RequestVideoQaduMethods.ListWithLimitAndRandom listWithLimitAndRandom: RequestVideoQaduMethods.ListWithLimitAndRandom
removeByRequestIdsAndPod: RequestVideoQaduMethods.RemoveByRequestIdsAndPod removeByRequestIdsAndPod: RequestVideoQaduMethods.RemoveByRequestIdsAndPod
@ -38,11 +36,13 @@ export interface RequestVideoQaduAttributes {
type: RequestVideoQaduType type: RequestVideoQaduType
} }
export interface RequestVideoQaduInstance extends RequestVideoQaduClass, RequestVideoQaduAttributes, Sequelize.Instance<RequestVideoQaduAttributes> { export interface RequestVideoQaduInstance
extends RequestVideoQaduClass, RequestVideoQaduAttributes, Sequelize.Instance<RequestVideoQaduAttributes> {
id: number id: number
Pod: PodInstance Pod: PodInstance
Video: VideoInstance 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 { REQUEST_VIDEO_QADU_TYPES } from '../../initializers'
import { addMethodsToModel } from '../utils' import { addMethodsToModel } from '../utils'
import { import {
RequestVideoQaduClass,
RequestVideoQaduInstance, RequestVideoQaduInstance,
RequestVideoQaduAttributes, RequestVideoQaduAttributes,
@ -83,20 +82,18 @@ function associate (models) {
}) })
} }
countTotalRequests = function (callback: RequestVideoQaduMethods.CountTotalRequestsCallback) { countTotalRequests = function () {
const query = {} 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 Pod = db.Pod
const tableJoin = '' const tableJoin = ''
Pod.listRandomPodIdsWithRequest(limitPods, 'RequestVideoQadus', tableJoin, function (err, podIds) { return Pod.listRandomPodIdsWithRequest(limitPods, 'RequestVideoQadus', tableJoin).then(podIds => {
if (err) return callback(err)
// We don't have friends that have requests // We don't have friends that have requests
if (podIds.length === 0) return callback(null, []) if (podIds.length === 0) return []
const query = { const query = {
include: [ include: [
@ -114,16 +111,14 @@ listWithLimitAndRandom = function (limitPods: number, limitRequestsPerPod: numbe
] ]
} }
RequestVideoQadu.findAll(query).asCallback(function (err, requests) { return RequestVideoQadu.findAll(query).then(requests => {
if (err) return callback(err)
const requestsGrouped = groupAndTruncateRequests(requests, limitRequestsPerPod) 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 = { const query = {
where: { where: {
id: { 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 // 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 { REQUEST_ENDPOINTS } from '../../initializers'
import { addMethodsToModel } from '../utils' import { addMethodsToModel } from '../utils'
import { import {
RequestClass,
RequestInstance, RequestInstance,
RequestAttributes, 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 // 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 // So we could count requests that do not have existing pod anymore
const query = { const query = {
include: [ Request['sequelize'].models.Pod ] 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 Pod = db.Pod
const tableJoin = '' const tableJoin = ''
Pod.listRandomPodIdsWithRequest(limitPods, 'RequestToPods', '', function (err, podIds) { return Pod.listRandomPodIdsWithRequest(limitPods, 'RequestToPods', tableJoin).then(podIds => {
if (err) return callback(err)
// We don't have friends that have requests // 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 // The first x requests of these pods
// It is very important to sort by id ASC to keep the requests order! // 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) { return Request.findAll(query).then(requests => {
if (err) return callback(err)
const requestsGrouped = groupAndTruncateRequests(requests, limitRequestsPerPod) const requestsGrouped = groupAndTruncateRequests(requests, limitRequestsPerPod)
return callback(err, requestsGrouped) return requestsGrouped
}) })
}) })
} }
removeAll = function (callback: RequestMethods.RemoveAllCallback) { removeAll = function () {
// Delete all requests // Delete all requests
Request.truncate({ cascade: true }).asCallback(callback) return Request.truncate({ cascade: true })
} }
removeWithEmptyTo = function (callback?: RequestMethods.RemoveWithEmptyToCallback) { removeWithEmptyTo = function () {
if (!callback) callback = function () { /* empty */ }
const query = { const query = {
where: { where: {
id: { 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 Sequelize from 'sequelize'
import * as Bluebird from 'bluebird' import * as Promise from 'bluebird'
// Don't use barrel, import just what we need // Don't use barrel, import just what we need
import { UserRole, User as FormatedUser } from '../../../shared/models/user.model' import { UserRole, User as FormatedUser } from '../../../shared/models/user.model'
import { ResultList } from '../../../shared/models/result-list.model'
export namespace UserMethods { export namespace UserMethods {
export type IsPasswordMatchCallback = (err: Error, same: boolean) => void export type IsPasswordMatch = (this: UserInstance, password: string) => Promise<boolean>
export type IsPasswordMatch = (this: UserInstance, password: string, callback: IsPasswordMatchCallback) => void
export type ToFormatedJSON = (this: UserInstance) => FormatedUser export type ToFormatedJSON = (this: UserInstance) => FormatedUser
export type IsAdmin = (this: UserInstance) => boolean export type IsAdmin = (this: UserInstance) => boolean
export type CountTotalCallback = (err: Error, total: number) => void export type CountTotal = () => Promise<number>
export type CountTotal = (callback: CountTotalCallback) => void
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 = () => Promise<UserInstance[]>
export type List = (callback: ListCallback) => void
export type ListForApiCallback = (err: Error, userInstances?: UserInstance[], total?: number) => void export type ListForApi = (start: number, count: number, sort: string) => Promise< ResultList<UserInstance> >
export type ListForApi = (start: number, count: number, sort: string, callback: ListForApiCallback) => void
export type LoadByIdCallback = (err: Error, userInstance: UserInstance) => void export type LoadById = (id: number) => Promise<UserInstance>
export type LoadById = (id: number, callback: LoadByIdCallback) => void
export type LoadByUsernameCallback = (err: Error, userInstance: UserInstance) => void export type LoadByUsername = (username: string) => Promise<UserInstance>
export type LoadByUsername = (username: string, callback: LoadByUsernameCallback) => void
export type LoadByUsernameOrEmailCallback = (err: Error, userInstance: UserInstance) => void export type LoadByUsernameOrEmail = (username: string, email: string) => Promise<UserInstance>
export type LoadByUsernameOrEmail = (username: string, email: string, callback: LoadByUsernameOrEmailCallback) => void
} }
export interface UserClass { export interface UserClass {

View File

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

View File

@ -8,7 +8,6 @@ import { VIDEO_RATE_TYPES } from '../../initializers'
import { addMethodsToModel } from '../utils' import { addMethodsToModel } from '../utils'
import { import {
UserVideoRateClass,
UserVideoRateInstance, UserVideoRateInstance,
UserVideoRateAttributes, 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 = { const options: Sequelize.FindOptions = {
where: { where: {
userId, userId,
@ -75,5 +74,5 @@ load = function (userId: number, videoId: string, transaction: Sequelize.Transac
} }
if (transaction) options.transaction = transaction 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 { addMethodsToModel } from '../utils'
import { import {
UserClass,
UserInstance, UserInstance,
UserAttributes, UserAttributes,
@ -118,21 +117,16 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
} }
function beforeCreateOrUpdate (user: UserInstance) { function beforeCreateOrUpdate (user: UserInstance) {
return new Promise(function (resolve, reject) { return cryptPassword(user.password).then(hash => {
cryptPassword(user.password, function (err, hash) { user.password = hash
if (err) return reject(err) return undefined
user.password = hash
return resolve()
})
}) })
} }
// ------------------------------ METHODS ------------------------------ // ------------------------------ METHODS ------------------------------
isPasswordMatch = function (this: UserInstance, password: string, callback: UserMethods.IsPasswordMatchCallback) { isPasswordMatch = function (this: UserInstance, password: string) {
return comparePassword(password, this.password, callback) return comparePassword(password, this.password)
} }
toFormatedJSON = function (this: UserInstance) { toFormatedJSON = function (this: UserInstance) {
@ -164,8 +158,8 @@ function associate (models) {
}) })
} }
countTotal = function (callback: UserMethods.CountTotalCallback) { countTotal = function () {
return this.count().asCallback(callback) return this.count()
} }
getByUsername = function (username: string) { getByUsername = function (username: string) {
@ -178,44 +172,45 @@ getByUsername = function (username: string) {
return User.findOne(query) return User.findOne(query)
} }
list = function (callback: UserMethods.ListCallback) { list = function () {
return User.find().asCallback(callback) return User.findAll()
} }
listForApi = function (start: number, count: number, sort: string, callback: UserMethods.ListForApiCallback) { listForApi = function (start: number, count: number, sort: string) {
const query = { const query = {
offset: start, offset: start,
limit: count, limit: count,
order: [ getSort(sort) ] order: [ getSort(sort) ]
} }
return User.findAndCountAll(query).asCallback(function (err, result) { return User.findAndCountAll(query).then(({ rows, count }) => {
if (err) return callback(err) return {
data: rows,
return callback(null, result.rows, result.count) total: count
}
}) })
} }
loadById = function (id: number, callback: UserMethods.LoadByIdCallback) { loadById = function (id: number) {
return User.findById(id).asCallback(callback) return User.findById(id)
} }
loadByUsername = function (username: string, callback: UserMethods.LoadByUsernameCallback) { loadByUsername = function (username: string) {
const query = { const query = {
where: { where: {
username: username 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 = { const query = {
where: { where: {
$or: [ { username }, { email } ] $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 Sequelize from 'sequelize'
import * as Promise from 'bluebird'
import { PodInstance } from '../pod' import { PodInstance } from '../pod'
export namespace AuthorMethods { export namespace AuthorMethods {
export type FindOrCreateAuthorCallback = (err: Error, authorInstance?: AuthorInstance) => void export type FindOrCreateAuthor = (
export type FindOrCreateAuthor = (name: string, podId: number, userId: number, transaction: Sequelize.Transaction, callback: FindOrCreateAuthorCallback) => void name: string,
podId: number,
userId: number,
transaction: Sequelize.Transaction
) => Promise<AuthorInstance>
} }
export interface AuthorClass { export interface AuthorClass {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,7 @@
import * as Sequelize from 'sequelize' import * as Sequelize from 'sequelize'
import * as Promise from 'bluebird'
import { ResultList } from '../../../shared'
// Don't use barrel, import just what we need // Don't use barrel, import just what we need
import { BlacklistedVideo as FormatedBlacklistedVideo } from '../../../shared/models/video-blacklist.model' 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 namespace BlacklistedVideoMethods {
export type ToFormatedJSON = (this: BlacklistedVideoInstance) => FormatedBlacklistedVideo export type ToFormatedJSON = (this: BlacklistedVideoInstance) => FormatedBlacklistedVideo
export type CountTotalCallback = (err: Error, total: number) => void export type CountTotal = () => Promise<number>
export type CountTotal = (callback: CountTotalCallback) => void
export type ListCallback = (err: Error, backlistedVideoInstances: BlacklistedVideoInstance[]) => void export type List = () => Promise<BlacklistedVideoInstance[]>
export type List = (callback: ListCallback) => void
export type ListForApiCallback = (err: Error, blacklistedVIdeoInstances?: BlacklistedVideoInstance[], total?: number) => void export type ListForApi = (start: number, count: number, sort: string) => Promise< ResultList<BlacklistedVideoInstance> >
export type ListForApi = (start: number, count: number, sort: string, callback: ListForApiCallback) => void
export type LoadByIdCallback = (err: Error, blacklistedVideoInstance: BlacklistedVideoInstance) => void export type LoadById = (id: number) => Promise<BlacklistedVideoInstance>
export type LoadById = (id: number, callback: LoadByIdCallback) => void
export type LoadByVideoIdCallback = (err: Error, blacklistedVideoInstance: BlacklistedVideoInstance) => void export type LoadByVideoId = (id: string) => Promise<BlacklistedVideoInstance>
export type LoadByVideoId = (id: string, callback: LoadByVideoIdCallback) => void
} }
export interface BlacklistedVideoClass { export interface BlacklistedVideoClass {
@ -35,7 +33,8 @@ export interface BlacklistedVideoAttributes {
videoId: string videoId: string
} }
export interface BlacklistedVideoInstance extends BlacklistedVideoClass, BlacklistedVideoAttributes, Sequelize.Instance<BlacklistedVideoAttributes> { export interface BlacklistedVideoInstance
extends BlacklistedVideoClass, BlacklistedVideoAttributes, Sequelize.Instance<BlacklistedVideoAttributes> {
id: number id: number
createdAt: Date createdAt: Date
updatedAt: Date updatedAt: Date
@ -43,4 +42,5 @@ export interface BlacklistedVideoInstance extends BlacklistedVideoClass, Blackli
toFormatedJSON: BlacklistedVideoMethods.ToFormatedJSON 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 { addMethodsToModel, getSort } from '../utils'
import { import {
BlacklistedVideoClass,
BlacklistedVideoInstance, BlacklistedVideoInstance,
BlacklistedVideoAttributes, BlacklistedVideoAttributes,
@ -66,38 +65,39 @@ function associate (models) {
}) })
} }
countTotal = function (callback: BlacklistedVideoMethods.CountTotalCallback) { countTotal = function () {
return BlacklistedVideo.count().asCallback(callback) return BlacklistedVideo.count()
} }
list = function (callback: BlacklistedVideoMethods.ListCallback) { list = function () {
return BlacklistedVideo.findAll().asCallback(callback) return BlacklistedVideo.findAll()
} }
listForApi = function (start: number, count: number, sort: string, callback: BlacklistedVideoMethods.ListForApiCallback) { listForApi = function (start: number, count: number, sort: string) {
const query = { const query = {
offset: start, offset: start,
limit: count, limit: count,
order: [ getSort(sort) ] order: [ getSort(sort) ]
} }
return BlacklistedVideo.findAndCountAll(query).asCallback(function (err, result) { return BlacklistedVideo.findAndCountAll(query).then(({ rows, count }) => {
if (err) return callback(err) return {
data: rows,
return callback(null, result.rows, result.count) total: count
}
}) })
} }
loadById = function (id: number, callback: BlacklistedVideoMethods.LoadByIdCallback) { loadById = function (id: number) {
return BlacklistedVideo.findById(id).asCallback(callback) return BlacklistedVideo.findById(id)
} }
loadByVideoId = function (id: string, callback: BlacklistedVideoMethods.LoadByIdCallback) { loadByVideoId = function (id: string) {
const query = { const query = {
where: { where: {
videoId: id 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 Sequelize from 'sequelize'
import * as Promise from 'bluebird'
import { AuthorInstance } from './author-interface' 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 // Don't use barrel, import just what we need
import { Video as FormatedVideo } from '../../../shared/models/video.model' import { Video as FormatedVideo } from '../../../shared/models/video.model'
import { ResultList } from '../../../shared/models/result-list.model'
export type FormatedAddRemoteVideo = { export type FormatedAddRemoteVideo = {
name: string name: string
@ -56,46 +58,32 @@ export namespace VideoMethods {
export type IsOwned = (this: VideoInstance) => boolean export type IsOwned = (this: VideoInstance) => boolean
export type ToFormatedJSON = (this: VideoInstance) => FormatedVideo export type ToFormatedJSON = (this: VideoInstance) => FormatedVideo
export type ToAddRemoteJSONCallback = (err: Error, videoFormated?: FormatedAddRemoteVideo) => void export type ToAddRemoteJSON = (this: VideoInstance) => Promise<FormatedAddRemoteVideo>
export type ToAddRemoteJSON = (this: VideoInstance, callback: ToAddRemoteJSONCallback) => void
export type ToUpdateRemoteJSON = (this: VideoInstance) => FormatedUpdateRemoteVideo export type ToUpdateRemoteJSON = (this: VideoInstance) => FormatedUpdateRemoteVideo
export type TranscodeVideofileCallback = (err: Error) => void export type TranscodeVideofile = (this: VideoInstance) => Promise<void>
export type TranscodeVideofile = (this: VideoInstance, callback: TranscodeVideofileCallback) => void
export type GenerateThumbnailFromDataCallback = (err: Error, thumbnailName?: string) => void // Return thumbnail name
export type GenerateThumbnailFromData = (video: VideoInstance, thumbnailData: string, callback: GenerateThumbnailFromDataCallback) => void 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 List = () => Promise<VideoInstance[]>
export type GetDurationFromFile = (videoPath, callback) => void export type ListOwnedAndPopulateAuthorAndTags = () => Promise<VideoInstance[]>
export type ListOwnedByAuthor = (author: string) => Promise<VideoInstance[]>
export type ListCallback = (err: Error, videoInstances: VideoInstance[]) => void export type ListForApi = (start: number, count: number, sort: string) => Promise< ResultList<VideoInstance> >
export type List = (callback: ListCallback) => void 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 Load = (id: string) => Promise<VideoInstance>
export type ListForApi = (start: number, count: number, sort: string, callback: ListForApiCallback) => void export type LoadByHostAndRemoteId = (fromHost: string, remoteId: string) => Promise<VideoInstance>
export type LoadAndPopulateAuthor = (id: string) => Promise<VideoInstance>
export type LoadByHostAndRemoteIdCallback = (err: Error, videoInstance: VideoInstance) => void export type LoadAndPopulateAuthorAndPodAndTags = (id: string) => Promise<VideoInstance>
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 interface VideoClass { export interface VideoClass {
@ -139,7 +127,7 @@ export interface VideoAttributes {
dislikes?: number dislikes?: number
Author?: AuthorInstance Author?: AuthorInstance
Tags?: VideoTagInstance[] Tags?: TagInstance[]
} }
export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.Instance<VideoAttributes> { export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.Instance<VideoAttributes> {
@ -157,6 +145,8 @@ export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.In
toAddRemoteJSON: VideoMethods.ToAddRemoteJSON toAddRemoteJSON: VideoMethods.ToAddRemoteJSON
toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON
transcodeVideofile: VideoMethods.TranscodeVideofile transcodeVideofile: VideoMethods.TranscodeVideofile
setTags: Sequelize.HasManySetAssociationsMixin<TagAttributes, string>
} }
export interface VideoModel extends VideoClass, Sequelize.Model<VideoInstance, VideoAttributes> {} export interface VideoModel extends VideoClass, Sequelize.Model<VideoInstance, VideoAttributes> {}

View File

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

View File

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

View File

@ -171,6 +171,23 @@ describe('Test advanced friends', function () {
function (next) { function (next) {
setTimeout(next, 22000) 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 // Rerun server 4
function (next) { function (next) {
serversUtils.runServer(4, function (server) { serversUtils.runServer(4, function (server) {
@ -187,7 +204,7 @@ describe('Test advanced friends', function () {
next() next()
}) })
}, },
// Pod 6 ask pod 1, 2 and 3 // Pod 6 asks pod 1, 2 and 3
function (next) { function (next) {
makeFriends(6, next) makeFriends(6, next)
}, },

View File

@ -1,6 +1,7 @@
export * from './job.model' export * from './job.model'
export * from './oauth-client-local.model' export * from './oauth-client-local.model'
export * from './pod.model' export * from './pod.model'
export * from './result-list.model'
export * from './request-scheduler.model' export * from './request-scheduler.model'
export * from './user-video-rate.model' export * from './user-video-rate.model'
export * from './user.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: dependencies:
"@types/express" "*" "@types/express" "*"
"@types/node@*", "@types/node@^7.0.18": "@types/node@*", "@types/node@^8.0.3":
version "7.0.32" version "8.0.3"
resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.32.tgz#6afe6c66520a4c316623a14aef123908d01b4bba" resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.3.tgz#fca61c26f83e5f453166114f57d53a47feb36d45"
"@types/request@^0.0.44": "@types/request@^0.0.44":
version "0.0.44" version "0.0.44"
@ -453,7 +453,7 @@ bluebird@^2.10.0, bluebird@^2.9.13:
version "2.11.0" version "2.11.0"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1" 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" version "3.5.0"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c"
@ -3909,9 +3909,9 @@ typedarray@^0.0.6:
version "0.0.6" version "0.0.6"
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
typescript@^2.3.4: typescript@^2.4.1:
version "2.4.0" version "2.4.1"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.4.0.tgz#aef5a8d404beba36ad339abf079ddddfffba86dd" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.4.1.tgz#c3ccb16ddaa0b2314de031e7e6fee89e5ba346bc"
uid-number@^0.0.6, uid-number@~0.0.6: uid-number@^0.0.6, uid-number@~0.0.6:
version "0.0.6" version "0.0.6"