diff options
88 files changed, 1995 insertions, 2520 deletions
diff --git a/package.json b/package.json index 93193fe2f..b3ea40ad3 100644 --- a/package.json +++ b/package.json | |||
@@ -49,6 +49,7 @@ | |||
49 | "async": "^2.0.0", | 49 | "async": "^2.0.0", |
50 | "bcrypt": "^1.0.2", | 50 | "bcrypt": "^1.0.2", |
51 | "bittorrent-tracker": "^9.0.0", | 51 | "bittorrent-tracker": "^9.0.0", |
52 | "bluebird": "^3.5.0", | ||
52 | "body-parser": "^1.12.4", | 53 | "body-parser": "^1.12.4", |
53 | "concurrently": "^3.1.0", | 54 | "concurrently": "^3.1.0", |
54 | "config": "^1.14.0", | 55 | "config": "^1.14.0", |
@@ -78,7 +79,7 @@ | |||
78 | "scripty": "^1.5.0", | 79 | "scripty": "^1.5.0", |
79 | "sequelize": "4.0.0-2", | 80 | "sequelize": "4.0.0-2", |
80 | "ts-node": "^3.0.6", | 81 | "ts-node": "^3.0.6", |
81 | "typescript": "^2.3.4", | 82 | "typescript": "^2.4.1", |
82 | "validator": "^7.0.0", | 83 | "validator": "^7.0.0", |
83 | "winston": "^2.1.1", | 84 | "winston": "^2.1.1", |
84 | "ws": "^2.0.0" | 85 | "ws": "^2.0.0" |
@@ -95,7 +96,7 @@ | |||
95 | "@types/mkdirp": "^0.3.29", | 96 | "@types/mkdirp": "^0.3.29", |
96 | "@types/morgan": "^1.7.32", | 97 | "@types/morgan": "^1.7.32", |
97 | "@types/multer": "^0.0.34", | 98 | "@types/multer": "^0.0.34", |
98 | "@types/node": "^7.0.18", | 99 | "@types/node": "^8.0.3", |
99 | "@types/request": "^0.0.44", | 100 | "@types/request": "^0.0.44", |
100 | "@types/sequelize": "^4.0.55", | 101 | "@types/sequelize": "^4.0.55", |
101 | "@types/validator": "^6.2.0", | 102 | "@types/validator": "^6.2.0", |
diff --git a/scripts/danger/clean/cleaner.ts b/scripts/danger/clean/cleaner.ts index d1dba4650..d80b8d323 100644 --- a/scripts/danger/clean/cleaner.ts +++ b/scripts/danger/clean/cleaner.ts | |||
@@ -1,25 +1,28 @@ | |||
1 | import * as eachSeries from 'async/eachSeries' | ||
2 | import * as rimraf from 'rimraf' | 1 | import * as rimraf from 'rimraf' |
2 | import * as Promise from 'bluebird' | ||
3 | 3 | ||
4 | import { CONFIG } from '../../../server/initializers/constants' | 4 | import { CONFIG } from '../../../server/initializers/constants' |
5 | import { database as db } from '../../../server/initializers/database' | 5 | import { database as db } from '../../../server/initializers/database' |
6 | 6 | ||
7 | db.init(true, function () { | 7 | db.init(true) |
8 | db.sequelize.drop().asCallback(function (err) { | 8 | .then(() => { |
9 | if (err) throw err | 9 | return db.sequelize.drop() |
10 | 10 | }) | |
11 | .then(() => { | ||
11 | console.info('Tables of %s deleted.', CONFIG.DATABASE.DBNAME) | 12 | console.info('Tables of %s deleted.', CONFIG.DATABASE.DBNAME) |
12 | 13 | ||
13 | const STORAGE = CONFIG.STORAGE | 14 | const STORAGE = CONFIG.STORAGE |
14 | eachSeries(Object.keys(STORAGE), function (storage, callbackEach) { | 15 | Promise.mapSeries(Object.keys(STORAGE), storage => { |
15 | const storageDir = STORAGE[storage] | 16 | const storageDir = STORAGE[storage] |
16 | 17 | ||
17 | rimraf(storageDir, function (err) { | 18 | return new Promise((res, rej) => { |
18 | console.info('%s deleted.', storageDir) | 19 | rimraf(storageDir, function (err) { |
19 | return callbackEach(err) | 20 | if (err) return rej(err) |
21 | |||
22 | console.info('%s deleted.', storageDir) | ||
23 | return res() | ||
24 | }) | ||
20 | }) | 25 | }) |
21 | }, function () { | ||
22 | process.exit(0) | ||
23 | }) | 26 | }) |
27 | .then(() => process.exit(0)) | ||
24 | }) | 28 | }) |
25 | }) | ||
diff --git a/scripts/reset-password.ts b/scripts/reset-password.ts index 50e11c69c..09f27bfa4 100755 --- a/scripts/reset-password.ts +++ b/scripts/reset-password.ts | |||
@@ -11,13 +11,11 @@ if (program.user === undefined) { | |||
11 | process.exit(-1) | 11 | process.exit(-1) |
12 | } | 12 | } |
13 | 13 | ||
14 | db.init(true, function () { | 14 | db.init(true) |
15 | db.User.loadByUsername(program.user, function (err, user) { | 15 | .then(() => { |
16 | if (err) { | 16 | return db.User.loadByUsername(program.user) |
17 | console.error(err) | 17 | }) |
18 | return | 18 | .then(user => { |
19 | } | ||
20 | |||
21 | if (!user) { | 19 | if (!user) { |
22 | console.error('User unknown.') | 20 | console.error('User unknown.') |
23 | return | 21 | return |
@@ -40,15 +38,9 @@ db.init(true, function () { | |||
40 | rl.on('line', function (password) { | 38 | rl.on('line', function (password) { |
41 | user.password = password | 39 | user.password = password |
42 | 40 | ||
43 | user.save().asCallback(function (err) { | 41 | user.save() |
44 | if (err) { | 42 | .then(() => console.log('User password updated.')) |
45 | console.error(err) | 43 | .catch(err => console.error(err)) |
46 | } else { | 44 | .finally(() => process.exit(0)) |
47 | console.log('User password updated.') | ||
48 | } | ||
49 | |||
50 | process.exit(0) | ||
51 | }) | ||
52 | }) | 45 | }) |
53 | }) | 46 | }) |
54 | }) | ||
diff --git a/scripts/test.sh b/scripts/test.sh index 6e8e38659..6c6312d52 100755 --- a/scripts/test.sh +++ b/scripts/test.sh | |||
@@ -1,4 +1,4 @@ | |||
1 | #!/usr/bin/env sh | 1 | #!/bin/bash |
2 | 2 | ||
3 | npm run build:server | 3 | npm run build:server |
4 | 4 | ||
@@ -6,5 +6,5 @@ cd client || exit -1 | |||
6 | npm test || exit -1 | 6 | npm test || exit -1 |
7 | 7 | ||
8 | cd .. || exit -1 | 8 | cd .. || exit -1 |
9 | npm run tslint -- --type-check --project ./tsconfig.json -c ./tslint.json server.ts server/**/*.ts || exit -1 | 9 | npm run tslint -- --type-check --project ./tsconfig.json -c ./tslint.json server.ts "server/**/*.ts" || exit -1 |
10 | mocha --bail server/tests | 10 | mocha --bail server/tests |
diff --git a/scripts/update-host.ts b/scripts/update-host.ts index 0f6af0942..23e8d5ef3 100755 --- a/scripts/update-host.ts +++ b/scripts/update-host.ts | |||
@@ -5,33 +5,32 @@ import { CONFIG, STATIC_PATHS } from '../server/initializers/constants' | |||
5 | import { database as db } from '../server/initializers/database' | 5 | import { database as db } from '../server/initializers/database' |
6 | import { hasFriends } from '../server/lib/friends' | 6 | import { hasFriends } from '../server/lib/friends' |
7 | 7 | ||
8 | db.init(true, function () { | 8 | db.init(true) |
9 | hasFriends(function (err, itHasFriends) { | 9 | .then(() => { |
10 | if (err) throw err | 10 | return hasFriends() |
11 | 11 | }) | |
12 | .then(itHasFriends => { | ||
12 | if (itHasFriends === true) { | 13 | if (itHasFriends === true) { |
13 | console.log('Cannot update host because you have friends!') | 14 | console.log('Cannot update host because you have friends!') |
14 | process.exit(-1) | 15 | process.exit(-1) |
15 | } | 16 | } |
16 | 17 | ||
17 | console.log('Updating torrent files.') | 18 | console.log('Updating torrent files.') |
18 | db.Video.list(function (err, videos) { | 19 | return db.Video.list() |
19 | if (err) throw err | 20 | }) |
20 | 21 | .then(videos => { | |
21 | videos.forEach(function (video) { | 22 | videos.forEach(function (video) { |
22 | const torrentName = video.id + '.torrent' | 23 | const torrentName = video.id + '.torrent' |
23 | const torrentPath = CONFIG.STORAGE.TORRENTS_DIR + torrentName | 24 | const torrentPath = CONFIG.STORAGE.TORRENTS_DIR + torrentName |
24 | const filename = video.id + video.extname | 25 | const filename = video.id + video.extname |
25 | 26 | ||
26 | const parsed = parseTorrent(readFileSync(torrentPath)) | 27 | const parsed = parseTorrent(readFileSync(torrentPath)) |
27 | parsed.announce = [ CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOST + '/tracker/socket' ] | 28 | parsed.announce = [ CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOST + '/tracker/socket' ] |
28 | parsed.urlList = [ CONFIG.WEBSERVER.URL + STATIC_PATHS.WEBSEED + filename ] | 29 | parsed.urlList = [ CONFIG.WEBSERVER.URL + STATIC_PATHS.WEBSEED + filename ] |
29 | 30 | ||
30 | const buf = parseTorrent.toTorrentFile(parsed) | 31 | const buf = parseTorrent.toTorrentFile(parsed) |
31 | writeFileSync(torrentPath, buf) | 32 | writeFileSync(torrentPath, buf) |
32 | }) | ||
33 | |||
34 | process.exit(0) | ||
35 | }) | 33 | }) |
34 | |||
35 | process.exit(0) | ||
36 | }) | 36 | }) |
37 | }) | ||
@@ -29,7 +29,7 @@ import { logger } from './server/helpers/logger' | |||
29 | import { API_VERSION, CONFIG } from './server/initializers/constants' | 29 | import { API_VERSION, CONFIG } from './server/initializers/constants' |
30 | // Initialize database and models | 30 | // Initialize database and models |
31 | import { database as db } from './server/initializers/database' | 31 | import { database as db } from './server/initializers/database' |
32 | db.init(false, onDatabaseInitDone) | 32 | db.init(false).then(() => onDatabaseInitDone()) |
33 | 33 | ||
34 | // ----------- Checker ----------- | 34 | // ----------- Checker ----------- |
35 | import { checkMissedConfig, checkFFmpeg, checkConfig } from './server/initializers/checker' | 35 | import { checkMissedConfig, checkFFmpeg, checkConfig } from './server/initializers/checker' |
@@ -38,11 +38,7 @@ const missed = checkMissedConfig() | |||
38 | if (missed.length !== 0) { | 38 | if (missed.length !== 0) { |
39 | throw new Error('Miss some configurations keys : ' + missed) | 39 | throw new Error('Miss some configurations keys : ' + missed) |
40 | } | 40 | } |
41 | checkFFmpeg(function (err) { | 41 | checkFFmpeg() |
42 | if (err) { | ||
43 | throw err | ||
44 | } | ||
45 | }) | ||
46 | 42 | ||
47 | const errorMessage = checkConfig() | 43 | const errorMessage = checkConfig() |
48 | if (errorMessage !== null) { | 44 | if (errorMessage !== null) { |
@@ -138,12 +134,11 @@ app.use(function (err, req, res, next) { | |||
138 | function onDatabaseInitDone () { | 134 | function onDatabaseInitDone () { |
139 | const port = CONFIG.LISTEN.PORT | 135 | const port = CONFIG.LISTEN.PORT |
140 | // Run the migration scripts if needed | 136 | // Run the migration scripts if needed |
141 | migrate(function (err) { | 137 | migrate() |
142 | if (err) throw err | 138 | .then(() => { |
143 | 139 | return installApplication() | |
144 | installApplication(function (err) { | 140 | }) |
145 | if (err) throw err | 141 | .then(() => { |
146 | |||
147 | // ----------- Make the server listening ----------- | 142 | // ----------- Make the server listening ----------- |
148 | server.listen(port, function () { | 143 | server.listen(port, function () { |
149 | // Activate the communication with friends | 144 | // Activate the communication with friends |
@@ -156,5 +151,4 @@ function onDatabaseInitDone () { | |||
156 | logger.info('Webserver: %s', CONFIG.WEBSERVER.URL) | 151 | logger.info('Webserver: %s', CONFIG.WEBSERVER.URL) |
157 | }) | 152 | }) |
158 | }) | 153 | }) |
159 | }) | ||
160 | } | 154 | } |
diff --git a/server/controllers/api/oauth-clients.ts b/server/controllers/api/oauth-clients.ts index b9bc0534f..f7dac598c 100644 --- a/server/controllers/api/oauth-clients.ts +++ b/server/controllers/api/oauth-clients.ts | |||
@@ -24,16 +24,17 @@ function getLocalClient (req: express.Request, res: express.Response, next: expr | |||
24 | return res.type('json').status(403).end() | 24 | return res.type('json').status(403).end() |
25 | } | 25 | } |
26 | 26 | ||
27 | db.OAuthClient.loadFirstClient(function (err, client) { | 27 | db.OAuthClient.loadFirstClient() |
28 | if (err) return next(err) | 28 | .then(client => { |
29 | if (!client) return next(new Error('No client available.')) | 29 | if (!client) throw new Error('No client available.') |
30 | 30 | ||
31 | const json: OAuthClientLocal = { | 31 | const json: OAuthClientLocal = { |
32 | client_id: client.clientId, | 32 | client_id: client.clientId, |
33 | client_secret: client.clientSecret | 33 | client_secret: client.clientSecret |
34 | } | 34 | } |
35 | res.json(json) | 35 | res.json(json) |
36 | }) | 36 | }) |
37 | .catch(err => next(err)) | ||
37 | } | 38 | } |
38 | 39 | ||
39 | // --------------------------------------------------------------------------- | 40 | // --------------------------------------------------------------------------- |
diff --git a/server/controllers/api/pods.ts b/server/controllers/api/pods.ts index 283105f6c..0f85ab51d 100644 --- a/server/controllers/api/pods.ts +++ b/server/controllers/api/pods.ts | |||
@@ -1,5 +1,4 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { waterfall } from 'async' | ||
3 | 2 | ||
4 | import { database as db } from '../../initializers/database' | 3 | import { database as db } from '../../initializers/database' |
5 | import { CONFIG } from '../../initializers' | 4 | import { CONFIG } from '../../initializers' |
@@ -57,65 +56,39 @@ export { | |||
57 | function addPods (req: express.Request, res: express.Response, next: express.NextFunction) { | 56 | function addPods (req: express.Request, res: express.Response, next: express.NextFunction) { |
58 | const informations = req.body | 57 | const informations = req.body |
59 | 58 | ||
60 | waterfall<string, Error>([ | 59 | const pod = db.Pod.build(informations) |
61 | function addPod (callback) { | 60 | pod.save() |
62 | const pod = db.Pod.build(informations) | 61 | .then(podCreated => { |
63 | pod.save().asCallback(function (err, podCreated) { | 62 | return sendOwnedVideosToPod(podCreated.id) |
64 | // Be sure about the number of parameters for the callback | 63 | }) |
65 | return callback(err, podCreated) | 64 | .then(() => { |
66 | }) | 65 | return getMyPublicCert() |
67 | }, | 66 | }) |
68 | 67 | .then(cert => { | |
69 | function sendMyVideos (podCreated: PodInstance, callback) { | 68 | return res.json({ cert: cert, email: CONFIG.ADMIN.EMAIL }) |
70 | sendOwnedVideosToPod(podCreated.id) | 69 | }) |
71 | 70 | .catch(err => next(err)) | |
72 | callback(null) | ||
73 | }, | ||
74 | |||
75 | function fetchMyCertificate (callback) { | ||
76 | getMyPublicCert(function (err, cert) { | ||
77 | if (err) { | ||
78 | logger.error('Cannot read cert file.') | ||
79 | return callback(err) | ||
80 | } | ||
81 | |||
82 | return callback(null, cert) | ||
83 | }) | ||
84 | } | ||
85 | ], function (err, cert) { | ||
86 | if (err) return next(err) | ||
87 | |||
88 | return res.json({ cert: cert, email: CONFIG.ADMIN.EMAIL }) | ||
89 | }) | ||
90 | } | 71 | } |
91 | 72 | ||
92 | function listPods (req: express.Request, res: express.Response, next: express.NextFunction) { | 73 | function listPods (req: express.Request, res: express.Response, next: express.NextFunction) { |
93 | db.Pod.list(function (err, podsList) { | 74 | db.Pod.list() |
94 | if (err) return next(err) | 75 | .then(podsList => res.json(getFormatedObjects(podsList, podsList.length))) |
95 | 76 | .catch(err => next(err)) | |
96 | res.json(getFormatedObjects(podsList, podsList.length)) | ||
97 | }) | ||
98 | } | 77 | } |
99 | 78 | ||
100 | function makeFriendsController (req: express.Request, res: express.Response, next: express.NextFunction) { | 79 | function makeFriendsController (req: express.Request, res: express.Response, next: express.NextFunction) { |
101 | const hosts = req.body.hosts as string[] | 80 | const hosts = req.body.hosts as string[] |
102 | 81 | ||
103 | makeFriends(hosts, function (err) { | 82 | makeFriends(hosts) |
104 | if (err) { | 83 | .then(() => logger.info('Made friends!')) |
105 | logger.error('Could not make friends.', { error: err }) | 84 | .catch(err => logger.error('Could not make friends.', { error: err })) |
106 | return | ||
107 | } | ||
108 | |||
109 | logger.info('Made friends!') | ||
110 | }) | ||
111 | 85 | ||
86 | // Don't wait the process that could be long | ||
112 | res.type('json').status(204).end() | 87 | res.type('json').status(204).end() |
113 | } | 88 | } |
114 | 89 | ||
115 | function quitFriendsController (req: express.Request, res: express.Response, next: express.NextFunction) { | 90 | function quitFriendsController (req: express.Request, res: express.Response, next: express.NextFunction) { |
116 | quitFriends(function (err) { | 91 | quitFriends() |
117 | if (err) return next(err) | 92 | .then(() => res.type('json').status(204).end()) |
118 | 93 | .catch(err => next(err)) | |
119 | res.type('json').status(204).end() | ||
120 | }) | ||
121 | } | 94 | } |
diff --git a/server/controllers/api/remote/pods.ts b/server/controllers/api/remote/pods.ts index b0d6642c1..6319957d3 100644 --- a/server/controllers/api/remote/pods.ts +++ b/server/controllers/api/remote/pods.ts | |||
@@ -1,5 +1,4 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import * as waterfall from 'async/waterfall' | ||
3 | 2 | ||
4 | import { database as db } from '../../../initializers/database' | 3 | import { database as db } from '../../../initializers/database' |
5 | import { checkSignature, signatureValidator } from '../../../middlewares' | 4 | import { checkSignature, signatureValidator } from '../../../middlewares' |
@@ -24,17 +23,10 @@ export { | |||
24 | function removePods (req: express.Request, res: express.Response, next: express.NextFunction) { | 23 | function removePods (req: express.Request, res: express.Response, next: express.NextFunction) { |
25 | const host = req.body.signature.host | 24 | const host = req.body.signature.host |
26 | 25 | ||
27 | waterfall([ | 26 | db.Pod.loadByHost(host) |
28 | function loadPod (callback) { | 27 | .then(pod => { |
29 | db.Pod.loadByHost(host, callback) | 28 | return pod.destroy() |
30 | }, | 29 | }) |
31 | 30 | .then(() => res.type('json').status(204).end()) | |
32 | function deletePod (pod, callback) { | 31 | .catch(err => next(err)) |
33 | pod.destroy().asCallback(callback) | ||
34 | } | ||
35 | ], function (err) { | ||
36 | if (err) return next(err) | ||
37 | |||
38 | return res.type('json').status(204).end() | ||
39 | }) | ||
40 | } | 32 | } |
diff --git a/server/controllers/api/remote/videos.ts b/server/controllers/api/remote/videos.ts index d9cc08fb4..ebe4eca36 100644 --- a/server/controllers/api/remote/videos.ts +++ b/server/controllers/api/remote/videos.ts | |||
@@ -1,6 +1,5 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import * as Sequelize from 'sequelize' | 2 | import * as Promise from 'bluebird' |
3 | import { eachSeries, waterfall } from 'async' | ||
4 | 3 | ||
5 | import { database as db } from '../../../initializers/database' | 4 | import { database as db } from '../../../initializers/database' |
6 | import { | 5 | import { |
@@ -16,20 +15,14 @@ import { | |||
16 | remoteQaduVideosValidator, | 15 | remoteQaduVideosValidator, |
17 | remoteEventsVideosValidator | 16 | remoteEventsVideosValidator |
18 | } from '../../../middlewares' | 17 | } from '../../../middlewares' |
19 | import { | 18 | import { logger, retryTransactionWrapper } from '../../../helpers' |
20 | logger, | ||
21 | commitTransaction, | ||
22 | retryTransactionWrapper, | ||
23 | rollbackTransaction, | ||
24 | startSerializableTransaction | ||
25 | } from '../../../helpers' | ||
26 | import { quickAndDirtyUpdatesVideoToFriends } from '../../../lib' | 19 | import { quickAndDirtyUpdatesVideoToFriends } from '../../../lib' |
27 | import { PodInstance, VideoInstance } from '../../../models' | 20 | import { PodInstance, VideoInstance } from '../../../models' |
28 | 21 | ||
29 | const ENDPOINT_ACTIONS = REQUEST_ENDPOINT_ACTIONS[REQUEST_ENDPOINTS.VIDEOS] | 22 | const ENDPOINT_ACTIONS = REQUEST_ENDPOINT_ACTIONS[REQUEST_ENDPOINTS.VIDEOS] |
30 | 23 | ||
31 | // Functions to call when processing a remote request | 24 | // Functions to call when processing a remote request |
32 | const functionsHash = {} | 25 | const functionsHash: { [ id: string ]: (...args) => Promise<any> } = {} |
33 | functionsHash[ENDPOINT_ACTIONS.ADD] = addRemoteVideoRetryWrapper | 26 | functionsHash[ENDPOINT_ACTIONS.ADD] = addRemoteVideoRetryWrapper |
34 | functionsHash[ENDPOINT_ACTIONS.UPDATE] = updateRemoteVideoRetryWrapper | 27 | functionsHash[ENDPOINT_ACTIONS.UPDATE] = updateRemoteVideoRetryWrapper |
35 | functionsHash[ENDPOINT_ACTIONS.REMOVE] = removeRemoteVideo | 28 | functionsHash[ENDPOINT_ACTIONS.REMOVE] = removeRemoteVideo |
@@ -72,20 +65,19 @@ function remoteVideos (req: express.Request, res: express.Response, next: expres | |||
72 | 65 | ||
73 | // We need to process in the same order to keep consistency | 66 | // We need to process in the same order to keep consistency |
74 | // TODO: optimization | 67 | // TODO: optimization |
75 | eachSeries(requests, function (request: any, callbackEach) { | 68 | Promise.mapSeries(requests, (request: any) => { |
76 | const data = request.data | 69 | const data = request.data |
77 | 70 | ||
78 | // Get the function we need to call in order to process the request | 71 | // Get the function we need to call in order to process the request |
79 | const fun = functionsHash[request.type] | 72 | const fun = functionsHash[request.type] |
80 | if (fun === undefined) { | 73 | if (fun === undefined) { |
81 | logger.error('Unkown remote request type %s.', request.type) | 74 | logger.error('Unkown remote request type %s.', request.type) |
82 | return callbackEach(null) | 75 | return |
83 | } | 76 | } |
84 | 77 | ||
85 | fun.call(this, data, fromPod, callbackEach) | 78 | return fun.call(this, data, fromPod) |
86 | }, function (err) { | ||
87 | if (err) logger.error('Error managing remote videos.', { error: err }) | ||
88 | }) | 79 | }) |
80 | .catch(err => logger.error('Error managing remote videos.', { error: err })) | ||
89 | 81 | ||
90 | // We don't need to keep the other pod waiting | 82 | // We don't need to keep the other pod waiting |
91 | return res.type('json').status(204).end() | 83 | return res.type('json').status(204).end() |
@@ -95,13 +87,12 @@ function remoteVideosQadu (req: express.Request, res: express.Response, next: ex | |||
95 | const requests = req.body.data | 87 | const requests = req.body.data |
96 | const fromPod = res.locals.secure.pod | 88 | const fromPod = res.locals.secure.pod |
97 | 89 | ||
98 | eachSeries(requests, function (request: any, callbackEach) { | 90 | Promise.mapSeries(requests, (request: any) => { |
99 | const videoData = request.data | 91 | const videoData = request.data |
100 | 92 | ||
101 | quickAndDirtyUpdateVideoRetryWrapper(videoData, fromPod, callbackEach) | 93 | return quickAndDirtyUpdateVideoRetryWrapper(videoData, fromPod) |
102 | }, function (err) { | ||
103 | if (err) logger.error('Error managing remote videos.', { error: err }) | ||
104 | }) | 94 | }) |
95 | .catch(err => logger.error('Error managing remote videos.', { error: err })) | ||
105 | 96 | ||
106 | return res.type('json').status(204).end() | 97 | return res.type('json').status(204).end() |
107 | } | 98 | } |
@@ -110,414 +101,303 @@ function remoteVideosEvents (req: express.Request, res: express.Response, next: | |||
110 | const requests = req.body.data | 101 | const requests = req.body.data |
111 | const fromPod = res.locals.secure.pod | 102 | const fromPod = res.locals.secure.pod |
112 | 103 | ||
113 | eachSeries(requests, function (request: any, callbackEach) { | 104 | Promise.mapSeries(requests, (request: any) => { |
114 | const eventData = request.data | 105 | const eventData = request.data |
115 | 106 | ||
116 | processVideosEventsRetryWrapper(eventData, fromPod, callbackEach) | 107 | return processVideosEventsRetryWrapper(eventData, fromPod) |
117 | }, function (err) { | ||
118 | if (err) logger.error('Error managing remote videos.', { error: err }) | ||
119 | }) | 108 | }) |
109 | .catch(err => logger.error('Error managing remote videos.', { error: err })) | ||
120 | 110 | ||
121 | return res.type('json').status(204).end() | 111 | return res.type('json').status(204).end() |
122 | } | 112 | } |
123 | 113 | ||
124 | function processVideosEventsRetryWrapper (eventData: any, fromPod: PodInstance, finalCallback: (err: Error) => void) { | 114 | function processVideosEventsRetryWrapper (eventData: any, fromPod: PodInstance) { |
125 | const options = { | 115 | const options = { |
126 | arguments: [ eventData, fromPod ], | 116 | arguments: [ eventData, fromPod ], |
127 | errorMessage: 'Cannot process videos events with many retries.' | 117 | errorMessage: 'Cannot process videos events with many retries.' |
128 | } | 118 | } |
129 | 119 | ||
130 | retryTransactionWrapper(processVideosEvents, options, finalCallback) | 120 | return retryTransactionWrapper(processVideosEvents, options) |
131 | } | 121 | } |
132 | 122 | ||
133 | function processVideosEvents (eventData: any, fromPod: PodInstance, finalCallback: (err: Error) => void) { | 123 | function processVideosEvents (eventData: any, fromPod: PodInstance) { |
134 | waterfall([ | ||
135 | startSerializableTransaction, | ||
136 | |||
137 | function findVideo (t, callback) { | ||
138 | fetchOwnedVideo(eventData.remoteId, function (err, videoInstance) { | ||
139 | return callback(err, t, videoInstance) | ||
140 | }) | ||
141 | }, | ||
142 | 124 | ||
143 | function updateVideoIntoDB (t, videoInstance, callback) { | 125 | return db.sequelize.transaction(t => { |
144 | const options = { transaction: t } | 126 | return fetchOwnedVideo(eventData.remoteId) |
127 | .then(videoInstance => { | ||
128 | const options = { transaction: t } | ||
145 | 129 | ||
146 | let columnToUpdate | 130 | let columnToUpdate |
147 | let qaduType | 131 | let qaduType |
148 | 132 | ||
149 | switch (eventData.eventType) { | 133 | switch (eventData.eventType) { |
150 | case REQUEST_VIDEO_EVENT_TYPES.VIEWS: | 134 | case REQUEST_VIDEO_EVENT_TYPES.VIEWS: |
151 | columnToUpdate = 'views' | 135 | columnToUpdate = 'views' |
152 | qaduType = REQUEST_VIDEO_QADU_TYPES.VIEWS | 136 | qaduType = REQUEST_VIDEO_QADU_TYPES.VIEWS |
153 | break | 137 | break |
154 | 138 | ||
155 | case REQUEST_VIDEO_EVENT_TYPES.LIKES: | 139 | case REQUEST_VIDEO_EVENT_TYPES.LIKES: |
156 | columnToUpdate = 'likes' | 140 | columnToUpdate = 'likes' |
157 | qaduType = REQUEST_VIDEO_QADU_TYPES.LIKES | 141 | qaduType = REQUEST_VIDEO_QADU_TYPES.LIKES |
158 | break | 142 | break |
159 | 143 | ||
160 | case REQUEST_VIDEO_EVENT_TYPES.DISLIKES: | 144 | case REQUEST_VIDEO_EVENT_TYPES.DISLIKES: |
161 | columnToUpdate = 'dislikes' | 145 | columnToUpdate = 'dislikes' |
162 | qaduType = REQUEST_VIDEO_QADU_TYPES.DISLIKES | 146 | qaduType = REQUEST_VIDEO_QADU_TYPES.DISLIKES |
163 | break | 147 | break |
164 | 148 | ||
165 | default: | 149 | default: |
166 | return callback(new Error('Unknown video event type.')) | 150 | throw new Error('Unknown video event type.') |
167 | } | 151 | } |
168 | 152 | ||
169 | const query = {} | 153 | const query = {} |
170 | query[columnToUpdate] = eventData.count | 154 | query[columnToUpdate] = eventData.count |
171 | 155 | ||
172 | videoInstance.increment(query, options).asCallback(function (err) { | 156 | return videoInstance.increment(query, options).then(() => ({ videoInstance, qaduType })) |
173 | return callback(err, t, videoInstance, qaduType) | ||
174 | }) | 157 | }) |
175 | }, | 158 | .then(({ videoInstance, qaduType }) => { |
176 | 159 | const qadusParams = [ | |
177 | function sendQaduToFriends (t, videoInstance, qaduType, callback) { | 160 | { |
178 | const qadusParams = [ | 161 | videoId: videoInstance.id, |
179 | { | 162 | type: qaduType |
180 | videoId: videoInstance.id, | 163 | } |
181 | type: qaduType | 164 | ] |
182 | } | 165 | |
183 | ] | 166 | return quickAndDirtyUpdatesVideoToFriends(qadusParams, t) |
184 | |||
185 | quickAndDirtyUpdatesVideoToFriends(qadusParams, t, function (err) { | ||
186 | return callback(err, t) | ||
187 | }) | 167 | }) |
188 | }, | 168 | }) |
189 | 169 | .then(() => logger.info('Remote video event processed for video %s.', eventData.remoteId)) | |
190 | commitTransaction | 170 | .catch(err => { |
191 | 171 | logger.debug('Cannot process a video event.', { error: err }) | |
192 | ], function (err: Error, t: Sequelize.Transaction) { | 172 | throw err |
193 | if (err) { | ||
194 | logger.debug('Cannot process a video event.', { error: err }) | ||
195 | return rollbackTransaction(err, t, finalCallback) | ||
196 | } | ||
197 | |||
198 | logger.info('Remote video event processed for video %s.', eventData.remoteId) | ||
199 | return finalCallback(null) | ||
200 | }) | 173 | }) |
201 | } | 174 | } |
202 | 175 | ||
203 | function quickAndDirtyUpdateVideoRetryWrapper (videoData: any, fromPod: PodInstance, finalCallback: (err: Error) => void) { | 176 | function quickAndDirtyUpdateVideoRetryWrapper (videoData: any, fromPod: PodInstance) { |
204 | const options = { | 177 | const options = { |
205 | arguments: [ videoData, fromPod ], | 178 | arguments: [ videoData, fromPod ], |
206 | errorMessage: 'Cannot update quick and dirty the remote video with many retries.' | 179 | errorMessage: 'Cannot update quick and dirty the remote video with many retries.' |
207 | } | 180 | } |
208 | 181 | ||
209 | retryTransactionWrapper(quickAndDirtyUpdateVideo, options, finalCallback) | 182 | return retryTransactionWrapper(quickAndDirtyUpdateVideo, options) |
210 | } | 183 | } |
211 | 184 | ||
212 | function quickAndDirtyUpdateVideo (videoData: any, fromPod: PodInstance, finalCallback: (err: Error) => void) { | 185 | function quickAndDirtyUpdateVideo (videoData: any, fromPod: PodInstance) { |
213 | let videoName | 186 | let videoName |
214 | 187 | ||
215 | waterfall([ | 188 | return db.sequelize.transaction(t => { |
216 | startSerializableTransaction, | 189 | return fetchRemoteVideo(fromPod.host, videoData.remoteId) |
217 | 190 | .then(videoInstance => { | |
218 | function findVideo (t, callback) { | 191 | const options = { transaction: t } |
219 | fetchRemoteVideo(fromPod.host, videoData.remoteId, function (err, videoInstance) { | ||
220 | return callback(err, t, videoInstance) | ||
221 | }) | ||
222 | }, | ||
223 | |||
224 | function updateVideoIntoDB (t, videoInstance, callback) { | ||
225 | const options = { transaction: t } | ||
226 | 192 | ||
227 | videoName = videoInstance.name | 193 | videoName = videoInstance.name |
228 | 194 | ||
229 | if (videoData.views) { | 195 | if (videoData.views) { |
230 | videoInstance.set('views', videoData.views) | 196 | videoInstance.set('views', videoData.views) |
231 | } | 197 | } |
232 | 198 | ||
233 | if (videoData.likes) { | 199 | if (videoData.likes) { |
234 | videoInstance.set('likes', videoData.likes) | 200 | videoInstance.set('likes', videoData.likes) |
235 | } | 201 | } |
236 | 202 | ||
237 | if (videoData.dislikes) { | 203 | if (videoData.dislikes) { |
238 | videoInstance.set('dislikes', videoData.dislikes) | 204 | videoInstance.set('dislikes', videoData.dislikes) |
239 | } | 205 | } |
240 | 206 | ||
241 | videoInstance.save(options).asCallback(function (err) { | 207 | return videoInstance.save(options) |
242 | return callback(err, t) | ||
243 | }) | 208 | }) |
244 | }, | ||
245 | |||
246 | commitTransaction | ||
247 | |||
248 | ], function (err: Error, t: Sequelize.Transaction) { | ||
249 | if (err) { | ||
250 | logger.debug('Cannot quick and dirty update the remote video.', { error: err }) | ||
251 | return rollbackTransaction(err, t, finalCallback) | ||
252 | } | ||
253 | |||
254 | logger.info('Remote video %s quick and dirty updated', videoName) | ||
255 | return finalCallback(null) | ||
256 | }) | 209 | }) |
210 | .then(() => logger.info('Remote video %s quick and dirty updated', videoName)) | ||
211 | .catch(err => logger.debug('Cannot quick and dirty update the remote video.', { error: err })) | ||
257 | } | 212 | } |
258 | 213 | ||
259 | // Handle retries on fail | 214 | // Handle retries on fail |
260 | function addRemoteVideoRetryWrapper (videoToCreateData: any, fromPod: PodInstance, finalCallback: (err: Error) => void) { | 215 | function addRemoteVideoRetryWrapper (videoToCreateData: any, fromPod: PodInstance) { |
261 | const options = { | 216 | const options = { |
262 | arguments: [ videoToCreateData, fromPod ], | 217 | arguments: [ videoToCreateData, fromPod ], |
263 | errorMessage: 'Cannot insert the remote video with many retries.' | 218 | errorMessage: 'Cannot insert the remote video with many retries.' |
264 | } | 219 | } |
265 | 220 | ||
266 | retryTransactionWrapper(addRemoteVideo, options, finalCallback) | 221 | return retryTransactionWrapper(addRemoteVideo, options) |
267 | } | 222 | } |
268 | 223 | ||
269 | function addRemoteVideo (videoToCreateData: any, fromPod: PodInstance, finalCallback: (err: Error) => void) { | 224 | function addRemoteVideo (videoToCreateData: any, fromPod: PodInstance) { |
270 | logger.debug('Adding remote video "%s".', videoToCreateData.remoteId) | 225 | logger.debug('Adding remote video "%s".', videoToCreateData.remoteId) |
271 | 226 | ||
272 | waterfall([ | 227 | return db.sequelize.transaction(t => { |
273 | 228 | return db.Video.loadByHostAndRemoteId(fromPod.host, videoToCreateData.remoteId) | |
274 | startSerializableTransaction, | 229 | .then(video => { |
275 | 230 | if (video) throw new Error('RemoteId and host pair is not unique.') | |
276 | function assertRemoteIdAndHostUnique (t, callback) { | ||
277 | db.Video.loadByHostAndRemoteId(fromPod.host, videoToCreateData.remoteId, function (err, video) { | ||
278 | if (err) return callback(err) | ||
279 | 231 | ||
280 | if (video) return callback(new Error('RemoteId and host pair is not unique.')) | 232 | return undefined |
281 | |||
282 | return callback(null, t) | ||
283 | }) | 233 | }) |
284 | }, | 234 | .then(() => { |
285 | 235 | const name = videoToCreateData.author | |
286 | function findOrCreateAuthor (t, callback) { | 236 | const podId = fromPod.id |
287 | const name = videoToCreateData.author | 237 | // This author is from another pod so we do not associate a user |
288 | const podId = fromPod.id | 238 | const userId = null |
289 | // This author is from another pod so we do not associate a user | ||
290 | const userId = null | ||
291 | 239 | ||
292 | db.Author.findOrCreateAuthor(name, podId, userId, t, function (err, authorInstance) { | 240 | return db.Author.findOrCreateAuthor(name, podId, userId, t) |
293 | return callback(err, t, authorInstance) | ||
294 | }) | 241 | }) |
295 | }, | 242 | .then(author => { |
243 | const tags = videoToCreateData.tags | ||
296 | 244 | ||
297 | function findOrCreateTags (t, author, callback) { | 245 | return db.Tag.findOrCreateTags(tags, t).then(tagInstances => ({ author, tagInstances })) |
298 | const tags = videoToCreateData.tags | ||
299 | |||
300 | db.Tag.findOrCreateTags(tags, t, function (err, tagInstances) { | ||
301 | return callback(err, t, author, tagInstances) | ||
302 | }) | 246 | }) |
303 | }, | 247 | .then(({ author, tagInstances }) => { |
304 | 248 | const videoData = { | |
305 | function createVideoObject (t, author, tagInstances, callback) { | 249 | name: videoToCreateData.name, |
306 | const videoData = { | 250 | remoteId: videoToCreateData.remoteId, |
307 | name: videoToCreateData.name, | 251 | extname: videoToCreateData.extname, |
308 | remoteId: videoToCreateData.remoteId, | 252 | infoHash: videoToCreateData.infoHash, |
309 | extname: videoToCreateData.extname, | 253 | category: videoToCreateData.category, |
310 | infoHash: videoToCreateData.infoHash, | 254 | licence: videoToCreateData.licence, |
311 | category: videoToCreateData.category, | 255 | language: videoToCreateData.language, |
312 | licence: videoToCreateData.licence, | 256 | nsfw: videoToCreateData.nsfw, |
313 | language: videoToCreateData.language, | 257 | description: videoToCreateData.description, |
314 | nsfw: videoToCreateData.nsfw, | 258 | authorId: author.id, |
315 | description: videoToCreateData.description, | 259 | duration: videoToCreateData.duration, |
316 | authorId: author.id, | 260 | createdAt: videoToCreateData.createdAt, |
317 | duration: videoToCreateData.duration, | 261 | // FIXME: updatedAt does not seems to be considered by Sequelize |
318 | createdAt: videoToCreateData.createdAt, | 262 | updatedAt: videoToCreateData.updatedAt, |
319 | // FIXME: updatedAt does not seems to be considered by Sequelize | 263 | views: videoToCreateData.views, |
320 | updatedAt: videoToCreateData.updatedAt, | 264 | likes: videoToCreateData.likes, |
321 | views: videoToCreateData.views, | 265 | dislikes: videoToCreateData.dislikes |
322 | likes: videoToCreateData.likes, | ||
323 | dislikes: videoToCreateData.dislikes | ||
324 | } | ||
325 | |||
326 | const video = db.Video.build(videoData) | ||
327 | |||
328 | return callback(null, t, tagInstances, video) | ||
329 | }, | ||
330 | |||
331 | function generateThumbnail (t, tagInstances, video, callback) { | ||
332 | db.Video.generateThumbnailFromData(video, videoToCreateData.thumbnailData, function (err) { | ||
333 | if (err) { | ||
334 | logger.error('Cannot generate thumbnail from data.', { error: err }) | ||
335 | return callback(err) | ||
336 | } | 266 | } |
337 | 267 | ||
338 | return callback(err, t, tagInstances, video) | 268 | const video = db.Video.build(videoData) |
269 | return { tagInstances, video } | ||
339 | }) | 270 | }) |
340 | }, | 271 | .then(({ tagInstances, video }) => { |
341 | 272 | return db.Video.generateThumbnailFromData(video, videoToCreateData.thumbnailData).then(() => ({ tagInstances, video })) | |
342 | function insertVideoIntoDB (t, tagInstances, video, callback) { | ||
343 | const options = { | ||
344 | transaction: t | ||
345 | } | ||
346 | |||
347 | video.save(options).asCallback(function (err, videoCreated) { | ||
348 | return callback(err, t, tagInstances, videoCreated) | ||
349 | }) | 273 | }) |
350 | }, | 274 | .then(({ tagInstances, video }) => { |
351 | 275 | const options = { | |
352 | function associateTagsToVideo (t, tagInstances, video, callback) { | 276 | transaction: t |
353 | const options = { | 277 | } |
354 | transaction: t | ||
355 | } | ||
356 | 278 | ||
357 | video.setTags(tagInstances, options).asCallback(function (err) { | 279 | return video.save(options).then(videoCreated => ({ tagInstances, videoCreated })) |
358 | return callback(err, t) | ||
359 | }) | 280 | }) |
360 | }, | 281 | .then(({ tagInstances, videoCreated }) => { |
361 | 282 | const options = { | |
362 | commitTransaction | 283 | transaction: t |
363 | 284 | } | |
364 | ], function (err: Error, t: Sequelize.Transaction) { | ||
365 | if (err) { | ||
366 | // This is just a debug because we will retry the insert | ||
367 | logger.debug('Cannot insert the remote video.', { error: err }) | ||
368 | return rollbackTransaction(err, t, finalCallback) | ||
369 | } | ||
370 | 285 | ||
371 | logger.info('Remote video %s inserted.', videoToCreateData.name) | 286 | return videoCreated.setTags(tagInstances, options) |
372 | return finalCallback(null) | 287 | }) |
288 | }) | ||
289 | .then(() => logger.info('Remote video %s inserted.', videoToCreateData.name)) | ||
290 | .catch(err => { | ||
291 | logger.debug('Cannot insert the remote video.', { error: err }) | ||
292 | throw err | ||
373 | }) | 293 | }) |
374 | } | 294 | } |
375 | 295 | ||
376 | // Handle retries on fail | 296 | // Handle retries on fail |
377 | function updateRemoteVideoRetryWrapper (videoAttributesToUpdate: any, fromPod: PodInstance, finalCallback: (err: Error) => void) { | 297 | function updateRemoteVideoRetryWrapper (videoAttributesToUpdate: any, fromPod: PodInstance) { |
378 | const options = { | 298 | const options = { |
379 | arguments: [ videoAttributesToUpdate, fromPod ], | 299 | arguments: [ videoAttributesToUpdate, fromPod ], |
380 | errorMessage: 'Cannot update the remote video with many retries' | 300 | errorMessage: 'Cannot update the remote video with many retries' |
381 | } | 301 | } |
382 | 302 | ||
383 | retryTransactionWrapper(updateRemoteVideo, options, finalCallback) | 303 | return retryTransactionWrapper(updateRemoteVideo, options) |
384 | } | 304 | } |
385 | 305 | ||
386 | function updateRemoteVideo (videoAttributesToUpdate: any, fromPod: PodInstance, finalCallback: (err: Error) => void) { | 306 | function updateRemoteVideo (videoAttributesToUpdate: any, fromPod: PodInstance) { |
387 | logger.debug('Updating remote video "%s".', videoAttributesToUpdate.remoteId) | 307 | logger.debug('Updating remote video "%s".', videoAttributesToUpdate.remoteId) |
388 | 308 | ||
389 | waterfall([ | 309 | return db.sequelize.transaction(t => { |
310 | return fetchRemoteVideo(fromPod.host, videoAttributesToUpdate.remoteId) | ||
311 | .then(videoInstance => { | ||
312 | const tags = videoAttributesToUpdate.tags | ||
390 | 313 | ||
391 | startSerializableTransaction, | 314 | return db.Tag.findOrCreateTags(tags, t).then(tagInstances => ({ videoInstance, tagInstances })) |
392 | |||
393 | function findVideo (t, callback) { | ||
394 | fetchRemoteVideo(fromPod.host, videoAttributesToUpdate.remoteId, function (err, videoInstance) { | ||
395 | return callback(err, t, videoInstance) | ||
396 | }) | 315 | }) |
397 | }, | 316 | .then(({ videoInstance, tagInstances }) => { |
398 | 317 | const options = { transaction: t } | |
399 | function findOrCreateTags (t, videoInstance, callback) { | 318 | |
400 | const tags = videoAttributesToUpdate.tags | 319 | videoInstance.set('name', videoAttributesToUpdate.name) |
401 | 320 | videoInstance.set('category', videoAttributesToUpdate.category) | |
402 | db.Tag.findOrCreateTags(tags, t, function (err, tagInstances) { | 321 | videoInstance.set('licence', videoAttributesToUpdate.licence) |
403 | return callback(err, t, videoInstance, tagInstances) | 322 | videoInstance.set('language', videoAttributesToUpdate.language) |
404 | }) | 323 | videoInstance.set('nsfw', videoAttributesToUpdate.nsfw) |
405 | }, | 324 | videoInstance.set('description', videoAttributesToUpdate.description) |
406 | 325 | videoInstance.set('infoHash', videoAttributesToUpdate.infoHash) | |
407 | function updateVideoIntoDB (t, videoInstance, tagInstances, callback) { | 326 | videoInstance.set('duration', videoAttributesToUpdate.duration) |
408 | const options = { transaction: t } | 327 | videoInstance.set('createdAt', videoAttributesToUpdate.createdAt) |
409 | 328 | videoInstance.set('updatedAt', videoAttributesToUpdate.updatedAt) | |
410 | videoInstance.set('name', videoAttributesToUpdate.name) | 329 | videoInstance.set('extname', videoAttributesToUpdate.extname) |
411 | videoInstance.set('category', videoAttributesToUpdate.category) | 330 | videoInstance.set('views', videoAttributesToUpdate.views) |
412 | videoInstance.set('licence', videoAttributesToUpdate.licence) | 331 | videoInstance.set('likes', videoAttributesToUpdate.likes) |
413 | videoInstance.set('language', videoAttributesToUpdate.language) | 332 | videoInstance.set('dislikes', videoAttributesToUpdate.dislikes) |
414 | videoInstance.set('nsfw', videoAttributesToUpdate.nsfw) | 333 | |
415 | videoInstance.set('description', videoAttributesToUpdate.description) | 334 | return videoInstance.save(options).then(() => ({ videoInstance, tagInstances })) |
416 | videoInstance.set('infoHash', videoAttributesToUpdate.infoHash) | ||
417 | videoInstance.set('duration', videoAttributesToUpdate.duration) | ||
418 | videoInstance.set('createdAt', videoAttributesToUpdate.createdAt) | ||
419 | videoInstance.set('updatedAt', videoAttributesToUpdate.updatedAt) | ||
420 | videoInstance.set('extname', videoAttributesToUpdate.extname) | ||
421 | videoInstance.set('views', videoAttributesToUpdate.views) | ||
422 | videoInstance.set('likes', videoAttributesToUpdate.likes) | ||
423 | videoInstance.set('dislikes', videoAttributesToUpdate.dislikes) | ||
424 | |||
425 | videoInstance.save(options).asCallback(function (err) { | ||
426 | return callback(err, t, videoInstance, tagInstances) | ||
427 | }) | 335 | }) |
428 | }, | 336 | .then(({ videoInstance, tagInstances }) => { |
429 | 337 | const options = { transaction: t } | |
430 | function associateTagsToVideo (t, videoInstance, tagInstances, callback) { | ||
431 | const options = { transaction: t } | ||
432 | 338 | ||
433 | videoInstance.setTags(tagInstances, options).asCallback(function (err) { | 339 | return videoInstance.setTags(tagInstances, options) |
434 | return callback(err, t) | ||
435 | }) | 340 | }) |
436 | }, | 341 | }) |
437 | 342 | .then(() => logger.info('Remote video %s updated', videoAttributesToUpdate.name)) | |
438 | commitTransaction | 343 | .catch(err => { |
439 | 344 | // This is just a debug because we will retry the insert | |
440 | ], function (err: Error, t: Sequelize.Transaction) { | 345 | logger.debug('Cannot update the remote video.', { error: err }) |
441 | if (err) { | 346 | throw err |
442 | // This is just a debug because we will retry the insert | ||
443 | logger.debug('Cannot update the remote video.', { error: err }) | ||
444 | return rollbackTransaction(err, t, finalCallback) | ||
445 | } | ||
446 | |||
447 | logger.info('Remote video %s updated', videoAttributesToUpdate.name) | ||
448 | return finalCallback(null) | ||
449 | }) | 347 | }) |
450 | } | 348 | } |
451 | 349 | ||
452 | function removeRemoteVideo (videoToRemoveData: any, fromPod: PodInstance, callback: (err: Error) => void) { | 350 | function removeRemoteVideo (videoToRemoveData: any, fromPod: PodInstance) { |
453 | // We need the instance because we have to remove some other stuffs (thumbnail etc) | 351 | // We need the instance because we have to remove some other stuffs (thumbnail etc) |
454 | fetchRemoteVideo(fromPod.host, videoToRemoveData.remoteId, function (err, video) { | 352 | return fetchRemoteVideo(fromPod.host, videoToRemoveData.remoteId) |
455 | // Do not return the error, continue the process | 353 | .then(video => { |
456 | if (err) return callback(null) | 354 | logger.debug('Removing remote video %s.', video.remoteId) |
457 | 355 | return video.destroy() | |
458 | logger.debug('Removing remote video %s.', video.remoteId) | 356 | }) |
459 | video.destroy().asCallback(function (err) { | 357 | .catch(err => { |
460 | // Do not return the error, continue the process | 358 | logger.debug('Could not fetch remote video.', { host: fromPod.host, remoteId: videoToRemoveData.remoteId, error: err }) |
461 | if (err) { | ||
462 | logger.error('Cannot remove remote video with id %s.', videoToRemoveData.remoteId, { error: err }) | ||
463 | } | ||
464 | |||
465 | return callback(null) | ||
466 | }) | 359 | }) |
467 | }) | ||
468 | } | 360 | } |
469 | 361 | ||
470 | function reportAbuseRemoteVideo (reportData: any, fromPod: PodInstance, callback: (err: Error) => void) { | 362 | function reportAbuseRemoteVideo (reportData: any, fromPod: PodInstance) { |
471 | fetchOwnedVideo(reportData.videoRemoteId, function (err, video) { | 363 | return fetchOwnedVideo(reportData.videoRemoteId) |
472 | if (err || !video) { | 364 | .then(video => { |
473 | if (!err) err = new Error('video not found') | 365 | logger.debug('Reporting remote abuse for video %s.', video.id) |
474 | |||
475 | logger.error('Cannot load video from id.', { error: err, id: reportData.videoRemoteId }) | ||
476 | // Do not return the error, continue the process | ||
477 | return callback(null) | ||
478 | } | ||
479 | |||
480 | logger.debug('Reporting remote abuse for video %s.', video.id) | ||
481 | |||
482 | const videoAbuseData = { | ||
483 | reporterUsername: reportData.reporterUsername, | ||
484 | reason: reportData.reportReason, | ||
485 | reporterPodId: fromPod.id, | ||
486 | videoId: video.id | ||
487 | } | ||
488 | 366 | ||
489 | db.VideoAbuse.create(videoAbuseData).asCallback(function (err) { | 367 | const videoAbuseData = { |
490 | if (err) { | 368 | reporterUsername: reportData.reporterUsername, |
491 | logger.error('Cannot create remote abuse video.', { error: err }) | 369 | reason: reportData.reportReason, |
370 | reporterPodId: fromPod.id, | ||
371 | videoId: video.id | ||
492 | } | 372 | } |
493 | 373 | ||
494 | return callback(null) | 374 | return db.VideoAbuse.create(videoAbuseData) |
495 | }) | 375 | }) |
496 | }) | 376 | .catch(err => logger.error('Cannot create remote abuse video.', { error: err })) |
497 | } | 377 | } |
498 | 378 | ||
499 | function fetchOwnedVideo (id: string, callback: (err: Error, video?: VideoInstance) => void) { | 379 | function fetchOwnedVideo (id: string) { |
500 | db.Video.load(id, function (err, video) { | 380 | return db.Video.load(id) |
501 | if (err || !video) { | 381 | .then(video => { |
502 | if (!err) err = new Error('video not found') | 382 | if (!video) throw new Error('Video not found') |
503 | 383 | ||
384 | return video | ||
385 | }) | ||
386 | .catch(err => { | ||
504 | logger.error('Cannot load owned video from id.', { error: err, id }) | 387 | logger.error('Cannot load owned video from id.', { error: err, id }) |
505 | return callback(err) | 388 | throw err |
506 | } | 389 | }) |
507 | |||
508 | return callback(null, video) | ||
509 | }) | ||
510 | } | 390 | } |
511 | 391 | ||
512 | function fetchRemoteVideo (podHost: string, remoteId: string, callback: (err: Error, video?: VideoInstance) => void) { | 392 | function fetchRemoteVideo (podHost: string, remoteId: string) { |
513 | db.Video.loadByHostAndRemoteId(podHost, remoteId, function (err, video) { | 393 | return db.Video.loadByHostAndRemoteId(podHost, remoteId) |
514 | if (err || !video) { | 394 | .then(video => { |
515 | if (!err) err = new Error('video not found') | 395 | if (!video) throw new Error('Video not found') |
516 | 396 | ||
397 | return video | ||
398 | }) | ||
399 | .catch(err => { | ||
517 | logger.error('Cannot load video from host and remote id.', { error: err, podHost, remoteId }) | 400 | logger.error('Cannot load video from host and remote id.', { error: err, podHost, remoteId }) |
518 | return callback(err) | 401 | throw err |
519 | } | 402 | }) |
520 | |||
521 | return callback(null, video) | ||
522 | }) | ||
523 | } | 403 | } |
diff --git a/server/controllers/api/request-schedulers.ts b/server/controllers/api/request-schedulers.ts index 8dd849007..2a934a512 100644 --- a/server/controllers/api/request-schedulers.ts +++ b/server/controllers/api/request-schedulers.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { parallel } from 'async' | 2 | import * as Promise from 'bluebird' |
3 | 3 | ||
4 | import { | 4 | import { |
5 | AbstractRequestScheduler, | 5 | AbstractRequestScheduler, |
@@ -27,33 +27,27 @@ export { | |||
27 | // --------------------------------------------------------------------------- | 27 | // --------------------------------------------------------------------------- |
28 | 28 | ||
29 | function getRequestSchedulersStats (req: express.Request, res: express.Response, next: express.NextFunction) { | 29 | function getRequestSchedulersStats (req: express.Request, res: express.Response, next: express.NextFunction) { |
30 | parallel({ | 30 | Promise.props({ |
31 | requestScheduler: buildRequestSchedulerStats(getRequestScheduler()), | 31 | requestScheduler: buildRequestSchedulerStats(getRequestScheduler()), |
32 | requestVideoQaduScheduler: buildRequestSchedulerStats(getRequestVideoQaduScheduler()), | 32 | requestVideoQaduScheduler: buildRequestSchedulerStats(getRequestVideoQaduScheduler()), |
33 | requestVideoEventScheduler: buildRequestSchedulerStats(getRequestVideoEventScheduler()) | 33 | requestVideoEventScheduler: buildRequestSchedulerStats(getRequestVideoEventScheduler()) |
34 | }, function (err, result) { | ||
35 | if (err) return next(err) | ||
36 | |||
37 | return res.json(result) | ||
38 | }) | 34 | }) |
35 | .then(result => res.json(result)) | ||
36 | .catch(err => next(err)) | ||
39 | } | 37 | } |
40 | 38 | ||
41 | // --------------------------------------------------------------------------- | 39 | // --------------------------------------------------------------------------- |
42 | 40 | ||
43 | function buildRequestSchedulerStats (requestScheduler: AbstractRequestScheduler) { | 41 | function buildRequestSchedulerStats (requestScheduler: AbstractRequestScheduler<any>) { |
44 | return function (callback) { | 42 | return requestScheduler.remainingRequestsCount().then(count => { |
45 | requestScheduler.remainingRequestsCount(function (err, count) { | 43 | const result: RequestSchedulerStatsAttributes = { |
46 | if (err) return callback(err) | 44 | totalRequests: count, |
47 | 45 | requestsLimitPods: requestScheduler.limitPods, | |
48 | const result: RequestSchedulerStatsAttributes = { | 46 | requestsLimitPerPod: requestScheduler.limitPerPod, |
49 | totalRequests: count, | 47 | remainingMilliSeconds: requestScheduler.remainingMilliSeconds(), |
50 | requestsLimitPods: requestScheduler.limitPods, | 48 | milliSecondsInterval: requestScheduler.requestInterval |
51 | requestsLimitPerPod: requestScheduler.limitPerPod, | 49 | } |
52 | remainingMilliSeconds: requestScheduler.remainingMilliSeconds(), | 50 | |
53 | milliSecondsInterval: requestScheduler.requestInterval | 51 | return result |
54 | } | 52 | }) |
55 | |||
56 | return callback(null, result) | ||
57 | }) | ||
58 | } | ||
59 | } | 53 | } |
diff --git a/server/controllers/api/users.ts b/server/controllers/api/users.ts index ce15353ef..6e0bb474a 100644 --- a/server/controllers/api/users.ts +++ b/server/controllers/api/users.ts | |||
@@ -1,8 +1,7 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { waterfall } from 'async' | ||
3 | 2 | ||
4 | import { database as db } from '../../initializers/database' | 3 | import { database as db } from '../../initializers/database' |
5 | import { CONFIG, USER_ROLES } from '../../initializers' | 4 | import { USER_ROLES } from '../../initializers' |
6 | import { logger, getFormatedObjects } from '../../helpers' | 5 | import { logger, getFormatedObjects } from '../../helpers' |
7 | import { | 6 | import { |
8 | authenticate, | 7 | authenticate, |
@@ -87,78 +86,61 @@ function createUser (req: express.Request, res: express.Response, next: express. | |||
87 | role: USER_ROLES.USER | 86 | role: USER_ROLES.USER |
88 | }) | 87 | }) |
89 | 88 | ||
90 | user.save().asCallback(function (err) { | 89 | user.save() |
91 | if (err) return next(err) | 90 | .then(() => res.type('json').status(204).end()) |
92 | 91 | .catch(err => next(err)) | |
93 | return res.type('json').status(204).end() | ||
94 | }) | ||
95 | } | 92 | } |
96 | 93 | ||
97 | function getUserInformation (req: express.Request, res: express.Response, next: express.NextFunction) { | 94 | function getUserInformation (req: express.Request, res: express.Response, next: express.NextFunction) { |
98 | db.User.loadByUsername(res.locals.oauth.token.user.username, function (err, user) { | 95 | db.User.loadByUsername(res.locals.oauth.token.user.username) |
99 | if (err) return next(err) | 96 | .then(user => res.json(user.toFormatedJSON())) |
100 | 97 | .catch(err => next(err)) | |
101 | return res.json(user.toFormatedJSON()) | ||
102 | }) | ||
103 | } | 98 | } |
104 | 99 | ||
105 | function getUserVideoRating (req: express.Request, res: express.Response, next: express.NextFunction) { | 100 | function getUserVideoRating (req: express.Request, res: express.Response, next: express.NextFunction) { |
106 | const videoId = '' + req.params.videoId | 101 | const videoId = '' + req.params.videoId |
107 | const userId = +res.locals.oauth.token.User.id | 102 | const userId = +res.locals.oauth.token.User.id |
108 | 103 | ||
109 | db.UserVideoRate.load(userId, videoId, null, function (err, ratingObj) { | 104 | db.UserVideoRate.load(userId, videoId, null) |
110 | if (err) return next(err) | 105 | .then(ratingObj => { |
111 | 106 | const rating = ratingObj ? ratingObj.type : 'none' | |
112 | const rating = ratingObj ? ratingObj.type : 'none' | 107 | const json: FormatedUserVideoRate = { |
113 | 108 | videoId, | |
114 | const json: FormatedUserVideoRate = { | 109 | rating |
115 | videoId, | 110 | } |
116 | rating | 111 | res.json(json) |
117 | } | 112 | }) |
118 | res.json(json) | 113 | .catch(err => next(err)) |
119 | }) | ||
120 | } | 114 | } |
121 | 115 | ||
122 | function listUsers (req: express.Request, res: express.Response, next: express.NextFunction) { | 116 | function listUsers (req: express.Request, res: express.Response, next: express.NextFunction) { |
123 | db.User.listForApi(req.query.start, req.query.count, req.query.sort, function (err, usersList, usersTotal) { | 117 | db.User.listForApi(req.query.start, req.query.count, req.query.sort) |
124 | if (err) return next(err) | 118 | .then(resultList => { |
125 | 119 | res.json(getFormatedObjects(resultList.data, resultList.total)) | |
126 | res.json(getFormatedObjects(usersList, usersTotal)) | 120 | }) |
127 | }) | 121 | .catch(err => next(err)) |
128 | } | 122 | } |
129 | 123 | ||
130 | function removeUser (req: express.Request, res: express.Response, next: express.NextFunction) { | 124 | function removeUser (req: express.Request, res: express.Response, next: express.NextFunction) { |
131 | waterfall([ | 125 | db.User.loadById(req.params.id) |
132 | function loadUser (callback) { | 126 | .then(user => user.destroy()) |
133 | db.User.loadById(req.params.id, callback) | 127 | .then(() => res.sendStatus(204)) |
134 | }, | 128 | .catch(err => { |
135 | |||
136 | function deleteUser (user, callback) { | ||
137 | user.destroy().asCallback(callback) | ||
138 | } | ||
139 | ], function andFinally (err) { | ||
140 | if (err) { | ||
141 | logger.error('Errors when removed the user.', { error: err }) | 129 | logger.error('Errors when removed the user.', { error: err }) |
142 | return next(err) | 130 | return next(err) |
143 | } | 131 | }) |
144 | |||
145 | return res.sendStatus(204) | ||
146 | }) | ||
147 | } | 132 | } |
148 | 133 | ||
149 | function updateUser (req: express.Request, res: express.Response, next: express.NextFunction) { | 134 | function updateUser (req: express.Request, res: express.Response, next: express.NextFunction) { |
150 | db.User.loadByUsername(res.locals.oauth.token.user.username, function (err, user) { | 135 | db.User.loadByUsername(res.locals.oauth.token.user.username) |
151 | if (err) return next(err) | 136 | .then(user => { |
152 | 137 | if (req.body.password) user.password = req.body.password | |
153 | if (req.body.password) user.password = req.body.password | 138 | if (req.body.displayNSFW !== undefined) user.displayNSFW = req.body.displayNSFW |
154 | if (req.body.displayNSFW !== undefined) user.displayNSFW = req.body.displayNSFW | ||
155 | 139 | ||
156 | user.save().asCallback(function (err) { | 140 | return user.save() |
157 | if (err) return next(err) | ||
158 | |||
159 | return res.sendStatus(204) | ||
160 | }) | 141 | }) |
161 | }) | 142 | .then(() => res.sendStatus(204)) |
143 | .catch(err => next(err)) | ||
162 | } | 144 | } |
163 | 145 | ||
164 | function success (req: express.Request, res: express.Response, next: express.NextFunction) { | 146 | function success (req: express.Request, res: express.Response, next: express.NextFunction) { |
diff --git a/server/controllers/api/videos/abuse.ts b/server/controllers/api/videos/abuse.ts index 78e8e8b3d..fcbd5465f 100644 --- a/server/controllers/api/videos/abuse.ts +++ b/server/controllers/api/videos/abuse.ts | |||
@@ -1,16 +1,11 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import * as Sequelize from 'sequelize' | ||
3 | import { waterfall } from 'async' | ||
4 | 2 | ||
5 | import { database as db } from '../../../initializers/database' | 3 | import { database as db } from '../../../initializers/database' |
6 | import * as friends from '../../../lib/friends' | 4 | import * as friends from '../../../lib/friends' |
7 | import { | 5 | import { |
8 | logger, | 6 | logger, |
9 | getFormatedObjects, | 7 | getFormatedObjects, |
10 | retryTransactionWrapper, | 8 | retryTransactionWrapper |
11 | startSerializableTransaction, | ||
12 | commitTransaction, | ||
13 | rollbackTransaction | ||
14 | } from '../../../helpers' | 9 | } from '../../../helpers' |
15 | import { | 10 | import { |
16 | authenticate, | 11 | authenticate, |
@@ -21,6 +16,7 @@ import { | |||
21 | setVideoAbusesSort, | 16 | setVideoAbusesSort, |
22 | setPagination | 17 | setPagination |
23 | } from '../../../middlewares' | 18 | } from '../../../middlewares' |
19 | import { VideoInstance } from '../../../models' | ||
24 | 20 | ||
25 | const abuseVideoRouter = express.Router() | 21 | const abuseVideoRouter = express.Router() |
26 | 22 | ||
@@ -48,11 +44,9 @@ export { | |||
48 | // --------------------------------------------------------------------------- | 44 | // --------------------------------------------------------------------------- |
49 | 45 | ||
50 | function listVideoAbuses (req: express.Request, res: express.Response, next: express.NextFunction) { | 46 | function listVideoAbuses (req: express.Request, res: express.Response, next: express.NextFunction) { |
51 | db.VideoAbuse.listForApi(req.query.start, req.query.count, req.query.sort, function (err, abusesList, abusesTotal) { | 47 | db.VideoAbuse.listForApi(req.query.start, req.query.count, req.query.sort) |
52 | if (err) return next(err) | 48 | .then(result => res.json(getFormatedObjects(result.data, result.total))) |
53 | 49 | .catch(err => next(err)) | |
54 | res.json(getFormatedObjects(abusesList, abusesTotal)) | ||
55 | }) | ||
56 | } | 50 | } |
57 | 51 | ||
58 | function reportVideoAbuseRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { | 52 | function reportVideoAbuseRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { |
@@ -61,14 +55,12 @@ function reportVideoAbuseRetryWrapper (req: express.Request, res: express.Respon | |||
61 | errorMessage: 'Cannot report abuse to the video with many retries.' | 55 | errorMessage: 'Cannot report abuse to the video with many retries.' |
62 | } | 56 | } |
63 | 57 | ||
64 | retryTransactionWrapper(reportVideoAbuse, options, function (err) { | 58 | retryTransactionWrapper(reportVideoAbuse, options) |
65 | if (err) return next(err) | 59 | .then(() => res.type('json').status(204).end()) |
66 | 60 | .catch(err => next(err)) | |
67 | return res.type('json').status(204).end() | ||
68 | }) | ||
69 | } | 61 | } |
70 | 62 | ||
71 | function reportVideoAbuse (req: express.Request, res: express.Response, finalCallback: (err: Error) => void) { | 63 | function reportVideoAbuse (req: express.Request, res: express.Response) { |
72 | const videoInstance = res.locals.video | 64 | const videoInstance = res.locals.video |
73 | const reporterUsername = res.locals.oauth.token.User.username | 65 | const reporterUsername = res.locals.oauth.token.User.username |
74 | 66 | ||
@@ -79,40 +71,26 @@ function reportVideoAbuse (req: express.Request, res: express.Response, finalCal | |||
79 | reporterPodId: null // This is our pod that reported this abuse | 71 | reporterPodId: null // This is our pod that reported this abuse |
80 | } | 72 | } |
81 | 73 | ||
82 | waterfall([ | 74 | return db.sequelize.transaction(t => { |
83 | 75 | return db.VideoAbuse.create(abuse, { transaction: t }) | |
84 | startSerializableTransaction, | 76 | .then(abuse => { |
85 | 77 | // We send the information to the destination pod | |
86 | function createAbuse (t, callback) { | 78 | if (videoInstance.isOwned() === false) { |
87 | db.VideoAbuse.create(abuse).asCallback(function (err, abuse) { | 79 | const reportData = { |
88 | return callback(err, t, abuse) | 80 | reporterUsername, |
89 | }) | 81 | reportReason: abuse.reason, |
90 | }, | 82 | videoRemoteId: videoInstance.remoteId |
91 | 83 | } | |
92 | function sendToFriendsIfNeeded (t, abuse, callback) { | 84 | |
93 | // We send the information to the destination pod | 85 | return friends.reportAbuseVideoToFriend(reportData, videoInstance, t).then(() => videoInstance) |
94 | if (videoInstance.isOwned() === false) { | ||
95 | const reportData = { | ||
96 | reporterUsername, | ||
97 | reportReason: abuse.reason, | ||
98 | videoRemoteId: videoInstance.remoteId | ||
99 | } | 86 | } |
100 | 87 | ||
101 | friends.reportAbuseVideoToFriend(reportData, videoInstance) | 88 | return videoInstance |
102 | } | 89 | }) |
103 | 90 | }) | |
104 | return callback(null, t) | 91 | .then((videoInstance: VideoInstance) => logger.info('Abuse report for video %s created.', videoInstance.name)) |
105 | }, | 92 | .catch(err => { |
106 | 93 | logger.debug('Cannot update the video.', { error: err }) | |
107 | commitTransaction | 94 | throw err |
108 | |||
109 | ], function andFinally (err: Error, t: Sequelize.Transaction) { | ||
110 | if (err) { | ||
111 | logger.debug('Cannot update the video.', { error: err }) | ||
112 | return rollbackTransaction(err, t, finalCallback) | ||
113 | } | ||
114 | |||
115 | logger.info('Abuse report for video %s created.', videoInstance.name) | ||
116 | return finalCallback(null) | ||
117 | }) | 95 | }) |
118 | } | 96 | } |
diff --git a/server/controllers/api/videos/blacklist.ts b/server/controllers/api/videos/blacklist.ts index 4b42fc2d7..e4be6f0f9 100644 --- a/server/controllers/api/videos/blacklist.ts +++ b/server/controllers/api/videos/blacklist.ts | |||
@@ -32,12 +32,10 @@ function addVideoToBlacklist (req: express.Request, res: express.Response, next: | |||
32 | videoId: videoInstance.id | 32 | videoId: videoInstance.id |
33 | } | 33 | } |
34 | 34 | ||
35 | db.BlacklistedVideo.create(toCreate).asCallback(function (err) { | 35 | db.BlacklistedVideo.create(toCreate) |
36 | if (err) { | 36 | .then(() => res.type('json').status(204).end()) |
37 | .catch(err => { | ||
37 | logger.error('Errors when blacklisting video ', { error: err }) | 38 | logger.error('Errors when blacklisting video ', { error: err }) |
38 | return next(err) | 39 | return next(err) |
39 | } | 40 | }) |
40 | |||
41 | return res.type('json').status(204).end() | ||
42 | }) | ||
43 | } | 41 | } |
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts index 5e8cf2d25..ed1f21d66 100644 --- a/server/controllers/api/videos/index.ts +++ b/server/controllers/api/videos/index.ts | |||
@@ -1,9 +1,7 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import * as Sequelize from 'sequelize' | 2 | import * as Promise from 'bluebird' |
3 | import * as fs from 'fs' | ||
4 | import * as multer from 'multer' | 3 | import * as multer from 'multer' |
5 | import * as path from 'path' | 4 | import * as path from 'path' |
6 | import { waterfall } from 'async' | ||
7 | 5 | ||
8 | import { database as db } from '../../../initializers/database' | 6 | import { database as db } from '../../../initializers/database' |
9 | import { | 7 | import { |
@@ -35,13 +33,12 @@ import { | |||
35 | } from '../../../middlewares' | 33 | } from '../../../middlewares' |
36 | import { | 34 | import { |
37 | logger, | 35 | logger, |
38 | commitTransaction, | ||
39 | retryTransactionWrapper, | 36 | retryTransactionWrapper, |
40 | rollbackTransaction, | ||
41 | startSerializableTransaction, | ||
42 | generateRandomString, | 37 | generateRandomString, |
43 | getFormatedObjects | 38 | getFormatedObjects, |
39 | renamePromise | ||
44 | } from '../../../helpers' | 40 | } from '../../../helpers' |
41 | import { TagInstance } from '../../../models' | ||
45 | 42 | ||
46 | import { abuseVideoRouter } from './abuse' | 43 | import { abuseVideoRouter } from './abuse' |
47 | import { blacklistRouter } from './blacklist' | 44 | import { blacklistRouter } from './blacklist' |
@@ -60,10 +57,15 @@ const storage = multer.diskStorage({ | |||
60 | if (file.mimetype === 'video/webm') extension = 'webm' | 57 | if (file.mimetype === 'video/webm') extension = 'webm' |
61 | else if (file.mimetype === 'video/mp4') extension = 'mp4' | 58 | else if (file.mimetype === 'video/mp4') extension = 'mp4' |
62 | else if (file.mimetype === 'video/ogg') extension = 'ogv' | 59 | else if (file.mimetype === 'video/ogg') extension = 'ogv' |
63 | generateRandomString(16, function (err, randomString) { | 60 | generateRandomString(16) |
64 | const fieldname = err ? undefined : randomString | 61 | .then(randomString => { |
65 | cb(null, fieldname + '.' + extension) | 62 | const filename = randomString |
66 | }) | 63 | cb(null, filename + '.' + extension) |
64 | }) | ||
65 | .catch(err => { | ||
66 | logger.error('Cannot generate random string for file name.', { error: err }) | ||
67 | throw err | ||
68 | }) | ||
67 | } | 69 | } |
68 | }) | 70 | }) |
69 | 71 | ||
@@ -144,125 +146,97 @@ function addVideoRetryWrapper (req: express.Request, res: express.Response, next | |||
144 | errorMessage: 'Cannot insert the video with many retries.' | 146 | errorMessage: 'Cannot insert the video with many retries.' |
145 | } | 147 | } |
146 | 148 | ||
147 | retryTransactionWrapper(addVideo, options, function (err) { | 149 | retryTransactionWrapper(addVideo, options) |
148 | if (err) return next(err) | 150 | .then(() => { |
149 | 151 | // TODO : include Location of the new video -> 201 | |
150 | // TODO : include Location of the new video -> 201 | 152 | res.type('json').status(204).end() |
151 | return res.type('json').status(204).end() | 153 | }) |
152 | }) | 154 | .catch(err => next(err)) |
153 | } | 155 | } |
154 | 156 | ||
155 | function addVideo (req: express.Request, res: express.Response, videoFile: Express.Multer.File, finalCallback: (err: Error) => void) { | 157 | function addVideo (req: express.Request, res: express.Response, videoFile: Express.Multer.File) { |
156 | const videoInfos = req.body | 158 | const videoInfos = req.body |
157 | 159 | ||
158 | waterfall([ | 160 | return db.sequelize.transaction(t => { |
159 | 161 | const user = res.locals.oauth.token.User | |
160 | startSerializableTransaction, | ||
161 | 162 | ||
162 | function findOrCreateAuthor (t, callback) { | 163 | const name = user.username |
163 | const user = res.locals.oauth.token.User | 164 | // null because it is OUR pod |
165 | const podId = null | ||
166 | const userId = user.id | ||
164 | 167 | ||
165 | const name = user.username | 168 | return db.Author.findOrCreateAuthor(name, podId, userId, t) |
166 | // null because it is OUR pod | 169 | .then(author => { |
167 | const podId = null | 170 | const tags = videoInfos.tags |
168 | const userId = user.id | 171 | if (!tags) return { author, tagInstances: undefined } |
169 | 172 | ||
170 | db.Author.findOrCreateAuthor(name, podId, userId, t, function (err, authorInstance) { | 173 | return db.Tag.findOrCreateTags(tags, t).then(tagInstances => ({ author, tagInstances })) |
171 | return callback(err, t, authorInstance) | ||
172 | }) | 174 | }) |
173 | }, | 175 | .then(({ author, tagInstances }) => { |
174 | 176 | const videoData = { | |
175 | function findOrCreateTags (t, author, callback) { | 177 | name: videoInfos.name, |
176 | const tags = videoInfos.tags | 178 | remoteId: null, |
177 | 179 | extname: path.extname(videoFile.filename), | |
178 | db.Tag.findOrCreateTags(tags, t, function (err, tagInstances) { | 180 | category: videoInfos.category, |
179 | return callback(err, t, author, tagInstances) | 181 | licence: videoInfos.licence, |
182 | language: videoInfos.language, | ||
183 | nsfw: videoInfos.nsfw, | ||
184 | description: videoInfos.description, | ||
185 | duration: videoFile['duration'], // duration was added by a previous middleware | ||
186 | authorId: author.id | ||
187 | } | ||
188 | |||
189 | const video = db.Video.build(videoData) | ||
190 | return { author, tagInstances, video } | ||
180 | }) | 191 | }) |
181 | }, | 192 | .then(({ author, tagInstances, video }) => { |
182 | 193 | const videoDir = CONFIG.STORAGE.VIDEOS_DIR | |
183 | function createVideoObject (t, author, tagInstances, callback) { | 194 | const source = path.join(videoDir, videoFile.filename) |
184 | const videoData = { | 195 | const destination = path.join(videoDir, video.getVideoFilename()) |
185 | name: videoInfos.name, | 196 | |
186 | remoteId: null, | 197 | return renamePromise(source, destination) |
187 | extname: path.extname(videoFile.filename), | 198 | .then(() => { |
188 | category: videoInfos.category, | 199 | // This is important in case if there is another attempt in the retry process |
189 | licence: videoInfos.licence, | 200 | videoFile.filename = video.getVideoFilename() |
190 | language: videoInfos.language, | 201 | return { author, tagInstances, video } |
191 | nsfw: videoInfos.nsfw, | 202 | }) |
192 | description: videoInfos.description, | ||
193 | duration: videoFile['duration'], // duration was added by a previous middleware | ||
194 | authorId: author.id | ||
195 | } | ||
196 | |||
197 | const video = db.Video.build(videoData) | ||
198 | |||
199 | return callback(null, t, author, tagInstances, video) | ||
200 | }, | ||
201 | |||
202 | // Set the videoname the same as the id | ||
203 | function renameVideoFile (t, author, tagInstances, video, callback) { | ||
204 | const videoDir = CONFIG.STORAGE.VIDEOS_DIR | ||
205 | const source = path.join(videoDir, videoFile.filename) | ||
206 | const destination = path.join(videoDir, video.getVideoFilename()) | ||
207 | |||
208 | fs.rename(source, destination, function (err) { | ||
209 | if (err) return callback(err) | ||
210 | |||
211 | // This is important in case if there is another attempt | ||
212 | videoFile.filename = video.getVideoFilename() | ||
213 | return callback(null, t, author, tagInstances, video) | ||
214 | }) | 203 | }) |
215 | }, | 204 | .then(({ author, tagInstances, video }) => { |
216 | 205 | const options = { transaction: t } | |
217 | function insertVideoIntoDB (t, author, tagInstances, video, callback) { | ||
218 | const options = { transaction: t } | ||
219 | |||
220 | // Add tags association | ||
221 | video.save(options).asCallback(function (err, videoCreated) { | ||
222 | if (err) return callback(err) | ||
223 | 206 | ||
224 | // Do not forget to add Author informations to the created video | 207 | return video.save(options) |
225 | videoCreated.Author = author | 208 | .then(videoCreated => { |
209 | // Do not forget to add Author informations to the created video | ||
210 | videoCreated.Author = author | ||
226 | 211 | ||
227 | return callback(err, t, tagInstances, videoCreated) | 212 | return { tagInstances, video: videoCreated } |
213 | }) | ||
228 | }) | 214 | }) |
229 | }, | 215 | .then(({ tagInstances, video }) => { |
230 | 216 | if (!tagInstances) return video | |
231 | function associateTagsToVideo (t, tagInstances, video, callback) { | ||
232 | const options = { transaction: t } | ||
233 | 217 | ||
234 | video.setTags(tagInstances, options).asCallback(function (err) { | 218 | const options = { transaction: t } |
235 | video.Tags = tagInstances | 219 | return video.setTags(tagInstances, options) |
236 | 220 | .then(() => { | |
237 | return callback(err, t, video) | 221 | video.Tags = tagInstances |
222 | return video | ||
223 | }) | ||
238 | }) | 224 | }) |
239 | }, | 225 | .then(video => { |
240 | 226 | // Let transcoding job send the video to friends because the videofile extension might change | |
241 | function sendToFriends (t, video, callback) { | 227 | if (CONFIG.TRANSCODING.ENABLED === true) return undefined |
242 | // Let transcoding job send the video to friends because the videofile extension might change | 228 | |
243 | if (CONFIG.TRANSCODING.ENABLED === true) return callback(null, t) | 229 | return video.toAddRemoteJSON() |
244 | 230 | .then(remoteVideo => { | |
245 | video.toAddRemoteJSON(function (err, remoteVideo) { | 231 | // Now we'll add the video's meta data to our friends |
246 | if (err) return callback(err) | 232 | return addVideoToFriends(remoteVideo, t) |
247 | 233 | }) | |
248 | // Now we'll add the video's meta data to our friends | ||
249 | addVideoToFriends(remoteVideo, t, function (err) { | ||
250 | return callback(err, t) | ||
251 | }) | ||
252 | }) | 234 | }) |
253 | }, | 235 | }) |
254 | 236 | .then(() => logger.info('Video with name %s created.', videoInfos.name)) | |
255 | commitTransaction | 237 | .catch((err: Error) => { |
256 | 238 | logger.debug('Cannot insert the video.', { error: err.stack }) | |
257 | ], function andFinally (err: Error, t: Sequelize.Transaction) { | 239 | throw err |
258 | if (err) { | ||
259 | // This is just a debug because we will retry the insert | ||
260 | logger.debug('Cannot insert the video.', { error: err }) | ||
261 | return rollbackTransaction(err, t, finalCallback) | ||
262 | } | ||
263 | |||
264 | logger.info('Video with name %s created.', videoInfos.name) | ||
265 | return finalCallback(null) | ||
266 | }) | 240 | }) |
267 | } | 241 | } |
268 | 242 | ||
@@ -272,92 +246,75 @@ function updateVideoRetryWrapper (req: express.Request, res: express.Response, n | |||
272 | errorMessage: 'Cannot update the video with many retries.' | 246 | errorMessage: 'Cannot update the video with many retries.' |
273 | } | 247 | } |
274 | 248 | ||
275 | retryTransactionWrapper(updateVideo, options, function (err) { | 249 | retryTransactionWrapper(updateVideo, options) |
276 | if (err) return next(err) | 250 | .then(() => { |
277 | 251 | // TODO : include Location of the new video -> 201 | |
278 | // TODO : include Location of the new video -> 201 | 252 | return res.type('json').status(204).end() |
279 | return res.type('json').status(204).end() | 253 | }) |
280 | }) | 254 | .catch(err => next(err)) |
281 | } | 255 | } |
282 | 256 | ||
283 | function updateVideo (req: express.Request, res: express.Response, finalCallback: (err: Error) => void) { | 257 | function updateVideo (req: express.Request, res: express.Response) { |
284 | const videoInstance = res.locals.video | 258 | const videoInstance = res.locals.video |
285 | const videoFieldsSave = videoInstance.toJSON() | 259 | const videoFieldsSave = videoInstance.toJSON() |
286 | const videoInfosToUpdate = req.body | 260 | const videoInfosToUpdate = req.body |
287 | 261 | ||
288 | waterfall([ | 262 | return db.sequelize.transaction(t => { |
289 | 263 | let tagsPromise: Promise<TagInstance[]> | |
290 | startSerializableTransaction, | 264 | if (!videoInfosToUpdate.tags) { |
291 | 265 | tagsPromise = Promise.resolve(null) | |
292 | function findOrCreateTags (t, callback) { | 266 | } else { |
293 | if (videoInfosToUpdate.tags) { | 267 | tagsPromise = db.Tag.findOrCreateTags(videoInfosToUpdate.tags, t) |
294 | db.Tag.findOrCreateTags(videoInfosToUpdate.tags, t, function (err, tagInstances) { | 268 | } |
295 | return callback(err, t, tagInstances) | ||
296 | }) | ||
297 | } else { | ||
298 | return callback(null, t, null) | ||
299 | } | ||
300 | }, | ||
301 | |||
302 | function updateVideoIntoDB (t, tagInstances, callback) { | ||
303 | const options = { | ||
304 | transaction: t | ||
305 | } | ||
306 | |||
307 | if (videoInfosToUpdate.name !== undefined) videoInstance.set('name', videoInfosToUpdate.name) | ||
308 | if (videoInfosToUpdate.category !== undefined) videoInstance.set('category', videoInfosToUpdate.category) | ||
309 | if (videoInfosToUpdate.licence !== undefined) videoInstance.set('licence', videoInfosToUpdate.licence) | ||
310 | if (videoInfosToUpdate.language !== undefined) videoInstance.set('language', videoInfosToUpdate.language) | ||
311 | if (videoInfosToUpdate.nsfw !== undefined) videoInstance.set('nsfw', videoInfosToUpdate.nsfw) | ||
312 | if (videoInfosToUpdate.description !== undefined) videoInstance.set('description', videoInfosToUpdate.description) | ||
313 | |||
314 | videoInstance.save(options).asCallback(function (err) { | ||
315 | return callback(err, t, tagInstances) | ||
316 | }) | ||
317 | }, | ||
318 | |||
319 | function associateTagsToVideo (t, tagInstances, callback) { | ||
320 | if (tagInstances) { | ||
321 | const options = { transaction: t } | ||
322 | |||
323 | videoInstance.setTags(tagInstances, options).asCallback(function (err) { | ||
324 | videoInstance.Tags = tagInstances | ||
325 | 269 | ||
326 | return callback(err, t) | 270 | return tagsPromise |
327 | }) | 271 | .then(tagInstances => { |
328 | } else { | 272 | const options = { |
329 | return callback(null, t) | 273 | transaction: t |
330 | } | 274 | } |
331 | }, | ||
332 | 275 | ||
333 | function sendToFriends (t, callback) { | 276 | if (videoInfosToUpdate.name !== undefined) videoInstance.set('name', videoInfosToUpdate.name) |
334 | const json = videoInstance.toUpdateRemoteJSON() | 277 | if (videoInfosToUpdate.category !== undefined) videoInstance.set('category', videoInfosToUpdate.category) |
278 | if (videoInfosToUpdate.licence !== undefined) videoInstance.set('licence', videoInfosToUpdate.licence) | ||
279 | if (videoInfosToUpdate.language !== undefined) videoInstance.set('language', videoInfosToUpdate.language) | ||
280 | if (videoInfosToUpdate.nsfw !== undefined) videoInstance.set('nsfw', videoInfosToUpdate.nsfw) | ||
281 | if (videoInfosToUpdate.description !== undefined) videoInstance.set('description', videoInfosToUpdate.description) | ||
335 | 282 | ||
336 | // Now we'll update the video's meta data to our friends | 283 | return videoInstance.save(options).then(() => tagInstances) |
337 | updateVideoToFriends(json, t, function (err) { | ||
338 | return callback(err, t) | ||
339 | }) | 284 | }) |
340 | }, | 285 | .then(tagInstances => { |
341 | 286 | if (!tagInstances) return | |
342 | commitTransaction | ||
343 | 287 | ||
344 | ], function andFinally (err: Error, t: Sequelize.Transaction) { | 288 | const options = { transaction: t } |
345 | if (err) { | 289 | return videoInstance.setTags(tagInstances, options) |
346 | logger.debug('Cannot update the video.', { error: err }) | 290 | .then(() => { |
291 | videoInstance.Tags = tagInstances | ||
347 | 292 | ||
348 | // Force fields we want to update | 293 | return |
349 | // If the transaction is retried, sequelize will think the object has not changed | 294 | }) |
350 | // So it will skip the SQL request, even if the last one was ROLLBACKed! | ||
351 | Object.keys(videoFieldsSave).forEach(function (key) { | ||
352 | const value = videoFieldsSave[key] | ||
353 | videoInstance.set(key, value) | ||
354 | }) | 295 | }) |
296 | .then(() => { | ||
297 | const json = videoInstance.toUpdateRemoteJSON() | ||
355 | 298 | ||
356 | return rollbackTransaction(err, t, finalCallback) | 299 | // Now we'll update the video's meta data to our friends |
357 | } | 300 | return updateVideoToFriends(json, t) |
358 | 301 | }) | |
302 | }) | ||
303 | .then(() => { | ||
359 | logger.info('Video with name %s updated.', videoInstance.name) | 304 | logger.info('Video with name %s updated.', videoInstance.name) |
360 | return finalCallback(null) | 305 | }) |
306 | .catch(err => { | ||
307 | logger.debug('Cannot update the video.', { error: err }) | ||
308 | |||
309 | // Force fields we want to update | ||
310 | // If the transaction is retried, sequelize will think the object has not changed | ||
311 | // So it will skip the SQL request, even if the last one was ROLLBACKed! | ||
312 | Object.keys(videoFieldsSave).forEach(function (key) { | ||
313 | const value = videoFieldsSave[key] | ||
314 | videoInstance.set(key, value) | ||
315 | }) | ||
316 | |||
317 | throw err | ||
361 | }) | 318 | }) |
362 | } | 319 | } |
363 | 320 | ||
@@ -366,20 +323,17 @@ function getVideo (req: express.Request, res: express.Response, next: express.Ne | |||
366 | 323 | ||
367 | if (videoInstance.isOwned()) { | 324 | if (videoInstance.isOwned()) { |
368 | // The increment is done directly in the database, not using the instance value | 325 | // The increment is done directly in the database, not using the instance value |
369 | videoInstance.increment('views').asCallback(function (err) { | 326 | videoInstance.increment('views') |
370 | if (err) { | 327 | .then(() => { |
371 | logger.error('Cannot add view to video %d.', videoInstance.id) | 328 | // FIXME: make a real view system |
372 | return | 329 | // For example, only add a view when a user watch a video during 30s etc |
373 | } | 330 | const qaduParams = { |
374 | 331 | videoId: videoInstance.id, | |
375 | // FIXME: make a real view system | 332 | type: REQUEST_VIDEO_QADU_TYPES.VIEWS |
376 | // For example, only add a view when a user watch a video during 30s etc | 333 | } |
377 | const qaduParams = { | 334 | return quickAndDirtyUpdateVideoToFriends(qaduParams) |
378 | videoId: videoInstance.id, | 335 | }) |
379 | type: REQUEST_VIDEO_QADU_TYPES.VIEWS | 336 | .catch(err => logger.error('Cannot add view to video %d.', videoInstance.id, { error: err })) |
380 | } | ||
381 | quickAndDirtyUpdateVideoToFriends(qaduParams) | ||
382 | }) | ||
383 | } else { | 337 | } else { |
384 | // Just send the event to our friends | 338 | // Just send the event to our friends |
385 | const eventParams = { | 339 | const eventParams = { |
@@ -394,33 +348,24 @@ function getVideo (req: express.Request, res: express.Response, next: express.Ne | |||
394 | } | 348 | } |
395 | 349 | ||
396 | function listVideos (req: express.Request, res: express.Response, next: express.NextFunction) { | 350 | function listVideos (req: express.Request, res: express.Response, next: express.NextFunction) { |
397 | db.Video.listForApi(req.query.start, req.query.count, req.query.sort, function (err, videosList, videosTotal) { | 351 | db.Video.listForApi(req.query.start, req.query.count, req.query.sort) |
398 | if (err) return next(err) | 352 | .then(result => res.json(getFormatedObjects(result.data, result.total))) |
399 | 353 | .catch(err => next(err)) | |
400 | res.json(getFormatedObjects(videosList, videosTotal)) | ||
401 | }) | ||
402 | } | 354 | } |
403 | 355 | ||
404 | function removeVideo (req: express.Request, res: express.Response, next: express.NextFunction) { | 356 | function removeVideo (req: express.Request, res: express.Response, next: express.NextFunction) { |
405 | const videoInstance = res.locals.video | 357 | const videoInstance = res.locals.video |
406 | 358 | ||
407 | videoInstance.destroy().asCallback(function (err) { | 359 | videoInstance.destroy() |
408 | if (err) { | 360 | .then(() => res.type('json').status(204).end()) |
361 | .catch(err => { | ||
409 | logger.error('Errors when removed the video.', { error: err }) | 362 | logger.error('Errors when removed the video.', { error: err }) |
410 | return next(err) | 363 | return next(err) |
411 | } | 364 | }) |
412 | |||
413 | return res.type('json').status(204).end() | ||
414 | }) | ||
415 | } | 365 | } |
416 | 366 | ||
417 | function searchVideos (req: express.Request, res: express.Response, next: express.NextFunction) { | 367 | function searchVideos (req: express.Request, res: express.Response, next: express.NextFunction) { |
418 | db.Video.searchAndPopulateAuthorAndPodAndTags( | 368 | db.Video.searchAndPopulateAuthorAndPodAndTags(req.params.value, req.query.field, req.query.start, req.query.count, req.query.sort) |
419 | req.params.value, req.query.field, req.query.start, req.query.count, req.query.sort, | 369 | .then(result => res.json(getFormatedObjects(result.data, result.total))) |
420 | function (err, videosList, videosTotal) { | 370 | .catch(err => next(err)) |
421 | if (err) return next(err) | ||
422 | |||
423 | res.json(getFormatedObjects(videosList, videosTotal)) | ||
424 | } | ||
425 | ) | ||
426 | } | 371 | } |
diff --git a/server/controllers/api/videos/rate.ts b/server/controllers/api/videos/rate.ts index afdd099f8..3d119d98b 100644 --- a/server/controllers/api/videos/rate.ts +++ b/server/controllers/api/videos/rate.ts | |||
@@ -1,14 +1,9 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import * as Sequelize from 'sequelize' | ||
3 | import { waterfall } from 'async' | ||
4 | 2 | ||
5 | import { database as db } from '../../../initializers/database' | 3 | import { database as db } from '../../../initializers/database' |
6 | import { | 4 | import { |
7 | logger, | 5 | logger, |
8 | retryTransactionWrapper, | 6 | retryTransactionWrapper |
9 | startSerializableTransaction, | ||
10 | commitTransaction, | ||
11 | rollbackTransaction | ||
12 | } from '../../../helpers' | 7 | } from '../../../helpers' |
13 | import { | 8 | import { |
14 | VIDEO_RATE_TYPES, | 9 | VIDEO_RATE_TYPES, |
@@ -46,137 +41,109 @@ function rateVideoRetryWrapper (req: express.Request, res: express.Response, nex | |||
46 | errorMessage: 'Cannot update the user video rate.' | 41 | errorMessage: 'Cannot update the user video rate.' |
47 | } | 42 | } |
48 | 43 | ||
49 | retryTransactionWrapper(rateVideo, options, function (err) { | 44 | retryTransactionWrapper(rateVideo, options) |
50 | if (err) return next(err) | 45 | .then(() => res.type('json').status(204).end()) |
51 | 46 | .catch(err => next(err)) | |
52 | return res.type('json').status(204).end() | ||
53 | }) | ||
54 | } | 47 | } |
55 | 48 | ||
56 | function rateVideo (req: express.Request, res: express.Response, finalCallback: (err: Error) => void) { | 49 | function rateVideo (req: express.Request, res: express.Response) { |
57 | const rateType = req.body.rating | 50 | const rateType = req.body.rating |
58 | const videoInstance = res.locals.video | 51 | const videoInstance = res.locals.video |
59 | const userInstance = res.locals.oauth.token.User | 52 | const userInstance = res.locals.oauth.token.User |
60 | 53 | ||
61 | waterfall([ | 54 | return db.sequelize.transaction(t => { |
62 | startSerializableTransaction, | 55 | return db.UserVideoRate.load(userInstance.id, videoInstance.id, t) |
63 | 56 | .then(previousRate => { | |
64 | function findPreviousRate (t, callback) { | 57 | const options = { transaction: t } |
65 | db.UserVideoRate.load(userInstance.id, videoInstance.id, t, function (err, previousRate) { | ||
66 | return callback(err, t, previousRate) | ||
67 | }) | ||
68 | }, | ||
69 | 58 | ||
70 | function insertUserRateIntoDB (t, previousRate, callback) { | 59 | let likesToIncrement = 0 |
71 | const options = { transaction: t } | 60 | let dislikesToIncrement = 0 |
72 | 61 | ||
73 | let likesToIncrement = 0 | 62 | if (rateType === VIDEO_RATE_TYPES.LIKE) likesToIncrement++ |
74 | let dislikesToIncrement = 0 | 63 | else if (rateType === VIDEO_RATE_TYPES.DISLIKE) dislikesToIncrement++ |
75 | 64 | ||
76 | if (rateType === VIDEO_RATE_TYPES.LIKE) likesToIncrement++ | 65 | // There was a previous rate, update it |
77 | else if (rateType === VIDEO_RATE_TYPES.DISLIKE) dislikesToIncrement++ | 66 | if (previousRate) { |
67 | // We will remove the previous rate, so we will need to remove it from the video attribute | ||
68 | if (previousRate.type === VIDEO_RATE_TYPES.LIKE) likesToIncrement-- | ||
69 | else if (previousRate.type === VIDEO_RATE_TYPES.DISLIKE) dislikesToIncrement-- | ||
78 | 70 | ||
79 | // There was a previous rate, update it | 71 | previousRate.type = rateType |
80 | if (previousRate) { | ||
81 | // We will remove the previous rate, so we will need to remove it from the video attribute | ||
82 | if (previousRate.type === VIDEO_RATE_TYPES.LIKE) likesToIncrement-- | ||
83 | else if (previousRate.type === VIDEO_RATE_TYPES.DISLIKE) dislikesToIncrement-- | ||
84 | 72 | ||
85 | previousRate.type = rateType | 73 | return previousRate.save(options).then(() => ({ t, likesToIncrement, dislikesToIncrement })) |
74 | } else { // There was not a previous rate, insert a new one | ||
75 | const query = { | ||
76 | userId: userInstance.id, | ||
77 | videoId: videoInstance.id, | ||
78 | type: rateType | ||
79 | } | ||
86 | 80 | ||
87 | previousRate.save(options).asCallback(function (err) { | 81 | return db.UserVideoRate.create(query, options).then(() => ({ likesToIncrement, dislikesToIncrement })) |
88 | return callback(err, t, likesToIncrement, dislikesToIncrement) | ||
89 | }) | ||
90 | } else { // There was not a previous rate, insert a new one | ||
91 | const query = { | ||
92 | userId: userInstance.id, | ||
93 | videoId: videoInstance.id, | ||
94 | type: rateType | ||
95 | } | 82 | } |
96 | |||
97 | db.UserVideoRate.create(query, options).asCallback(function (err) { | ||
98 | return callback(err, t, likesToIncrement, dislikesToIncrement) | ||
99 | }) | ||
100 | } | ||
101 | }, | ||
102 | |||
103 | function updateVideoAttributeDB (t, likesToIncrement, dislikesToIncrement, callback) { | ||
104 | const options = { transaction: t } | ||
105 | const incrementQuery = { | ||
106 | likes: likesToIncrement, | ||
107 | dislikes: dislikesToIncrement | ||
108 | } | ||
109 | |||
110 | // Even if we do not own the video we increment the attributes | ||
111 | // It is usefull for the user to have a feedback | ||
112 | videoInstance.increment(incrementQuery, options).asCallback(function (err) { | ||
113 | return callback(err, t, likesToIncrement, dislikesToIncrement) | ||
114 | }) | ||
115 | }, | ||
116 | |||
117 | function sendEventsToFriendsIfNeeded (t, likesToIncrement, dislikesToIncrement, callback) { | ||
118 | // No need for an event type, we own the video | ||
119 | if (videoInstance.isOwned()) return callback(null, t, likesToIncrement, dislikesToIncrement) | ||
120 | |||
121 | const eventsParams = [] | ||
122 | |||
123 | if (likesToIncrement !== 0) { | ||
124 | eventsParams.push({ | ||
125 | videoId: videoInstance.id, | ||
126 | type: REQUEST_VIDEO_EVENT_TYPES.LIKES, | ||
127 | count: likesToIncrement | ||
128 | }) | ||
129 | } | ||
130 | |||
131 | if (dislikesToIncrement !== 0) { | ||
132 | eventsParams.push({ | ||
133 | videoId: videoInstance.id, | ||
134 | type: REQUEST_VIDEO_EVENT_TYPES.DISLIKES, | ||
135 | count: dislikesToIncrement | ||
136 | }) | ||
137 | } | ||
138 | |||
139 | addEventsToRemoteVideo(eventsParams, t, function (err) { | ||
140 | return callback(err, t, likesToIncrement, dislikesToIncrement) | ||
141 | }) | 83 | }) |
142 | }, | 84 | .then(({ likesToIncrement, dislikesToIncrement }) => { |
143 | 85 | const options = { transaction: t } | |
144 | function sendQaduToFriendsIfNeeded (t, likesToIncrement, dislikesToIncrement, callback) { | 86 | const incrementQuery = { |
145 | // We do not own the video, there is no need to send a quick and dirty update to friends | 87 | likes: likesToIncrement, |
146 | // Our rate was already sent by the addEvent function | 88 | dislikes: dislikesToIncrement |
147 | if (videoInstance.isOwned() === false) return callback(null, t) | 89 | } |
148 | 90 | ||
149 | const qadusParams = [] | 91 | // Even if we do not own the video we increment the attributes |
150 | 92 | // It is usefull for the user to have a feedback | |
151 | if (likesToIncrement !== 0) { | 93 | return videoInstance.increment(incrementQuery, options).then(() => ({ likesToIncrement, dislikesToIncrement })) |
152 | qadusParams.push({ | ||
153 | videoId: videoInstance.id, | ||
154 | type: REQUEST_VIDEO_QADU_TYPES.LIKES | ||
155 | }) | ||
156 | } | ||
157 | |||
158 | if (dislikesToIncrement !== 0) { | ||
159 | qadusParams.push({ | ||
160 | videoId: videoInstance.id, | ||
161 | type: REQUEST_VIDEO_QADU_TYPES.DISLIKES | ||
162 | }) | ||
163 | } | ||
164 | |||
165 | quickAndDirtyUpdatesVideoToFriends(qadusParams, t, function (err) { | ||
166 | return callback(err, t) | ||
167 | }) | 94 | }) |
168 | }, | 95 | .then(({ likesToIncrement, dislikesToIncrement }) => { |
96 | // No need for an event type, we own the video | ||
97 | if (videoInstance.isOwned()) return { likesToIncrement, dislikesToIncrement } | ||
98 | |||
99 | const eventsParams = [] | ||
100 | |||
101 | if (likesToIncrement !== 0) { | ||
102 | eventsParams.push({ | ||
103 | videoId: videoInstance.id, | ||
104 | type: REQUEST_VIDEO_EVENT_TYPES.LIKES, | ||
105 | count: likesToIncrement | ||
106 | }) | ||
107 | } | ||
108 | |||
109 | if (dislikesToIncrement !== 0) { | ||
110 | eventsParams.push({ | ||
111 | videoId: videoInstance.id, | ||
112 | type: REQUEST_VIDEO_EVENT_TYPES.DISLIKES, | ||
113 | count: dislikesToIncrement | ||
114 | }) | ||
115 | } | ||
169 | 116 | ||
170 | commitTransaction | 117 | return addEventsToRemoteVideo(eventsParams, t).then(() => ({ likesToIncrement, dislikesToIncrement })) |
118 | }) | ||
119 | .then(({ likesToIncrement, dislikesToIncrement }) => { | ||
120 | // We do not own the video, there is no need to send a quick and dirty update to friends | ||
121 | // Our rate was already sent by the addEvent function | ||
122 | if (videoInstance.isOwned() === false) return undefined | ||
123 | |||
124 | const qadusParams = [] | ||
125 | |||
126 | if (likesToIncrement !== 0) { | ||
127 | qadusParams.push({ | ||
128 | videoId: videoInstance.id, | ||
129 | type: REQUEST_VIDEO_QADU_TYPES.LIKES | ||
130 | }) | ||
131 | } | ||
171 | 132 | ||
172 | ], function (err: Error, t: Sequelize.Transaction) { | 133 | if (dislikesToIncrement !== 0) { |
173 | if (err) { | 134 | qadusParams.push({ |
174 | // This is just a debug because we will retry the insert | 135 | videoId: videoInstance.id, |
175 | logger.debug('Cannot add the user video rate.', { error: err }) | 136 | type: REQUEST_VIDEO_QADU_TYPES.DISLIKES |
176 | return rollbackTransaction(err, t, finalCallback) | 137 | }) |
177 | } | 138 | } |
178 | 139 | ||
179 | logger.info('User video rate for video %s of user %s updated.', videoInstance.name, userInstance.username) | 140 | return quickAndDirtyUpdatesVideoToFriends(qadusParams, t) |
180 | return finalCallback(null) | 141 | }) |
142 | }) | ||
143 | .then(() => logger.info('User video rate for video %s of user %s updated.', videoInstance.name, userInstance.username)) | ||
144 | .catch(err => { | ||
145 | // This is just a debug because we will retry the insert | ||
146 | logger.debug('Cannot add the user video rate.', { error: err }) | ||
147 | throw err | ||
181 | }) | 148 | }) |
182 | } | 149 | } |
diff --git a/server/controllers/client.ts b/server/controllers/client.ts index 503eff750..e4d69eae7 100644 --- a/server/controllers/client.ts +++ b/server/controllers/client.ts | |||
@@ -1,8 +1,7 @@ | |||
1 | import { parallel } from 'async' | ||
2 | import * as express from 'express' | 1 | import * as express from 'express' |
3 | import * as fs from 'fs' | ||
4 | import { join } from 'path' | 2 | import { join } from 'path' |
5 | import * as validator from 'validator' | 3 | import * as validator from 'validator' |
4 | import * as Promise from 'bluebird' | ||
6 | 5 | ||
7 | import { database as db } from '../initializers/database' | 6 | import { database as db } from '../initializers/database' |
8 | import { | 7 | import { |
@@ -11,7 +10,7 @@ import { | |||
11 | STATIC_PATHS, | 10 | STATIC_PATHS, |
12 | STATIC_MAX_AGE | 11 | STATIC_MAX_AGE |
13 | } from '../initializers' | 12 | } from '../initializers' |
14 | import { root } from '../helpers' | 13 | import { root, readFileBufferPromise } from '../helpers' |
15 | import { VideoInstance } from '../models' | 14 | import { VideoInstance } from '../models' |
16 | 15 | ||
17 | const clientsRouter = express.Router() | 16 | const clientsRouter = express.Router() |
@@ -95,19 +94,15 @@ function generateWatchHtmlPage (req: express.Request, res: express.Response, nex | |||
95 | // Let Angular application handle errors | 94 | // Let Angular application handle errors |
96 | if (!validator.isUUID(videoId, 4)) return res.sendFile(indexPath) | 95 | if (!validator.isUUID(videoId, 4)) return res.sendFile(indexPath) |
97 | 96 | ||
98 | parallel({ | 97 | Promise.all([ |
99 | file: function (callback) { | 98 | readFileBufferPromise(indexPath), |
100 | fs.readFile(indexPath, callback) | 99 | db.Video.loadAndPopulateAuthorAndPodAndTags(videoId) |
101 | }, | 100 | ]) |
101 | .then(([ file, video ]) => { | ||
102 | file = file as Buffer | ||
103 | video = video as VideoInstance | ||
102 | 104 | ||
103 | video: function (callback) { | 105 | const html = file.toString() |
104 | db.Video.loadAndPopulateAuthorAndPodAndTags(videoId, callback) | ||
105 | } | ||
106 | }, function (err: Error, result: { file: Buffer, video: VideoInstance }) { | ||
107 | if (err) return next(err) | ||
108 | |||
109 | const html = result.file.toString() | ||
110 | const video = result.video | ||
111 | 106 | ||
112 | // Let Angular application handle errors | 107 | // Let Angular application handle errors |
113 | if (!video) return res.sendFile(indexPath) | 108 | if (!video) return res.sendFile(indexPath) |
@@ -115,4 +110,5 @@ function generateWatchHtmlPage (req: express.Request, res: express.Response, nex | |||
115 | const htmlStringPageWithTags = addOpenGraphTags(html, video) | 110 | const htmlStringPageWithTags = addOpenGraphTags(html, video) |
116 | res.set('Content-Type', 'text/html; charset=UTF-8').send(htmlStringPageWithTags) | 111 | res.set('Content-Type', 'text/html; charset=UTF-8').send(htmlStringPageWithTags) |
117 | }) | 112 | }) |
113 | .catch(err => next(err)) | ||
118 | } | 114 | } |
diff --git a/server/helpers/core-utils.ts b/server/helpers/core-utils.ts index 32b89b6dd..1e92049f1 100644 --- a/server/helpers/core-utils.ts +++ b/server/helpers/core-utils.ts | |||
@@ -4,6 +4,20 @@ | |||
4 | */ | 4 | */ |
5 | 5 | ||
6 | import { join } from 'path' | 6 | import { join } from 'path' |
7 | import { pseudoRandomBytes } from 'crypto' | ||
8 | import { | ||
9 | readdir, | ||
10 | readFile, | ||
11 | rename, | ||
12 | unlink, | ||
13 | writeFile, | ||
14 | access | ||
15 | } from 'fs' | ||
16 | import * as mkdirp from 'mkdirp' | ||
17 | import * as bcrypt from 'bcrypt' | ||
18 | import * as createTorrent from 'create-torrent' | ||
19 | import * as openssl from 'openssl-wrapper' | ||
20 | import * as Promise from 'bluebird' | ||
7 | 21 | ||
8 | function isTestInstance () { | 22 | function isTestInstance () { |
9 | return process.env.NODE_ENV === 'test' | 23 | return process.env.NODE_ENV === 'test' |
@@ -14,9 +28,82 @@ function root () { | |||
14 | return join(__dirname, '..', '..', '..') | 28 | return join(__dirname, '..', '..', '..') |
15 | } | 29 | } |
16 | 30 | ||
31 | function promisify0<A> (func: (cb: (err: any, result: A) => void) => void): () => Promise<A> { | ||
32 | return function promisified (): Promise<A> { | ||
33 | return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => { | ||
34 | func.apply(null, [ (err: any, res: A) => err ? reject(err) : resolve(res) ]) | ||
35 | }) | ||
36 | } | ||
37 | } | ||
38 | |||
39 | // Thanks to https://gist.github.com/kumasento/617daa7e46f13ecdd9b2 | ||
40 | function promisify1<T, A> (func: (arg: T, cb: (err: any, result: A) => void) => void): (arg: T) => Promise<A> { | ||
41 | return function promisified (arg: T): Promise<A> { | ||
42 | return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => { | ||
43 | func.apply(null, [ arg, (err: any, res: A) => err ? reject(err) : resolve(res) ]) | ||
44 | }) | ||
45 | } | ||
46 | } | ||
47 | |||
48 | function promisify1WithVoid<T> (func: (arg: T, cb: (err: any) => void) => void): (arg: T) => Promise<void> { | ||
49 | return function promisified (arg: T): Promise<void> { | ||
50 | return new Promise<void>((resolve: () => void, reject: (err: any) => void) => { | ||
51 | func.apply(null, [ arg, (err: any) => err ? reject(err) : resolve() ]) | ||
52 | }) | ||
53 | } | ||
54 | } | ||
55 | |||
56 | function promisify2<T, U, A> (func: (arg1: T, arg2: U, cb: (err: any, result: A) => void) => void): (arg1: T, arg2: U) => Promise<A> { | ||
57 | return function promisified (arg1: T, arg2: U): Promise<A> { | ||
58 | return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => { | ||
59 | func.apply(null, [ arg1, arg2, (err: any, res: A) => err ? reject(err) : resolve(res) ]) | ||
60 | }) | ||
61 | } | ||
62 | } | ||
63 | |||
64 | function promisify2WithVoid<T, U> (func: (arg1: T, arg2: U, cb: (err: any) => void) => void): (arg1: T, arg2: U) => Promise<void> { | ||
65 | return function promisified (arg1: T, arg2: U): Promise<void> { | ||
66 | return new Promise<void>((resolve: () => void, reject: (err: any) => void) => { | ||
67 | func.apply(null, [ arg1, arg2, (err: any) => err ? reject(err) : resolve() ]) | ||
68 | }) | ||
69 | } | ||
70 | } | ||
71 | |||
72 | const readFilePromise = promisify2<string, string, string>(readFile) | ||
73 | const readFileBufferPromise = promisify1<string, Buffer>(readFile) | ||
74 | const unlinkPromise = promisify1WithVoid<string>(unlink) | ||
75 | const renamePromise = promisify2WithVoid<string, string>(rename) | ||
76 | const writeFilePromise = promisify2<string, any, void>(writeFile) | ||
77 | const readdirPromise = promisify1<string, string[]>(readdir) | ||
78 | const mkdirpPromise = promisify1<string, string>(mkdirp) | ||
79 | const pseudoRandomBytesPromise = promisify1<number, Buffer>(pseudoRandomBytes) | ||
80 | const accessPromise = promisify1WithVoid<string|Buffer>(access) | ||
81 | const opensslExecPromise = promisify2WithVoid<string, any>(openssl.exec) | ||
82 | const bcryptComparePromise = promisify2<any, string, boolean>(bcrypt.compare) | ||
83 | const bcryptGenSaltPromise = promisify1<number, string>(bcrypt.genSalt) | ||
84 | const bcryptHashPromise = promisify2<any, string|number, string>(bcrypt.hash) | ||
85 | const createTorrentPromise = promisify2<string, any, any>(createTorrent) | ||
86 | |||
17 | // --------------------------------------------------------------------------- | 87 | // --------------------------------------------------------------------------- |
18 | 88 | ||
19 | export { | 89 | export { |
20 | isTestInstance, | 90 | isTestInstance, |
21 | root | 91 | root, |
92 | |||
93 | promisify0, | ||
94 | promisify1, | ||
95 | readdirPromise, | ||
96 | readFilePromise, | ||
97 | readFileBufferPromise, | ||
98 | unlinkPromise, | ||
99 | renamePromise, | ||
100 | writeFilePromise, | ||
101 | mkdirpPromise, | ||
102 | pseudoRandomBytesPromise, | ||
103 | accessPromise, | ||
104 | opensslExecPromise, | ||
105 | bcryptComparePromise, | ||
106 | bcryptGenSaltPromise, | ||
107 | bcryptHashPromise, | ||
108 | createTorrentPromise | ||
22 | } | 109 | } |
diff --git a/server/helpers/database-utils.ts b/server/helpers/database-utils.ts index f8ee9a454..f761cfb89 100644 --- a/server/helpers/database-utils.ts +++ b/server/helpers/database-utils.ts | |||
@@ -1,70 +1,45 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | // TODO: import from ES6 when retry typing file will include errorFilter function | 1 | // TODO: import from ES6 when retry typing file will include errorFilter function |
3 | import * as retry from 'async/retry' | 2 | import * as retry from 'async/retry' |
3 | import * as Promise from 'bluebird' | ||
4 | 4 | ||
5 | import { database as db } from '../initializers/database' | ||
6 | import { logger } from './logger' | 5 | import { logger } from './logger' |
7 | 6 | ||
8 | function commitTransaction (t: Sequelize.Transaction, callback: (err: Error) => void) { | ||
9 | return t.commit().asCallback(callback) | ||
10 | } | ||
11 | |||
12 | function rollbackTransaction (err: Error, t: Sequelize.Transaction, callback: (err: Error) => void) { | ||
13 | // Try to rollback transaction | ||
14 | if (t) { | ||
15 | // Do not catch err, report the original one | ||
16 | t.rollback().asCallback(function () { | ||
17 | return callback(err) | ||
18 | }) | ||
19 | } else { | ||
20 | return callback(err) | ||
21 | } | ||
22 | } | ||
23 | |||
24 | type RetryTransactionWrapperOptions = { errorMessage: string, arguments?: any[] } | 7 | type RetryTransactionWrapperOptions = { errorMessage: string, arguments?: any[] } |
25 | function retryTransactionWrapper (functionToRetry: Function, options: RetryTransactionWrapperOptions, finalCallback: Function) { | 8 | function retryTransactionWrapper (functionToRetry: (... args) => Promise<any>, options: RetryTransactionWrapperOptions) { |
26 | const args = options.arguments ? options.arguments : [] | 9 | const args = options.arguments ? options.arguments : [] |
27 | 10 | ||
28 | transactionRetryer( | 11 | return transactionRetryer( |
29 | function (callback) { | 12 | function (callback) { |
30 | return functionToRetry.apply(this, args.concat([ callback ])) | 13 | functionToRetry.apply(this, args) |
31 | }, | 14 | .then(result => callback(null, result)) |
32 | function (err) { | 15 | .catch(err => callback(err)) |
33 | if (err) { | ||
34 | logger.error(options.errorMessage, { error: err }) | ||
35 | } | ||
36 | |||
37 | // Do not return the error, continue the process | ||
38 | return finalCallback(null) | ||
39 | } | 16 | } |
40 | ) | 17 | ) |
18 | .catch(err => { | ||
19 | // Do not throw the error, continue the process | ||
20 | logger.error(options.errorMessage, { error: err }) | ||
21 | }) | ||
41 | } | 22 | } |
42 | 23 | ||
43 | function transactionRetryer (func: Function, callback: (err: Error) => void) { | 24 | function transactionRetryer (func: Function) { |
44 | retry({ | 25 | return new Promise((res, rej) => { |
45 | times: 5, | 26 | retry({ |
46 | 27 | times: 5, | |
47 | errorFilter: function (err) { | ||
48 | const willRetry = (err.name === 'SequelizeDatabaseError') | ||
49 | logger.debug('Maybe retrying the transaction function.', { willRetry }) | ||
50 | return willRetry | ||
51 | } | ||
52 | }, func, callback) | ||
53 | } | ||
54 | 28 | ||
55 | function startSerializableTransaction (callback: (err: Error, t: Sequelize.Transaction) => void) { | 29 | errorFilter: function (err) { |
56 | db.sequelize.transaction(/* { isolationLevel: 'SERIALIZABLE' } */).asCallback(function (err, t) { | 30 | const willRetry = (err.name === 'SequelizeDatabaseError') |
57 | // We force to return only two parameters | 31 | logger.debug('Maybe retrying the transaction function.', { willRetry }) |
58 | return callback(err, t) | 32 | return willRetry |
33 | } | ||
34 | }, func, function (err) { | ||
35 | err ? rej(err) : res() | ||
36 | }) | ||
59 | }) | 37 | }) |
60 | } | 38 | } |
61 | 39 | ||
62 | // --------------------------------------------------------------------------- | 40 | // --------------------------------------------------------------------------- |
63 | 41 | ||
64 | export { | 42 | export { |
65 | commitTransaction, | ||
66 | retryTransactionWrapper, | 43 | retryTransactionWrapper, |
67 | rollbackTransaction, | ||
68 | startSerializableTransaction, | ||
69 | transactionRetryer | 44 | transactionRetryer |
70 | } | 45 | } |
diff --git a/server/helpers/peertube-crypto.ts b/server/helpers/peertube-crypto.ts index 0ac875127..8e8001cd6 100644 --- a/server/helpers/peertube-crypto.ts +++ b/server/helpers/peertube-crypto.ts | |||
@@ -1,7 +1,5 @@ | |||
1 | import * as crypto from 'crypto' | 1 | import * as crypto from 'crypto' |
2 | import * as bcrypt from 'bcrypt' | ||
3 | import * as fs from 'fs' | 2 | import * as fs from 'fs' |
4 | import * as openssl from 'openssl-wrapper' | ||
5 | import { join } from 'path' | 3 | import { join } from 'path' |
6 | 4 | ||
7 | import { | 5 | import { |
@@ -12,6 +10,14 @@ import { | |||
12 | BCRYPT_SALT_SIZE, | 10 | BCRYPT_SALT_SIZE, |
13 | PUBLIC_CERT_NAME | 11 | PUBLIC_CERT_NAME |
14 | } from '../initializers' | 12 | } from '../initializers' |
13 | import { | ||
14 | readFilePromise, | ||
15 | bcryptComparePromise, | ||
16 | bcryptGenSaltPromise, | ||
17 | bcryptHashPromise, | ||
18 | accessPromise, | ||
19 | opensslExecPromise | ||
20 | } from './core-utils' | ||
15 | import { logger } from './logger' | 21 | import { logger } from './logger' |
16 | 22 | ||
17 | function checkSignature (publicKey: string, data: string, hexSignature: string) { | 23 | function checkSignature (publicKey: string, data: string, hexSignature: string) { |
@@ -60,46 +66,32 @@ function sign (data: string|Object) { | |||
60 | return signature | 66 | return signature |
61 | } | 67 | } |
62 | 68 | ||
63 | function comparePassword (plainPassword: string, hashPassword: string, callback: (err: Error, match?: boolean) => void) { | 69 | function comparePassword (plainPassword: string, hashPassword: string) { |
64 | bcrypt.compare(plainPassword, hashPassword, function (err, isPasswordMatch) { | 70 | return bcryptComparePromise(plainPassword, hashPassword) |
65 | if (err) return callback(err) | ||
66 | |||
67 | return callback(null, isPasswordMatch) | ||
68 | }) | ||
69 | } | 71 | } |
70 | 72 | ||
71 | function createCertsIfNotExist (callback: (err: Error) => void) { | 73 | function createCertsIfNotExist () { |
72 | certsExist(function (err, exist) { | 74 | return certsExist().then(exist => { |
73 | if (err) return callback(err) | ||
74 | |||
75 | if (exist === true) { | 75 | if (exist === true) { |
76 | return callback(null) | 76 | return undefined |
77 | } | 77 | } |
78 | 78 | ||
79 | createCerts(function (err) { | 79 | return createCerts() |
80 | return callback(err) | ||
81 | }) | ||
82 | }) | 80 | }) |
83 | } | 81 | } |
84 | 82 | ||
85 | function cryptPassword (password: string, callback: (err: Error, hash?: string) => void) { | 83 | function cryptPassword (password: string) { |
86 | bcrypt.genSalt(BCRYPT_SALT_SIZE, function (err, salt) { | 84 | return bcryptGenSaltPromise(BCRYPT_SALT_SIZE).then(salt => bcryptHashPromise(password, salt)) |
87 | if (err) return callback(err) | ||
88 | |||
89 | bcrypt.hash(password, salt, function (err, hash) { | ||
90 | return callback(err, hash) | ||
91 | }) | ||
92 | }) | ||
93 | } | 85 | } |
94 | 86 | ||
95 | function getMyPrivateCert (callback: (err: Error, privateCert: string) => void) { | 87 | function getMyPrivateCert () { |
96 | const certPath = join(CONFIG.STORAGE.CERT_DIR, PRIVATE_CERT_NAME) | 88 | const certPath = join(CONFIG.STORAGE.CERT_DIR, PRIVATE_CERT_NAME) |
97 | fs.readFile(certPath, 'utf8', callback) | 89 | return readFilePromise(certPath, 'utf8') |
98 | } | 90 | } |
99 | 91 | ||
100 | function getMyPublicCert (callback: (err: Error, publicCert: string) => void) { | 92 | function getMyPublicCert () { |
101 | const certPath = join(CONFIG.STORAGE.CERT_DIR, PUBLIC_CERT_NAME) | 93 | const certPath = join(CONFIG.STORAGE.CERT_DIR, PUBLIC_CERT_NAME) |
102 | fs.readFile(certPath, 'utf8', callback) | 94 | return readFilePromise(certPath, 'utf8') |
103 | } | 95 | } |
104 | 96 | ||
105 | // --------------------------------------------------------------------------- | 97 | // --------------------------------------------------------------------------- |
@@ -116,23 +108,21 @@ export { | |||
116 | 108 | ||
117 | // --------------------------------------------------------------------------- | 109 | // --------------------------------------------------------------------------- |
118 | 110 | ||
119 | function certsExist (callback: (err: Error, certsExist: boolean) => void) { | 111 | function certsExist () { |
120 | const certPath = join(CONFIG.STORAGE.CERT_DIR, PRIVATE_CERT_NAME) | 112 | const certPath = join(CONFIG.STORAGE.CERT_DIR, PRIVATE_CERT_NAME) |
121 | fs.access(certPath, function (err) { | ||
122 | // If there is an error the certificates do not exist | ||
123 | const exists = !err | ||
124 | return callback(null, exists) | ||
125 | }) | ||
126 | } | ||
127 | 113 | ||
128 | function createCerts (callback: (err: Error) => void) { | 114 | // If there is an error the certificates do not exist |
129 | certsExist(function (err, exist) { | 115 | return accessPromise(certPath) |
130 | if (err) return callback(err) | 116 | .then(() => true) |
117 | .catch(() => false) | ||
118 | } | ||
131 | 119 | ||
120 | function createCerts () { | ||
121 | return certsExist().then(exist => { | ||
132 | if (exist === true) { | 122 | if (exist === true) { |
133 | const errorMessage = 'Certs already exist.' | 123 | const errorMessage = 'Certs already exist.' |
134 | logger.warning(errorMessage) | 124 | logger.warning(errorMessage) |
135 | return callback(new Error(errorMessage)) | 125 | throw new Error(errorMessage) |
136 | } | 126 | } |
137 | 127 | ||
138 | logger.info('Generating a RSA key...') | 128 | logger.info('Generating a RSA key...') |
@@ -142,30 +132,27 @@ function createCerts (callback: (err: Error) => void) { | |||
142 | 'out': privateCertPath, | 132 | 'out': privateCertPath, |
143 | '2048': false | 133 | '2048': false |
144 | } | 134 | } |
145 | openssl.exec('genrsa', genRsaOptions, function (err) { | 135 | return opensslExecPromise('genrsa', genRsaOptions) |
146 | if (err) { | 136 | .then(() => { |
147 | logger.error('Cannot create private key on this pod.') | 137 | logger.info('RSA key generated.') |
148 | return callback(err) | 138 | logger.info('Managing public key...') |
149 | } | 139 | |
150 | 140 | const publicCertPath = join(CONFIG.STORAGE.CERT_DIR, 'peertube.pub') | |
151 | logger.info('RSA key generated.') | 141 | const rsaOptions = { |
152 | logger.info('Managing public key...') | 142 | 'in': privateCertPath, |
153 | 143 | 'pubout': true, | |
154 | const publicCertPath = join(CONFIG.STORAGE.CERT_DIR, 'peertube.pub') | 144 | 'out': publicCertPath |
155 | const rsaOptions = { | ||
156 | 'in': privateCertPath, | ||
157 | 'pubout': true, | ||
158 | 'out': publicCertPath | ||
159 | } | ||
160 | openssl.exec('rsa', rsaOptions, function (err) { | ||
161 | if (err) { | ||
162 | logger.error('Cannot create public key on this pod.') | ||
163 | return callback(err) | ||
164 | } | 145 | } |
165 | 146 | return opensslExecPromise('rsa', rsaOptions) | |
166 | logger.info('Public key managed.') | 147 | .then(() => logger.info('Public key managed.')) |
167 | return callback(null) | 148 | .catch(err => { |
149 | logger.error('Cannot create public key on this pod.') | ||
150 | throw err | ||
151 | }) | ||
152 | }) | ||
153 | .catch(err => { | ||
154 | logger.error('Cannot create private key on this pod.') | ||
155 | throw err | ||
168 | }) | 156 | }) |
169 | }) | ||
170 | }) | 157 | }) |
171 | } | 158 | } |
diff --git a/server/helpers/requests.ts b/server/helpers/requests.ts index b40fc8e39..b31074373 100644 --- a/server/helpers/requests.ts +++ b/server/helpers/requests.ts | |||
@@ -1,5 +1,6 @@ | |||
1 | import * as replay from 'request-replay' | 1 | import * as replay from 'request-replay' |
2 | import * as request from 'request' | 2 | import * as request from 'request' |
3 | import * as Promise from 'bluebird' | ||
3 | 4 | ||
4 | import { | 5 | import { |
5 | RETRY_REQUESTS, | 6 | RETRY_REQUESTS, |
@@ -14,16 +15,18 @@ type MakeRetryRequestParams = { | |||
14 | method: 'GET'|'POST', | 15 | method: 'GET'|'POST', |
15 | json: Object | 16 | json: Object |
16 | } | 17 | } |
17 | function makeRetryRequest (params: MakeRetryRequestParams, callback: request.RequestCallback) { | 18 | function makeRetryRequest (params: MakeRetryRequestParams) { |
18 | replay( | 19 | return new Promise<{ response: request.RequestResponse, body: any }>((res, rej) => { |
19 | request(params, callback), | 20 | replay( |
20 | { | 21 | request(params, (err, response, body) => err ? rej(err) : res({ response, body })), |
21 | retries: RETRY_REQUESTS, | 22 | { |
22 | factor: 3, | 23 | retries: RETRY_REQUESTS, |
23 | maxTimeout: Infinity, | 24 | factor: 3, |
24 | errorCodes: [ 'EADDRINFO', 'ETIMEDOUT', 'ECONNRESET', 'ESOCKETTIMEDOUT', 'ENOTFOUND', 'ECONNREFUSED' ] | 25 | maxTimeout: Infinity, |
25 | } | 26 | errorCodes: [ 'EADDRINFO', 'ETIMEDOUT', 'ECONNRESET', 'ESOCKETTIMEDOUT', 'ENOTFOUND', 'ECONNREFUSED' ] |
26 | ) | 27 | } |
28 | ) | ||
29 | }) | ||
27 | } | 30 | } |
28 | 31 | ||
29 | type MakeSecureRequestParams = { | 32 | type MakeSecureRequestParams = { |
@@ -33,41 +36,43 @@ type MakeSecureRequestParams = { | |||
33 | sign: boolean | 36 | sign: boolean |
34 | data?: Object | 37 | data?: Object |
35 | } | 38 | } |
36 | function makeSecureRequest (params: MakeSecureRequestParams, callback: request.RequestCallback) { | 39 | function makeSecureRequest (params: MakeSecureRequestParams) { |
37 | const requestParams = { | 40 | return new Promise<{ response: request.RequestResponse, body: any }>((res, rej) => { |
38 | url: REMOTE_SCHEME.HTTP + '://' + params.toPod.host + params.path, | 41 | const requestParams = { |
39 | json: {} | 42 | url: REMOTE_SCHEME.HTTP + '://' + params.toPod.host + params.path, |
40 | } | 43 | json: {} |
44 | } | ||
41 | 45 | ||
42 | if (params.method !== 'POST') { | 46 | if (params.method !== 'POST') { |
43 | return callback(new Error('Cannot make a secure request with a non POST method.'), null, null) | 47 | return rej(new Error('Cannot make a secure request with a non POST method.')) |
44 | } | 48 | } |
45 | 49 | ||
46 | // Add signature if it is specified in the params | 50 | // Add signature if it is specified in the params |
47 | if (params.sign === true) { | 51 | if (params.sign === true) { |
48 | const host = CONFIG.WEBSERVER.HOST | 52 | const host = CONFIG.WEBSERVER.HOST |
49 | 53 | ||
50 | let dataToSign | 54 | let dataToSign |
51 | if (params.data) { | 55 | if (params.data) { |
52 | dataToSign = params.data | 56 | dataToSign = params.data |
53 | } else { | 57 | } else { |
54 | // We do not have data to sign so we just take our host | 58 | // We do not have data to sign so we just take our host |
55 | // It is not ideal but the connection should be in HTTPS | 59 | // It is not ideal but the connection should be in HTTPS |
56 | dataToSign = host | 60 | dataToSign = host |
57 | } | 61 | } |
58 | 62 | ||
59 | requestParams.json['signature'] = { | 63 | requestParams.json['signature'] = { |
60 | host, // Which host we pretend to be | 64 | host, // Which host we pretend to be |
61 | signature: sign(dataToSign) | 65 | signature: sign(dataToSign) |
66 | } | ||
62 | } | 67 | } |
63 | } | ||
64 | 68 | ||
65 | // If there are data informations | 69 | // If there are data informations |
66 | if (params.data) { | 70 | if (params.data) { |
67 | requestParams.json['data'] = params.data | 71 | requestParams.json['data'] = params.data |
68 | } | 72 | } |
69 | 73 | ||
70 | request.post(requestParams, callback) | 74 | request.post(requestParams, (err, response, body) => err ? rej(err) : res({ response, body })) |
75 | }) | ||
71 | } | 76 | } |
72 | 77 | ||
73 | // --------------------------------------------------------------------------- | 78 | // --------------------------------------------------------------------------- |
diff --git a/server/helpers/utils.ts b/server/helpers/utils.ts index bbf135fa1..e99a48393 100644 --- a/server/helpers/utils.ts +++ b/server/helpers/utils.ts | |||
@@ -1,25 +1,14 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | 2 | ||
3 | import { pseudoRandomBytes } from 'crypto' | 3 | import { pseudoRandomBytesPromise } from './core-utils' |
4 | 4 | import { ResultList } from '../../shared' | |
5 | import { logger } from './logger' | ||
6 | 5 | ||
7 | function badRequest (req: express.Request, res: express.Response, next: express.NextFunction) { | 6 | function badRequest (req: express.Request, res: express.Response, next: express.NextFunction) { |
8 | res.type('json').status(400).end() | 7 | res.type('json').status(400).end() |
9 | } | 8 | } |
10 | 9 | ||
11 | function generateRandomString (size: number, callback: (err: Error, randomString?: string) => void) { | 10 | function generateRandomString (size: number) { |
12 | pseudoRandomBytes(size, function (err, raw) { | 11 | return pseudoRandomBytesPromise(size).then(raw => raw.toString('hex')) |
13 | if (err) return callback(err) | ||
14 | |||
15 | callback(null, raw.toString('hex')) | ||
16 | }) | ||
17 | } | ||
18 | |||
19 | function createEmptyCallback () { | ||
20 | return function (err) { | ||
21 | if (err) logger.error('Error in empty callback.', { error: err }) | ||
22 | } | ||
23 | } | 12 | } |
24 | 13 | ||
25 | interface FormatableToJSON { | 14 | interface FormatableToJSON { |
@@ -33,17 +22,18 @@ function getFormatedObjects<U, T extends FormatableToJSON> (objects: T[], object | |||
33 | formatedObjects.push(object.toFormatedJSON()) | 22 | formatedObjects.push(object.toFormatedJSON()) |
34 | }) | 23 | }) |
35 | 24 | ||
36 | return { | 25 | const res: ResultList<U> = { |
37 | total: objectsTotal, | 26 | total: objectsTotal, |
38 | data: formatedObjects | 27 | data: formatedObjects |
39 | } | 28 | } |
29 | |||
30 | return res | ||
40 | } | 31 | } |
41 | 32 | ||
42 | // --------------------------------------------------------------------------- | 33 | // --------------------------------------------------------------------------- |
43 | 34 | ||
44 | export { | 35 | export { |
45 | badRequest, | 36 | badRequest, |
46 | createEmptyCallback, | ||
47 | generateRandomString, | 37 | generateRandomString, |
48 | getFormatedObjects | 38 | getFormatedObjects |
49 | } | 39 | } |
diff --git a/server/initializers/checker.ts b/server/initializers/checker.ts index 7007f2c0b..fb69e05fc 100644 --- a/server/initializers/checker.ts +++ b/server/initializers/checker.ts | |||
@@ -2,6 +2,7 @@ import * as config from 'config' | |||
2 | 2 | ||
3 | import { database as db } from './database' | 3 | import { database as db } from './database' |
4 | import { CONFIG } from './constants' | 4 | import { CONFIG } from './constants' |
5 | import { promisify0 } from '../helpers/core-utils' | ||
5 | 6 | ||
6 | // Some checks on configuration files | 7 | // Some checks on configuration files |
7 | function checkConfig () { | 8 | function checkConfig () { |
@@ -35,41 +36,36 @@ function checkMissedConfig () { | |||
35 | } | 36 | } |
36 | 37 | ||
37 | // Check the available codecs | 38 | // Check the available codecs |
38 | function checkFFmpeg (callback: (err: Error) => void) { | 39 | function checkFFmpeg () { |
39 | const Ffmpeg = require('fluent-ffmpeg') | 40 | const Ffmpeg = require('fluent-ffmpeg') |
40 | 41 | const getAvailableCodecsPromise = promisify0(Ffmpeg.getAvailableCodecs) | |
41 | Ffmpeg.getAvailableCodecs(function (err, codecs) { | 42 | |
42 | if (err) return callback(err) | 43 | getAvailableCodecsPromise() |
43 | if (CONFIG.TRANSCODING.ENABLED === false) return callback(null) | 44 | .then(codecs => { |
44 | 45 | if (CONFIG.TRANSCODING.ENABLED === false) return undefined | |
45 | const canEncode = [ 'libx264' ] | 46 | |
46 | canEncode.forEach(function (codec) { | 47 | const canEncode = [ 'libx264' ] |
47 | if (codecs[codec] === undefined) { | 48 | canEncode.forEach(function (codec) { |
48 | return callback(new Error('Unknown codec ' + codec + ' in FFmpeg.')) | 49 | if (codecs[codec] === undefined) { |
49 | } | 50 | throw new Error('Unknown codec ' + codec + ' in FFmpeg.') |
50 | 51 | } | |
51 | if (codecs[codec].canEncode !== true) { | 52 | |
52 | return callback(new Error('Unavailable encode codec ' + codec + ' in FFmpeg')) | 53 | if (codecs[codec].canEncode !== true) { |
53 | } | 54 | throw new Error('Unavailable encode codec ' + codec + ' in FFmpeg') |
55 | } | ||
56 | }) | ||
54 | }) | 57 | }) |
55 | |||
56 | return callback(null) | ||
57 | }) | ||
58 | } | 58 | } |
59 | 59 | ||
60 | function clientsExist (callback: (err: Error, clientsExist?: boolean) => void) { | 60 | function clientsExist () { |
61 | db.OAuthClient.countTotal(function (err, totalClients) { | 61 | return db.OAuthClient.countTotal().then(totalClients => { |
62 | if (err) return callback(err) | 62 | return totalClients !== 0 |
63 | |||
64 | return callback(null, totalClients !== 0) | ||
65 | }) | 63 | }) |
66 | } | 64 | } |
67 | 65 | ||
68 | function usersExist (callback: (err: Error, usersExist?: boolean) => void) { | 66 | function usersExist () { |
69 | db.User.countTotal(function (err, totalUsers) { | 67 | return db.User.countTotal().then(totalUsers => { |
70 | if (err) return callback(err) | 68 | return totalUsers !== 0 |
71 | |||
72 | return callback(null, totalUsers !== 0) | ||
73 | }) | 69 | }) |
74 | } | 70 | } |
75 | 71 | ||
diff --git a/server/initializers/database.ts b/server/initializers/database.ts index 705dec6da..6e3a8d009 100644 --- a/server/initializers/database.ts +++ b/server/initializers/database.ts | |||
@@ -1,12 +1,12 @@ | |||
1 | import * as fs from 'fs' | ||
2 | import { join } from 'path' | 1 | import { join } from 'path' |
2 | import { flattenDepth } from 'lodash' | ||
3 | import * as Sequelize from 'sequelize' | 3 | import * as Sequelize from 'sequelize' |
4 | import { each } from 'async' | 4 | import * as Promise from 'bluebird' |
5 | 5 | ||
6 | import { CONFIG } from './constants' | 6 | import { CONFIG } from './constants' |
7 | // Do not use barrel, we need to load database first | 7 | // Do not use barrel, we need to load database first |
8 | import { logger } from '../helpers/logger' | 8 | import { logger } from '../helpers/logger' |
9 | import { isTestInstance } from '../helpers/core-utils' | 9 | import { isTestInstance, readdirPromise } from '../helpers/core-utils' |
10 | import { | 10 | import { |
11 | ApplicationModel, | 11 | ApplicationModel, |
12 | AuthorModel, | 12 | AuthorModel, |
@@ -33,7 +33,7 @@ const password = CONFIG.DATABASE.PASSWORD | |||
33 | 33 | ||
34 | const database: { | 34 | const database: { |
35 | sequelize?: Sequelize.Sequelize, | 35 | sequelize?: Sequelize.Sequelize, |
36 | init?: (silent: any, callback: any) => void, | 36 | init?: (silent: boolean) => Promise<void>, |
37 | 37 | ||
38 | Application?: ApplicationModel, | 38 | Application?: ApplicationModel, |
39 | Author?: AuthorModel, | 39 | Author?: AuthorModel, |
@@ -72,19 +72,17 @@ const sequelize = new Sequelize(dbname, username, password, { | |||
72 | 72 | ||
73 | database.sequelize = sequelize | 73 | database.sequelize = sequelize |
74 | 74 | ||
75 | database.init = function (silent: boolean, callback: (err: Error) => void) { | 75 | database.init = function (silent: boolean) { |
76 | const modelDirectory = join(__dirname, '..', 'models') | 76 | const modelDirectory = join(__dirname, '..', 'models') |
77 | 77 | ||
78 | getModelFiles(modelDirectory, function (err, filePaths) { | 78 | return getModelFiles(modelDirectory).then(filePaths => { |
79 | if (err) throw err | 79 | filePaths.forEach(filePath => { |
80 | |||
81 | filePaths.forEach(function (filePath) { | ||
82 | const model = sequelize.import(filePath) | 80 | const model = sequelize.import(filePath) |
83 | 81 | ||
84 | database[model['name']] = model | 82 | database[model['name']] = model |
85 | }) | 83 | }) |
86 | 84 | ||
87 | Object.keys(database).forEach(function (modelName) { | 85 | Object.keys(database).forEach(modelName => { |
88 | if ('associate' in database[modelName]) { | 86 | if ('associate' in database[modelName]) { |
89 | database[modelName].associate(database) | 87 | database[modelName].associate(database) |
90 | } | 88 | } |
@@ -92,7 +90,7 @@ database.init = function (silent: boolean, callback: (err: Error) => void) { | |||
92 | 90 | ||
93 | if (!silent) logger.info('Database %s is ready.', dbname) | 91 | if (!silent) logger.info('Database %s is ready.', dbname) |
94 | 92 | ||
95 | return callback(null) | 93 | return undefined |
96 | }) | 94 | }) |
97 | } | 95 | } |
98 | 96 | ||
@@ -104,49 +102,50 @@ export { | |||
104 | 102 | ||
105 | // --------------------------------------------------------------------------- | 103 | // --------------------------------------------------------------------------- |
106 | 104 | ||
107 | function getModelFiles (modelDirectory: string, callback: (err: Error, filePaths: string[]) => void) { | 105 | function getModelFiles (modelDirectory: string) { |
108 | fs.readdir(modelDirectory, function (err, files) { | 106 | return readdirPromise(modelDirectory) |
109 | if (err) throw err | 107 | .then(files => { |
110 | 108 | const directories: string[] = files.filter(function (directory) { | |
111 | const directories = files.filter(function (directory) { | 109 | // Find directories |
112 | // Find directories | 110 | if ( |
113 | if ( | 111 | directory.endsWith('.js.map') || |
114 | directory.endsWith('.js.map') || | 112 | directory === 'index.js' || directory === 'index.ts' || |
115 | directory === 'index.js' || directory === 'index.ts' || | 113 | directory === 'utils.js' || directory === 'utils.ts' |
116 | directory === 'utils.js' || directory === 'utils.ts' | 114 | ) return false |
117 | ) return false | 115 | |
116 | return true | ||
117 | }) | ||
118 | 118 | ||
119 | return true | 119 | return directories |
120 | }) | 120 | }) |
121 | 121 | .then(directories => { | |
122 | let modelFilePaths: string[] = [] | 122 | const tasks = [] |
123 | 123 | ||
124 | // For each directory we read it and append model in the modelFilePaths array | 124 | // For each directory we read it and append model in the modelFilePaths array |
125 | each(directories, function (directory: string, eachCallback: ErrorCallback<Error>) { | 125 | directories.forEach(directory => { |
126 | const modelDirectoryPath = join(modelDirectory, directory) | 126 | const modelDirectoryPath = join(modelDirectory, directory) |
127 | 127 | ||
128 | fs.readdir(modelDirectoryPath, function (err, files) { | 128 | const promise = readdirPromise(modelDirectoryPath).then(files => { |
129 | if (err) return eachCallback(err) | 129 | const filteredFiles = files.filter(file => { |
130 | 130 | if ( | |
131 | const filteredFiles = files.filter(file => { | 131 | file === 'index.js' || file === 'index.ts' || |
132 | if ( | 132 | file === 'utils.js' || file === 'utils.ts' || |
133 | file === 'index.js' || file === 'index.ts' || | 133 | file.endsWith('-interface.js') || file.endsWith('-interface.ts') || |
134 | file === 'utils.js' || file === 'utils.ts' || | 134 | file.endsWith('.js.map') |
135 | file.endsWith('-interface.js') || file.endsWith('-interface.ts') || | 135 | ) return false |
136 | file.endsWith('.js.map') | 136 | |
137 | ) return false | 137 | return true |
138 | 138 | }).map(file => join(modelDirectoryPath, file)) | |
139 | return true | 139 | |
140 | }).map(file => { | 140 | return filteredFiles |
141 | return join(modelDirectoryPath, file) | ||
142 | }) | 141 | }) |
143 | 142 | ||
144 | modelFilePaths = modelFilePaths.concat(filteredFiles) | 143 | tasks.push(promise) |
145 | |||
146 | return eachCallback(null) | ||
147 | }) | 144 | }) |
148 | }, function (err: Error) { | 145 | |
149 | return callback(err, modelFilePaths) | 146 | return Promise.all(tasks) |
147 | }) | ||
148 | .then((filteredFiles: string[][]) => { | ||
149 | return flattenDepth<string>(filteredFiles, 1) | ||
150 | }) | 150 | }) |
151 | }) | ||
152 | } | 151 | } |
diff --git a/server/initializers/installer.ts b/server/initializers/installer.ts index f105c8292..1ec24c4ad 100644 --- a/server/initializers/installer.ts +++ b/server/initializers/installer.ts | |||
@@ -1,37 +1,19 @@ | |||
1 | import { join } from 'path' | 1 | import { join } from 'path' |
2 | import * as config from 'config' | 2 | import * as config from 'config' |
3 | import { each, series } from 'async' | ||
4 | import * as mkdirp from 'mkdirp' | ||
5 | import * as passwordGenerator from 'password-generator' | 3 | import * as passwordGenerator from 'password-generator' |
4 | import * as Promise from 'bluebird' | ||
6 | 5 | ||
7 | import { database as db } from './database' | 6 | import { database as db } from './database' |
8 | import { USER_ROLES, CONFIG, LAST_MIGRATION_VERSION } from './constants' | 7 | import { USER_ROLES, CONFIG, LAST_MIGRATION_VERSION } from './constants' |
9 | import { clientsExist, usersExist } from './checker' | 8 | import { clientsExist, usersExist } from './checker' |
10 | import { logger, createCertsIfNotExist, root } from '../helpers' | 9 | import { logger, createCertsIfNotExist, root, mkdirpPromise } from '../helpers' |
11 | 10 | ||
12 | function installApplication (callback: (err: Error) => void) { | 11 | function installApplication () { |
13 | series([ | 12 | return db.sequelize.sync() |
14 | function createDatabase (callbackAsync) { | 13 | .then(() => createDirectoriesIfNotExist()) |
15 | db.sequelize.sync().asCallback(callbackAsync) | 14 | .then(() => createCertsIfNotExist()) |
16 | // db.sequelize.sync({ force: true }).asCallback(callbackAsync) | 15 | .then(() => createOAuthClientIfNotExist()) |
17 | }, | 16 | .then(() => createOAuthAdminIfNotExist()) |
18 | |||
19 | function createDirectories (callbackAsync) { | ||
20 | createDirectoriesIfNotExist(callbackAsync) | ||
21 | }, | ||
22 | |||
23 | function createCertificates (callbackAsync) { | ||
24 | createCertsIfNotExist(callbackAsync) | ||
25 | }, | ||
26 | |||
27 | function createOAuthClient (callbackAsync) { | ||
28 | createOAuthClientIfNotExist(callbackAsync) | ||
29 | }, | ||
30 | |||
31 | function createOAuthUser (callbackAsync) { | ||
32 | createOAuthAdminIfNotExist(callbackAsync) | ||
33 | } | ||
34 | ], callback) | ||
35 | } | 17 | } |
36 | 18 | ||
37 | // --------------------------------------------------------------------------- | 19 | // --------------------------------------------------------------------------- |
@@ -42,21 +24,22 @@ export { | |||
42 | 24 | ||
43 | // --------------------------------------------------------------------------- | 25 | // --------------------------------------------------------------------------- |
44 | 26 | ||
45 | function createDirectoriesIfNotExist (callback: (err: Error) => void) { | 27 | function createDirectoriesIfNotExist () { |
46 | const storages = config.get('storage') | 28 | const storages = config.get('storage') |
47 | 29 | ||
48 | each(Object.keys(storages), function (key, callbackEach) { | 30 | const tasks = [] |
31 | Object.keys(storages).forEach(key => { | ||
49 | const dir = storages[key] | 32 | const dir = storages[key] |
50 | mkdirp(join(root(), dir), callbackEach) | 33 | tasks.push(mkdirpPromise(join(root(), dir))) |
51 | }, callback) | 34 | }) |
52 | } | ||
53 | 35 | ||
54 | function createOAuthClientIfNotExist (callback: (err: Error) => void) { | 36 | return Promise.all(tasks) |
55 | clientsExist(function (err, exist) { | 37 | } |
56 | if (err) return callback(err) | ||
57 | 38 | ||
39 | function createOAuthClientIfNotExist () { | ||
40 | return clientsExist().then(exist => { | ||
58 | // Nothing to do, clients already exist | 41 | // Nothing to do, clients already exist |
59 | if (exist === true) return callback(null) | 42 | if (exist === true) return undefined |
60 | 43 | ||
61 | logger.info('Creating a default OAuth Client.') | 44 | logger.info('Creating a default OAuth Client.') |
62 | 45 | ||
@@ -69,23 +52,19 @@ function createOAuthClientIfNotExist (callback: (err: Error) => void) { | |||
69 | redirectUris: null | 52 | redirectUris: null |
70 | }) | 53 | }) |
71 | 54 | ||
72 | client.save().asCallback(function (err, createdClient) { | 55 | return client.save().then(createdClient => { |
73 | if (err) return callback(err) | ||
74 | |||
75 | logger.info('Client id: ' + createdClient.clientId) | 56 | logger.info('Client id: ' + createdClient.clientId) |
76 | logger.info('Client secret: ' + createdClient.clientSecret) | 57 | logger.info('Client secret: ' + createdClient.clientSecret) |
77 | 58 | ||
78 | return callback(null) | 59 | return undefined |
79 | }) | 60 | }) |
80 | }) | 61 | }) |
81 | } | 62 | } |
82 | 63 | ||
83 | function createOAuthAdminIfNotExist (callback: (err: Error) => void) { | 64 | function createOAuthAdminIfNotExist () { |
84 | usersExist(function (err, exist) { | 65 | return usersExist().then(exist => { |
85 | if (err) return callback(err) | ||
86 | |||
87 | // Nothing to do, users already exist | 66 | // Nothing to do, users already exist |
88 | if (exist === true) return callback(null) | 67 | if (exist === true) return undefined |
89 | 68 | ||
90 | logger.info('Creating the administrator.') | 69 | logger.info('Creating the administrator.') |
91 | 70 | ||
@@ -116,14 +95,12 @@ function createOAuthAdminIfNotExist (callback: (err: Error) => void) { | |||
116 | role | 95 | role |
117 | } | 96 | } |
118 | 97 | ||
119 | db.User.create(userData, createOptions).asCallback(function (err, createdUser) { | 98 | return db.User.create(userData, createOptions).then(createdUser => { |
120 | if (err) return callback(err) | ||
121 | |||
122 | logger.info('Username: ' + username) | 99 | logger.info('Username: ' + username) |
123 | logger.info('User password: ' + password) | 100 | logger.info('User password: ' + password) |
124 | 101 | ||
125 | logger.info('Creating Application table.') | 102 | logger.info('Creating Application table.') |
126 | db.Application.create({ migrationVersion: LAST_MIGRATION_VERSION }).asCallback(callback) | 103 | return db.Application.create({ migrationVersion: LAST_MIGRATION_VERSION }) |
127 | }) | 104 | }) |
128 | }) | 105 | }) |
129 | } | 106 | } |
diff --git a/server/initializers/migrations/0005-email-pod.ts b/server/initializers/migrations/0005-email-pod.ts index a9200c47f..ceefaad4a 100644 --- a/server/initializers/migrations/0005-email-pod.ts +++ b/server/initializers/migrations/0005-email-pod.ts | |||
@@ -1,9 +1,12 @@ | |||
1 | import { waterfall } from 'async' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | import * as Promise from 'bluebird' | |
3 | // utils = { transaction, queryInterface, sequelize, Sequelize } | 3 | |
4 | function up (utils, finalCallback) { | 4 | function up (utils: { |
5 | transaction: Sequelize.Transaction, | ||
6 | queryInterface: Sequelize.QueryInterface, | ||
7 | sequelize: Sequelize.Sequelize | ||
8 | }): Promise<void> { | ||
5 | const q = utils.queryInterface | 9 | const q = utils.queryInterface |
6 | const Sequelize = utils.Sequelize | ||
7 | 10 | ||
8 | const data = { | 11 | const data = { |
9 | type: Sequelize.STRING(400), | 12 | type: Sequelize.STRING(400), |
@@ -11,27 +14,16 @@ function up (utils, finalCallback) { | |||
11 | defaultValue: '' | 14 | defaultValue: '' |
12 | } | 15 | } |
13 | 16 | ||
14 | waterfall([ | 17 | return q.addColumn('Pods', 'email', data) |
15 | 18 | .then(() => { | |
16 | function addEmailColumn (callback) { | ||
17 | q.addColumn('Pods', 'email', data, { transaction: utils.transaction }).asCallback(function (err) { | ||
18 | return callback(err) | ||
19 | }) | ||
20 | }, | ||
21 | |||
22 | function updateWithFakeEmails (callback) { | ||
23 | const query = 'UPDATE "Pods" SET "email" = \'dummy@example.com\'' | 19 | const query = 'UPDATE "Pods" SET "email" = \'dummy@example.com\'' |
24 | utils.sequelize.query(query, { transaction: utils.transaction }).asCallback(function (err) { | 20 | return utils.sequelize.query(query, { transaction: utils.transaction }) |
25 | return callback(err) | 21 | }) |
26 | }) | 22 | .then(() => { |
27 | }, | ||
28 | |||
29 | function nullOnDefault (callback) { | ||
30 | data.defaultValue = null | 23 | data.defaultValue = null |
31 | 24 | ||
32 | q.changeColumn('Pods', 'email', data, { transaction: utils.transaction }).asCallback(callback) | 25 | return q.changeColumn('Pods', 'email', data) |
33 | } | 26 | }) |
34 | ], finalCallback) | ||
35 | } | 27 | } |
36 | 28 | ||
37 | function down (options, callback) { | 29 | function down (options, callback) { |
diff --git a/server/initializers/migrations/0010-email-user.ts b/server/initializers/migrations/0010-email-user.ts index 4b5d29394..e8865acdb 100644 --- a/server/initializers/migrations/0010-email-user.ts +++ b/server/initializers/migrations/0010-email-user.ts | |||
@@ -1,37 +1,28 @@ | |||
1 | import { waterfall } from 'async' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | import * as Promise from 'bluebird' | |
3 | // utils = { transaction, queryInterface, sequelize, Sequelize } | 3 | |
4 | function up (utils, finalCallback) { | 4 | function up (utils: { |
5 | transaction: Sequelize.Transaction, | ||
6 | queryInterface: Sequelize.QueryInterface, | ||
7 | sequelize: Sequelize.Sequelize | ||
8 | }): Promise<void> { | ||
5 | const q = utils.queryInterface | 9 | const q = utils.queryInterface |
6 | const Sequelize = utils.Sequelize | ||
7 | 10 | ||
8 | const data = { | 11 | const data = { |
9 | type: Sequelize.STRING(400), | 12 | type: Sequelize.STRING(400), |
10 | allowNull: false, | 13 | allowNull: false, |
11 | defaultValue: '' | 14 | defaultValue: '' |
12 | } | 15 | } |
13 | 16 | return q.addColumn('Users', 'email', data) | |
14 | waterfall([ | 17 | .then(() => { |
15 | |||
16 | function addEmailColumn (callback) { | ||
17 | q.addColumn('Users', 'email', data, { transaction: utils.transaction }).asCallback(function (err) { | ||
18 | return callback(err) | ||
19 | }) | ||
20 | }, | ||
21 | |||
22 | function updateWithFakeEmails (callback) { | ||
23 | const query = 'UPDATE "Users" SET "email" = CONCAT("username", \'@example.com\')' | 18 | const query = 'UPDATE "Users" SET "email" = CONCAT("username", \'@example.com\')' |
24 | utils.sequelize.query(query, { transaction: utils.transaction }).asCallback(function (err) { | 19 | return utils.sequelize.query(query, { transaction: utils.transaction }) |
25 | return callback(err) | 20 | }) |
26 | }) | 21 | .then(() => { |
27 | }, | ||
28 | |||
29 | function nullOnDefault (callback) { | ||
30 | data.defaultValue = null | 22 | data.defaultValue = null |
31 | 23 | ||
32 | q.changeColumn('Users', 'email', data, { transaction: utils.transaction }).asCallback(callback) | 24 | return q.changeColumn('Users', 'email', data) |
33 | } | 25 | }) |
34 | ], finalCallback) | ||
35 | } | 26 | } |
36 | 27 | ||
37 | function down (options, callback) { | 28 | function down (options, callback) { |
diff --git a/server/initializers/migrations/0015-video-views.ts b/server/initializers/migrations/0015-video-views.ts index e70869404..df274d817 100644 --- a/server/initializers/migrations/0015-video-views.ts +++ b/server/initializers/migrations/0015-video-views.ts | |||
@@ -1,7 +1,12 @@ | |||
1 | // utils = { transaction, queryInterface, sequelize, Sequelize } | 1 | import * as Sequelize from 'sequelize' |
2 | function up (utils, finalCallback) { | 2 | import * as Promise from 'bluebird' |
3 | |||
4 | function up (utils: { | ||
5 | transaction: Sequelize.Transaction, | ||
6 | queryInterface: Sequelize.QueryInterface, | ||
7 | sequelize: Sequelize.Sequelize | ||
8 | }): Promise<void> { | ||
3 | const q = utils.queryInterface | 9 | const q = utils.queryInterface |
4 | const Sequelize = utils.Sequelize | ||
5 | 10 | ||
6 | const data = { | 11 | const data = { |
7 | type: Sequelize.INTEGER, | 12 | type: Sequelize.INTEGER, |
@@ -9,7 +14,7 @@ function up (utils, finalCallback) { | |||
9 | defaultValue: 0 | 14 | defaultValue: 0 |
10 | } | 15 | } |
11 | 16 | ||
12 | q.addColumn('Videos', 'views', data, { transaction: utils.transaction }).asCallback(finalCallback) | 17 | return q.addColumn('Videos', 'views', data) |
13 | } | 18 | } |
14 | 19 | ||
15 | function down (options, callback) { | 20 | function down (options, callback) { |
diff --git a/server/initializers/migrations/0020-video-likes.ts b/server/initializers/migrations/0020-video-likes.ts index e435d0657..3d7182d0a 100644 --- a/server/initializers/migrations/0020-video-likes.ts +++ b/server/initializers/migrations/0020-video-likes.ts | |||
@@ -1,7 +1,12 @@ | |||
1 | // utils = { transaction, queryInterface, sequelize, Sequelize } | 1 | import * as Sequelize from 'sequelize' |
2 | function up (utils, finalCallback) { | 2 | import * as Promise from 'bluebird' |
3 | |||
4 | function up (utils: { | ||
5 | transaction: Sequelize.Transaction, | ||
6 | queryInterface: Sequelize.QueryInterface, | ||
7 | sequelize: Sequelize.Sequelize | ||
8 | }): Promise<void> { | ||
3 | const q = utils.queryInterface | 9 | const q = utils.queryInterface |
4 | const Sequelize = utils.Sequelize | ||
5 | 10 | ||
6 | const data = { | 11 | const data = { |
7 | type: Sequelize.INTEGER, | 12 | type: Sequelize.INTEGER, |
@@ -9,7 +14,7 @@ function up (utils, finalCallback) { | |||
9 | defaultValue: 0 | 14 | defaultValue: 0 |
10 | } | 15 | } |
11 | 16 | ||
12 | q.addColumn('Videos', 'likes', data, { transaction: utils.transaction }).asCallback(finalCallback) | 17 | return q.addColumn('Videos', 'likes', data) |
13 | } | 18 | } |
14 | 19 | ||
15 | function down (options, callback) { | 20 | function down (options, callback) { |
diff --git a/server/initializers/migrations/0025-video-dislikes.ts b/server/initializers/migrations/0025-video-dislikes.ts index 57e54e904..ed41095dc 100644 --- a/server/initializers/migrations/0025-video-dislikes.ts +++ b/server/initializers/migrations/0025-video-dislikes.ts | |||
@@ -1,7 +1,12 @@ | |||
1 | // utils = { transaction, queryInterface, sequelize, Sequelize } | 1 | import * as Sequelize from 'sequelize' |
2 | function up (utils, finalCallback) { | 2 | import * as Promise from 'bluebird' |
3 | |||
4 | function up (utils: { | ||
5 | transaction: Sequelize.Transaction, | ||
6 | queryInterface: Sequelize.QueryInterface, | ||
7 | sequelize: Sequelize.Sequelize | ||
8 | }): Promise<void> { | ||
3 | const q = utils.queryInterface | 9 | const q = utils.queryInterface |
4 | const Sequelize = utils.Sequelize | ||
5 | 10 | ||
6 | const data = { | 11 | const data = { |
7 | type: Sequelize.INTEGER, | 12 | type: Sequelize.INTEGER, |
@@ -9,7 +14,7 @@ function up (utils, finalCallback) { | |||
9 | defaultValue: 0 | 14 | defaultValue: 0 |
10 | } | 15 | } |
11 | 16 | ||
12 | q.addColumn('Videos', 'dislikes', data, { transaction: utils.transaction }).asCallback(finalCallback) | 17 | return q.addColumn('Videos', 'dislikes', data) |
13 | } | 18 | } |
14 | 19 | ||
15 | function down (options, callback) { | 20 | function down (options, callback) { |
diff --git a/server/initializers/migrations/0030-video-category.ts b/server/initializers/migrations/0030-video-category.ts index 1073f449c..f5adee8f9 100644 --- a/server/initializers/migrations/0030-video-category.ts +++ b/server/initializers/migrations/0030-video-category.ts | |||
@@ -1,9 +1,12 @@ | |||
1 | import { waterfall } from 'async' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | import * as Promise from 'bluebird' | |
3 | // utils = { transaction, queryInterface, sequelize, Sequelize } | 3 | |
4 | function up (utils, finalCallback) { | 4 | function up (utils: { |
5 | transaction: Sequelize.Transaction, | ||
6 | queryInterface: Sequelize.QueryInterface, | ||
7 | sequelize: Sequelize.Sequelize | ||
8 | }): Promise<void> { | ||
5 | const q = utils.queryInterface | 9 | const q = utils.queryInterface |
6 | const Sequelize = utils.Sequelize | ||
7 | 10 | ||
8 | const data = { | 11 | const data = { |
9 | type: Sequelize.INTEGER, | 12 | type: Sequelize.INTEGER, |
@@ -11,20 +14,12 @@ function up (utils, finalCallback) { | |||
11 | defaultValue: 0 | 14 | defaultValue: 0 |
12 | } | 15 | } |
13 | 16 | ||
14 | waterfall([ | 17 | return q.addColumn('Videos', 'category', data) |
15 | 18 | .then(() => { | |
16 | function addCategoryColumn (callback) { | ||
17 | q.addColumn('Videos', 'category', data, { transaction: utils.transaction }).asCallback(function (err) { | ||
18 | return callback(err) | ||
19 | }) | ||
20 | }, | ||
21 | |||
22 | function nullOnDefault (callback) { | ||
23 | data.defaultValue = null | 19 | data.defaultValue = null |
24 | 20 | ||
25 | q.changeColumn('Videos', 'category', data, { transaction: utils.transaction }).asCallback(callback) | 21 | return q.changeColumn('Videos', 'category', data) |
26 | } | 22 | }) |
27 | ], finalCallback) | ||
28 | } | 23 | } |
29 | 24 | ||
30 | function down (options, callback) { | 25 | function down (options, callback) { |
diff --git a/server/initializers/migrations/0035-video-licence.ts b/server/initializers/migrations/0035-video-licence.ts index 9316b3c37..00c64d8e7 100644 --- a/server/initializers/migrations/0035-video-licence.ts +++ b/server/initializers/migrations/0035-video-licence.ts | |||
@@ -1,9 +1,12 @@ | |||
1 | import { waterfall } from 'async' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | import * as Promise from 'bluebird' | |
3 | // utils = { transaction, queryInterface, sequelize, Sequelize } | 3 | |
4 | function up (utils, finalCallback) { | 4 | function up (utils: { |
5 | transaction: Sequelize.Transaction, | ||
6 | queryInterface: Sequelize.QueryInterface, | ||
7 | sequelize: Sequelize.Sequelize | ||
8 | }): Promise<void> { | ||
5 | const q = utils.queryInterface | 9 | const q = utils.queryInterface |
6 | const Sequelize = utils.Sequelize | ||
7 | 10 | ||
8 | const data = { | 11 | const data = { |
9 | type: Sequelize.INTEGER, | 12 | type: Sequelize.INTEGER, |
@@ -11,20 +14,11 @@ function up (utils, finalCallback) { | |||
11 | defaultValue: 0 | 14 | defaultValue: 0 |
12 | } | 15 | } |
13 | 16 | ||
14 | waterfall([ | 17 | return q.addColumn('Videos', 'licence', data) |
15 | 18 | .then(() => { | |
16 | function addLicenceColumn (callback) { | ||
17 | q.addColumn('Videos', 'licence', data, { transaction: utils.transaction }).asCallback(function (err) { | ||
18 | return callback(err) | ||
19 | }) | ||
20 | }, | ||
21 | |||
22 | function nullOnDefault (callback) { | ||
23 | data.defaultValue = null | 19 | data.defaultValue = null |
24 | 20 | return q.changeColumn('Videos', 'licence', data) | |
25 | q.changeColumn('Videos', 'licence', data, { transaction: utils.transaction }).asCallback(callback) | 21 | }) |
26 | } | ||
27 | ], finalCallback) | ||
28 | } | 22 | } |
29 | 23 | ||
30 | function down (options, callback) { | 24 | function down (options, callback) { |
diff --git a/server/initializers/migrations/0040-video-nsfw.ts b/server/initializers/migrations/0040-video-nsfw.ts index c61f496f1..046876b61 100644 --- a/server/initializers/migrations/0040-video-nsfw.ts +++ b/server/initializers/migrations/0040-video-nsfw.ts | |||
@@ -1,9 +1,12 @@ | |||
1 | import { waterfall } from 'async' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | import * as Promise from 'bluebird' | |
3 | // utils = { transaction, queryInterface, sequelize, Sequelize } | 3 | |
4 | function up (utils, finalCallback) { | 4 | function up (utils: { |
5 | transaction: Sequelize.Transaction, | ||
6 | queryInterface: Sequelize.QueryInterface, | ||
7 | sequelize: Sequelize.Sequelize | ||
8 | }): Promise<void> { | ||
5 | const q = utils.queryInterface | 9 | const q = utils.queryInterface |
6 | const Sequelize = utils.Sequelize | ||
7 | 10 | ||
8 | const data = { | 11 | const data = { |
9 | type: Sequelize.BOOLEAN, | 12 | type: Sequelize.BOOLEAN, |
@@ -11,20 +14,12 @@ function up (utils, finalCallback) { | |||
11 | defaultValue: false | 14 | defaultValue: false |
12 | } | 15 | } |
13 | 16 | ||
14 | waterfall([ | 17 | return q.addColumn('Videos', 'nsfw', data) |
15 | 18 | .then(() => { | |
16 | function addNSFWColumn (callback) { | ||
17 | q.addColumn('Videos', 'nsfw', data, { transaction: utils.transaction }).asCallback(function (err) { | ||
18 | return callback(err) | ||
19 | }) | ||
20 | }, | ||
21 | |||
22 | function nullOnDefault (callback) { | ||
23 | data.defaultValue = null | 19 | data.defaultValue = null |
24 | 20 | ||
25 | q.changeColumn('Videos', 'nsfw', data, { transaction: utils.transaction }).asCallback(callback) | 21 | return q.changeColumn('Videos', 'nsfw', data) |
26 | } | 22 | }) |
27 | ], finalCallback) | ||
28 | } | 23 | } |
29 | 24 | ||
30 | function down (options, callback) { | 25 | function down (options, callback) { |
diff --git a/server/initializers/migrations/0045-user-display-nsfw.ts b/server/initializers/migrations/0045-user-display-nsfw.ts index 1ca317795..75bd3bbea 100644 --- a/server/initializers/migrations/0045-user-display-nsfw.ts +++ b/server/initializers/migrations/0045-user-display-nsfw.ts | |||
@@ -1,7 +1,12 @@ | |||
1 | // utils = { transaction, queryInterface, sequelize, Sequelize } | 1 | import * as Sequelize from 'sequelize' |
2 | function up (utils, finalCallback) { | 2 | import * as Promise from 'bluebird' |
3 | |||
4 | function up (utils: { | ||
5 | transaction: Sequelize.Transaction, | ||
6 | queryInterface: Sequelize.QueryInterface, | ||
7 | sequelize: Sequelize.Sequelize | ||
8 | }): Promise<void> { | ||
3 | const q = utils.queryInterface | 9 | const q = utils.queryInterface |
4 | const Sequelize = utils.Sequelize | ||
5 | 10 | ||
6 | const data = { | 11 | const data = { |
7 | type: Sequelize.BOOLEAN, | 12 | type: Sequelize.BOOLEAN, |
@@ -9,7 +14,7 @@ function up (utils, finalCallback) { | |||
9 | defaultValue: false | 14 | defaultValue: false |
10 | } | 15 | } |
11 | 16 | ||
12 | q.addColumn('Users', 'displayNSFW', data, { transaction: utils.transaction }).asCallback(finalCallback) | 17 | return q.addColumn('Users', 'displayNSFW', data) |
13 | } | 18 | } |
14 | 19 | ||
15 | function down (options, callback) { | 20 | function down (options, callback) { |
diff --git a/server/initializers/migrations/0050-video-language.ts b/server/initializers/migrations/0050-video-language.ts index 95d0a473a..ed08f5866 100644 --- a/server/initializers/migrations/0050-video-language.ts +++ b/server/initializers/migrations/0050-video-language.ts | |||
@@ -1,7 +1,12 @@ | |||
1 | // utils = { transaction, queryInterface, sequelize, Sequelize } | 1 | import * as Sequelize from 'sequelize' |
2 | function up (utils, finalCallback) { | 2 | import * as Promise from 'bluebird' |
3 | |||
4 | function up (utils: { | ||
5 | transaction: Sequelize.Transaction, | ||
6 | queryInterface: Sequelize.QueryInterface, | ||
7 | sequelize: Sequelize.Sequelize | ||
8 | }): Promise<void> { | ||
3 | const q = utils.queryInterface | 9 | const q = utils.queryInterface |
4 | const Sequelize = utils.Sequelize | ||
5 | 10 | ||
6 | const data = { | 11 | const data = { |
7 | type: Sequelize.INTEGER, | 12 | type: Sequelize.INTEGER, |
@@ -9,7 +14,7 @@ function up (utils, finalCallback) { | |||
9 | defaultValue: null | 14 | defaultValue: null |
10 | } | 15 | } |
11 | 16 | ||
12 | q.addColumn('Videos', 'language', data, { transaction: utils.transaction }).asCallback(finalCallback) | 17 | return q.addColumn('Videos', 'language', data) |
13 | } | 18 | } |
14 | 19 | ||
15 | function down (options, callback) { | 20 | function down (options, callback) { |
diff --git a/server/initializers/migrator.ts b/server/initializers/migrator.ts index d8faaebc6..d381551b5 100644 --- a/server/initializers/migrator.ts +++ b/server/initializers/migrator.ts | |||
@@ -1,70 +1,54 @@ | |||
1 | import { waterfall, eachSeries } from 'async' | ||
2 | import * as fs from 'fs' | ||
3 | import * as path from 'path' | 1 | import * as path from 'path' |
4 | import * as Sequelize from 'sequelize' | 2 | import * as Promise from 'bluebird' |
5 | 3 | ||
6 | import { database as db } from './database' | 4 | import { database as db } from './database' |
7 | import { LAST_MIGRATION_VERSION } from './constants' | 5 | import { LAST_MIGRATION_VERSION } from './constants' |
8 | import { logger } from '../helpers' | 6 | import { logger, readdirPromise } from '../helpers' |
9 | 7 | ||
10 | function migrate (finalCallback: (err: Error) => void) { | 8 | function migrate () { |
11 | waterfall([ | 9 | const p = db.sequelize.getQueryInterface().showAllTables() |
12 | 10 | .then(tables => { | |
13 | function checkApplicationTableExists (callback) { | 11 | // No tables, we don't need to migrate anything |
14 | db.sequelize.getQueryInterface().showAllTables().asCallback(function (err, tables) { | 12 | // The installer will do that |
15 | if (err) return callback(err) | 13 | if (tables.length === 0) throw null |
16 | 14 | }) | |
17 | // No tables, we don't need to migrate anything | 15 | .then(() => { |
18 | // The installer will do that | 16 | return db.Application.loadMigrationVersion() |
19 | if (tables.length === 0) return finalCallback(null) | 17 | }) |
20 | 18 | .then(actualVersion => { | |
21 | return callback(null) | ||
22 | }) | ||
23 | }, | ||
24 | |||
25 | function loadMigrationVersion (callback) { | ||
26 | db.Application.loadMigrationVersion(callback) | ||
27 | }, | ||
28 | |||
29 | function createMigrationRowIfNotExists (actualVersion, callback) { | ||
30 | if (actualVersion === null) { | 19 | if (actualVersion === null) { |
31 | db.Application.create({ | 20 | return db.Application.create({ migrationVersion: 0 }).then(() => 0) |
32 | migrationVersion: 0 | ||
33 | }, function (err) { | ||
34 | return callback(err, 0) | ||
35 | }) | ||
36 | } | 21 | } |
37 | 22 | ||
38 | return callback(null, actualVersion) | 23 | return actualVersion |
39 | }, | 24 | }) |
40 | 25 | .then(actualVersion => { | |
41 | function abortMigrationIfNotNeeded (actualVersion, callback) { | 26 | // No need migrations, abort |
42 | // No need migrations | 27 | if (actualVersion >= LAST_MIGRATION_VERSION) throw null |
43 | if (actualVersion >= LAST_MIGRATION_VERSION) return finalCallback(null) | ||
44 | |||
45 | return callback(null, actualVersion) | ||
46 | }, | ||
47 | 28 | ||
48 | function getMigrations (actualVersion, callback) { | 29 | return actualVersion |
30 | }) | ||
31 | .then(actualVersion => { | ||
49 | // If there are a new migration scripts | 32 | // If there are a new migration scripts |
50 | logger.info('Begin migrations.') | 33 | logger.info('Begin migrations.') |
51 | 34 | ||
52 | getMigrationScripts(function (err, migrationScripts) { | 35 | return getMigrationScripts().then(migrationScripts => ({ actualVersion, migrationScripts })) |
53 | return callback(err, actualVersion, migrationScripts) | 36 | }) |
37 | .then(({ actualVersion, migrationScripts }) => { | ||
38 | return Promise.mapSeries(migrationScripts, entity => { | ||
39 | return executeMigration(actualVersion, entity) | ||
54 | }) | 40 | }) |
55 | }, | 41 | }) |
42 | .then(() => { | ||
43 | logger.info('Migrations finished. New migration version schema: %s', LAST_MIGRATION_VERSION) | ||
44 | }) | ||
45 | .catch(err => { | ||
46 | if (err === null) return undefined | ||
56 | 47 | ||
57 | function doMigrations (actualVersion, migrationScripts, callback) { | 48 | throw err |
58 | eachSeries(migrationScripts, function (entity: any, callbackEach) { | 49 | }) |
59 | executeMigration(actualVersion, entity, callbackEach) | ||
60 | }, function (err) { | ||
61 | if (err) return callback(err) | ||
62 | 50 | ||
63 | logger.info('Migrations finished. New migration version schema: %s', LAST_MIGRATION_VERSION) | 51 | return p |
64 | return callback(null) | ||
65 | }) | ||
66 | } | ||
67 | ], finalCallback) | ||
68 | } | 52 | } |
69 | 53 | ||
70 | // --------------------------------------------------------------------------- | 54 | // --------------------------------------------------------------------------- |
@@ -75,12 +59,12 @@ export { | |||
75 | 59 | ||
76 | // --------------------------------------------------------------------------- | 60 | // --------------------------------------------------------------------------- |
77 | 61 | ||
78 | type GetMigrationScriptsCallback = (err: Error, filesToMigrate?: { version: string, script: string }[]) => void | 62 | function getMigrationScripts () { |
79 | function getMigrationScripts (callback: GetMigrationScriptsCallback) { | 63 | return readdirPromise(path.join(__dirname, 'migrations')).then(files => { |
80 | fs.readdir(path.join(__dirname, 'migrations'), function (err, files) { | 64 | const filesToMigrate: { |
81 | if (err) return callback(err) | 65 | version: string, |
82 | 66 | script: string | |
83 | const filesToMigrate = [] | 67 | }[] = [] |
84 | 68 | ||
85 | files.forEach(function (file) { | 69 | files.forEach(function (file) { |
86 | // Filename is something like 'version-blabla.js' | 70 | // Filename is something like 'version-blabla.js' |
@@ -91,15 +75,15 @@ function getMigrationScripts (callback: GetMigrationScriptsCallback) { | |||
91 | }) | 75 | }) |
92 | }) | 76 | }) |
93 | 77 | ||
94 | return callback(err, filesToMigrate) | 78 | return filesToMigrate |
95 | }) | 79 | }) |
96 | } | 80 | } |
97 | 81 | ||
98 | function executeMigration (actualVersion: number, entity: { version: string, script: string }, callback: (err: Error) => void) { | 82 | function executeMigration (actualVersion: number, entity: { version: string, script: string }) { |
99 | const versionScript = parseInt(entity.version, 10) | 83 | const versionScript = parseInt(entity.version, 10) |
100 | 84 | ||
101 | // Do not execute old migration scripts | 85 | // Do not execute old migration scripts |
102 | if (versionScript <= actualVersion) return callback(null) | 86 | if (versionScript <= actualVersion) return undefined |
103 | 87 | ||
104 | // Load the migration module and run it | 88 | // Load the migration module and run it |
105 | const migrationScriptName = entity.script | 89 | const migrationScriptName = entity.script |
@@ -107,30 +91,17 @@ function executeMigration (actualVersion: number, entity: { version: string, scr | |||
107 | 91 | ||
108 | const migrationScript = require(path.join(__dirname, 'migrations', migrationScriptName)) | 92 | const migrationScript = require(path.join(__dirname, 'migrations', migrationScriptName)) |
109 | 93 | ||
110 | db.sequelize.transaction().asCallback(function (err, t) { | 94 | return db.sequelize.transaction(t => { |
111 | if (err) return callback(err) | ||
112 | |||
113 | const options = { | 95 | const options = { |
114 | transaction: t, | 96 | transaction: t, |
115 | queryInterface: db.sequelize.getQueryInterface(), | 97 | queryInterface: db.sequelize.getQueryInterface(), |
116 | sequelize: db.sequelize, | 98 | sequelize: db.sequelize |
117 | Sequelize: Sequelize | ||
118 | } | 99 | } |
119 | migrationScript.up(options, function (err) { | ||
120 | if (err) { | ||
121 | t.rollback() | ||
122 | return callback(err) | ||
123 | } | ||
124 | |||
125 | // Update the new migration version | ||
126 | db.Application.updateMigrationVersion(versionScript, t, function (err) { | ||
127 | if (err) { | ||
128 | t.rollback() | ||
129 | return callback(err) | ||
130 | } | ||
131 | 100 | ||
132 | t.commit().asCallback(callback) | 101 | migrationScript.up(options) |
102 | .then(() => { | ||
103 | // Update the new migration version | ||
104 | db.Application.updateMigrationVersion(versionScript, t) | ||
133 | }) | 105 | }) |
134 | }) | ||
135 | }) | 106 | }) |
136 | } | 107 | } |
diff --git a/server/lib/friends.ts b/server/lib/friends.ts index 522cb82b3..498144318 100644 --- a/server/lib/friends.ts +++ b/server/lib/friends.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { each, eachLimit, eachSeries, series, waterfall } from 'async' | ||
2 | import * as request from 'request' | 1 | import * as request from 'request' |
3 | import * as Sequelize from 'sequelize' | 2 | import * as Sequelize from 'sequelize' |
3 | import * as Promise from 'bluebird' | ||
4 | 4 | ||
5 | import { database as db } from '../initializers/database' | 5 | import { database as db } from '../initializers/database' |
6 | import { | 6 | import { |
@@ -15,8 +15,7 @@ import { | |||
15 | logger, | 15 | logger, |
16 | getMyPublicCert, | 16 | getMyPublicCert, |
17 | makeSecureRequest, | 17 | makeSecureRequest, |
18 | makeRetryRequest, | 18 | makeRetryRequest |
19 | createEmptyCallback | ||
20 | } from '../helpers' | 19 | } from '../helpers' |
21 | import { | 20 | import { |
22 | RequestScheduler, | 21 | RequestScheduler, |
@@ -53,24 +52,24 @@ function activateSchedulers () { | |||
53 | requestVideoEventScheduler.activate() | 52 | requestVideoEventScheduler.activate() |
54 | } | 53 | } |
55 | 54 | ||
56 | function addVideoToFriends (videoData: Object, transaction: Sequelize.Transaction, callback: (err: Error) => void) { | 55 | function addVideoToFriends (videoData: Object, transaction: Sequelize.Transaction) { |
57 | const options = { | 56 | const options = { |
58 | type: ENDPOINT_ACTIONS.ADD, | 57 | type: ENDPOINT_ACTIONS.ADD, |
59 | endpoint: REQUEST_ENDPOINTS.VIDEOS, | 58 | endpoint: REQUEST_ENDPOINTS.VIDEOS, |
60 | data: videoData, | 59 | data: videoData, |
61 | transaction | 60 | transaction |
62 | } | 61 | } |
63 | createRequest(options, callback) | 62 | return createRequest(options) |
64 | } | 63 | } |
65 | 64 | ||
66 | function updateVideoToFriends (videoData: Object, transaction: Sequelize.Transaction, callback: (err: Error) => void) { | 65 | function updateVideoToFriends (videoData: Object, transaction: Sequelize.Transaction) { |
67 | const options = { | 66 | const options = { |
68 | type: ENDPOINT_ACTIONS.UPDATE, | 67 | type: ENDPOINT_ACTIONS.UPDATE, |
69 | endpoint: REQUEST_ENDPOINTS.VIDEOS, | 68 | endpoint: REQUEST_ENDPOINTS.VIDEOS, |
70 | data: videoData, | 69 | data: videoData, |
71 | transaction | 70 | transaction |
72 | } | 71 | } |
73 | createRequest(options, callback) | 72 | return createRequest(options) |
74 | } | 73 | } |
75 | 74 | ||
76 | function removeVideoToFriends (videoParams: Object) { | 75 | function removeVideoToFriends (videoParams: Object) { |
@@ -80,121 +79,93 @@ function removeVideoToFriends (videoParams: Object) { | |||
80 | data: videoParams, | 79 | data: videoParams, |
81 | transaction: null | 80 | transaction: null |
82 | } | 81 | } |
83 | createRequest(options) | 82 | return createRequest(options) |
84 | } | 83 | } |
85 | 84 | ||
86 | function reportAbuseVideoToFriend (reportData: Object, video: VideoInstance) { | 85 | function reportAbuseVideoToFriend (reportData: Object, video: VideoInstance, transaction: Sequelize.Transaction) { |
87 | const options = { | 86 | const options = { |
88 | type: ENDPOINT_ACTIONS.REPORT_ABUSE, | 87 | type: ENDPOINT_ACTIONS.REPORT_ABUSE, |
89 | endpoint: REQUEST_ENDPOINTS.VIDEOS, | 88 | endpoint: REQUEST_ENDPOINTS.VIDEOS, |
90 | data: reportData, | 89 | data: reportData, |
91 | toIds: [ video.Author.podId ], | 90 | toIds: [ video.Author.podId ], |
92 | transaction: null | 91 | transaction |
93 | } | 92 | } |
94 | createRequest(options) | 93 | return createRequest(options) |
95 | } | 94 | } |
96 | 95 | ||
97 | function quickAndDirtyUpdateVideoToFriends (qaduParam: QaduParam, transaction?: Sequelize.Transaction, callback?: (err: Error) => void) { | 96 | function quickAndDirtyUpdateVideoToFriends (qaduParam: QaduParam, transaction?: Sequelize.Transaction) { |
98 | const options = { | 97 | const options = { |
99 | videoId: qaduParam.videoId, | 98 | videoId: qaduParam.videoId, |
100 | type: qaduParam.type, | 99 | type: qaduParam.type, |
101 | transaction | 100 | transaction |
102 | } | 101 | } |
103 | return createVideoQaduRequest(options, callback) | 102 | return createVideoQaduRequest(options) |
104 | } | 103 | } |
105 | 104 | ||
106 | function quickAndDirtyUpdatesVideoToFriends ( | 105 | function quickAndDirtyUpdatesVideoToFriends (qadusParams: QaduParam[], transaction: Sequelize.Transaction) { |
107 | qadusParams: QaduParam[], | ||
108 | transaction: Sequelize.Transaction, | ||
109 | finalCallback: (err: Error) => void | ||
110 | ) { | ||
111 | const tasks = [] | 106 | const tasks = [] |
112 | 107 | ||
113 | qadusParams.forEach(function (qaduParams) { | 108 | qadusParams.forEach(function (qaduParams) { |
114 | const fun = function (callback) { | 109 | tasks.push(quickAndDirtyUpdateVideoToFriends(qaduParams, transaction)) |
115 | quickAndDirtyUpdateVideoToFriends(qaduParams, transaction, callback) | ||
116 | } | ||
117 | |||
118 | tasks.push(fun) | ||
119 | }) | 110 | }) |
120 | 111 | ||
121 | series(tasks, finalCallback) | 112 | return Promise.all(tasks) |
122 | } | 113 | } |
123 | 114 | ||
124 | function addEventToRemoteVideo (eventParam: EventParam, transaction?: Sequelize.Transaction, callback?: (err: Error) => void) { | 115 | function addEventToRemoteVideo (eventParam: EventParam, transaction?: Sequelize.Transaction) { |
125 | const options = { | 116 | const options = { |
126 | videoId: eventParam.videoId, | 117 | videoId: eventParam.videoId, |
127 | type: eventParam.type, | 118 | type: eventParam.type, |
128 | transaction | 119 | transaction |
129 | } | 120 | } |
130 | createVideoEventRequest(options, callback) | 121 | return createVideoEventRequest(options) |
131 | } | 122 | } |
132 | 123 | ||
133 | function addEventsToRemoteVideo (eventsParams: EventParam[], transaction: Sequelize.Transaction, finalCallback: (err: Error) => void) { | 124 | function addEventsToRemoteVideo (eventsParams: EventParam[], transaction: Sequelize.Transaction) { |
134 | const tasks = [] | 125 | const tasks = [] |
135 | 126 | ||
136 | eventsParams.forEach(function (eventParams) { | 127 | eventsParams.forEach(function (eventParams) { |
137 | const fun = function (callback) { | 128 | tasks.push(addEventToRemoteVideo(eventParams, transaction)) |
138 | addEventToRemoteVideo(eventParams, transaction, callback) | ||
139 | } | ||
140 | |||
141 | tasks.push(fun) | ||
142 | }) | 129 | }) |
143 | 130 | ||
144 | series(tasks, finalCallback) | 131 | return Promise.all(tasks) |
145 | } | 132 | } |
146 | 133 | ||
147 | function hasFriends (callback: (err: Error, hasFriends?: boolean) => void) { | 134 | function hasFriends () { |
148 | db.Pod.countAll(function (err, count) { | 135 | return db.Pod.countAll().then(count => count !== 0) |
149 | if (err) return callback(err) | ||
150 | |||
151 | const hasFriends = (count !== 0) | ||
152 | callback(null, hasFriends) | ||
153 | }) | ||
154 | } | 136 | } |
155 | 137 | ||
156 | function makeFriends (hosts: string[], callback: (err: Error) => void) { | 138 | function makeFriends (hosts: string[]) { |
157 | const podsScore = {} | 139 | const podsScore = {} |
158 | 140 | ||
159 | logger.info('Make friends!') | 141 | logger.info('Make friends!') |
160 | getMyPublicCert(function (err, cert) { | 142 | return getMyPublicCert() |
161 | if (err) { | 143 | .then(cert => { |
162 | logger.error('Cannot read public cert.') | 144 | return Promise.mapSeries(hosts, host => { |
163 | return callback(err) | 145 | return computeForeignPodsList(host, podsScore) |
164 | } | 146 | }).then(() => cert) |
165 | 147 | }) | |
166 | eachSeries(hosts, function (host, callbackEach) { | 148 | .then(cert => { |
167 | computeForeignPodsList(host, podsScore, callbackEach) | ||
168 | }, function (err: Error) { | ||
169 | if (err) return callback(err) | ||
170 | |||
171 | logger.debug('Pods scores computed.', { podsScore: podsScore }) | 149 | logger.debug('Pods scores computed.', { podsScore: podsScore }) |
172 | const podsList = computeWinningPods(hosts, podsScore) | 150 | const podsList = computeWinningPods(hosts, podsScore) |
173 | logger.debug('Pods that we keep.', { podsToKeep: podsList }) | 151 | logger.debug('Pods that we keep.', { podsToKeep: podsList }) |
174 | 152 | ||
175 | makeRequestsToWinningPods(cert, podsList, callback) | 153 | return makeRequestsToWinningPods(cert, podsList) |
176 | }) | 154 | }) |
177 | }) | ||
178 | } | 155 | } |
179 | 156 | ||
180 | function quitFriends (callback: (err: Error) => void) { | 157 | function quitFriends () { |
181 | // Stop pool requests | 158 | // Stop pool requests |
182 | requestScheduler.deactivate() | 159 | requestScheduler.deactivate() |
183 | 160 | ||
184 | waterfall([ | 161 | return requestScheduler.flush() |
185 | function flushRequests (callbackAsync) { | 162 | .then(() => { |
186 | requestScheduler.flush(err => callbackAsync(err)) | 163 | return requestVideoQaduScheduler.flush() |
187 | }, | 164 | }) |
188 | 165 | .then(() => { | |
189 | function flushVideoQaduRequests (callbackAsync) { | 166 | return db.Pod.list() |
190 | requestVideoQaduScheduler.flush(err => callbackAsync(err)) | 167 | }) |
191 | }, | 168 | .then(pods => { |
192 | |||
193 | function getPodsList (callbackAsync) { | ||
194 | return db.Pod.list(callbackAsync) | ||
195 | }, | ||
196 | |||
197 | function announceIQuitMyFriends (pods, callbackAsync) { | ||
198 | const requestParams = { | 169 | const requestParams = { |
199 | method: 'POST' as 'POST', | 170 | method: 'POST' as 'POST', |
200 | path: '/api/' + API_VERSION + '/remote/pods/remove', | 171 | path: '/api/' + API_VERSION + '/remote/pods/remove', |
@@ -205,61 +176,57 @@ function quitFriends (callback: (err: Error) => void) { | |||
205 | // Announce we quit them | 176 | // Announce we quit them |
206 | // We don't care if the request fails | 177 | // We don't care if the request fails |
207 | // The other pod will exclude us automatically after a while | 178 | // The other pod will exclude us automatically after a while |
208 | eachLimit(pods, REQUESTS_IN_PARALLEL, function (pod, callbackEach) { | 179 | return Promise.map(pods, pod => { |
209 | requestParams.toPod = pod | 180 | requestParams.toPod = pod |
210 | makeSecureRequest(requestParams, callbackEach) | 181 | return makeSecureRequest(requestParams) |
211 | }, function (err) { | 182 | }, { concurrency: REQUESTS_IN_PARALLEL }) |
212 | if (err) { | 183 | .then(() => pods) |
213 | logger.error('Some errors while quitting friends.', { err: err }) | 184 | .catch(err => { |
214 | // Don't stop the process | 185 | logger.error('Some errors while quitting friends.', { err: err }) |
215 | } | 186 | // Don't stop the process |
216 | |||
217 | return callbackAsync(null, pods) | ||
218 | }) | 187 | }) |
219 | }, | 188 | }) |
220 | 189 | .then(pods => { | |
221 | function removePodsFromDB (pods, callbackAsync) { | 190 | const tasks = [] |
222 | each(pods, function (pod: any, callbackEach) { | 191 | pods.forEach(pod => tasks.push(pod.destroy())) |
223 | pod.destroy().asCallback(callbackEach) | ||
224 | }, callbackAsync) | ||
225 | } | ||
226 | ], function (err: Error) { | ||
227 | // Don't forget to re activate the scheduler, even if there was an error | ||
228 | requestScheduler.activate() | ||
229 | |||
230 | if (err) return callback(err) | ||
231 | 192 | ||
232 | logger.info('Removed all remote videos.') | 193 | return Promise.all(pods) |
233 | return callback(null) | 194 | }) |
234 | }) | 195 | .then(() => { |
196 | logger.info('Removed all remote videos.') | ||
197 | // Don't forget to re activate the scheduler, even if there was an error | ||
198 | return requestScheduler.activate() | ||
199 | }) | ||
200 | .finally(() => requestScheduler.activate()) | ||
235 | } | 201 | } |
236 | 202 | ||
237 | function sendOwnedVideosToPod (podId: number) { | 203 | function sendOwnedVideosToPod (podId: number) { |
238 | db.Video.listOwnedAndPopulateAuthorAndTags(function (err, videosList) { | 204 | db.Video.listOwnedAndPopulateAuthorAndTags() |
239 | if (err) { | 205 | .then(videosList => { |
240 | logger.error('Cannot get the list of videos we own.') | 206 | const tasks = [] |
241 | return | 207 | videosList.forEach(video => { |
242 | } | 208 | const promise = video.toAddRemoteJSON() |
243 | 209 | .then(remoteVideo => { | |
244 | videosList.forEach(function (video) { | 210 | const options = { |
245 | video.toAddRemoteJSON(function (err, remoteVideo) { | 211 | type: 'add', |
246 | if (err) { | 212 | endpoint: REQUEST_ENDPOINTS.VIDEOS, |
247 | logger.error('Cannot convert video to remote.', { error: err }) | 213 | data: remoteVideo, |
248 | // Don't break the process | 214 | toIds: [ podId ], |
249 | return | 215 | transaction: null |
250 | } | 216 | } |
251 | 217 | return createRequest(options) | |
252 | const options = { | 218 | }) |
253 | type: 'add', | 219 | .catch(err => { |
254 | endpoint: REQUEST_ENDPOINTS.VIDEOS, | 220 | logger.error('Cannot convert video to remote.', { error: err }) |
255 | data: remoteVideo, | 221 | // Don't break the process |
256 | toIds: [ podId ], | 222 | return undefined |
257 | transaction: null | 223 | }) |
258 | } | 224 | |
259 | createRequest(options) | 225 | tasks.push(promise) |
260 | }) | 226 | }) |
227 | |||
228 | return Promise.all(tasks) | ||
261 | }) | 229 | }) |
262 | }) | ||
263 | } | 230 | } |
264 | 231 | ||
265 | function getRequestScheduler () { | 232 | function getRequestScheduler () { |
@@ -297,23 +264,22 @@ export { | |||
297 | 264 | ||
298 | // --------------------------------------------------------------------------- | 265 | // --------------------------------------------------------------------------- |
299 | 266 | ||
300 | function computeForeignPodsList (host: string, podsScore: { [ host: string ]: number }, callback: (err: Error) => void) { | 267 | function computeForeignPodsList (host: string, podsScore: { [ host: string ]: number }) { |
301 | getForeignPodsList(host, function (err, res) { | 268 | // TODO: type res |
302 | if (err) return callback(err) | 269 | return getForeignPodsList(host).then((res: any) => { |
303 | |||
304 | const foreignPodsList = res.data | 270 | const foreignPodsList = res.data |
305 | 271 | ||
306 | // Let's give 1 point to the pod we ask the friends list | 272 | // Let's give 1 point to the pod we ask the friends list |
307 | foreignPodsList.push({ host }) | 273 | foreignPodsList.push({ host }) |
308 | 274 | ||
309 | foreignPodsList.forEach(function (foreignPod) { | 275 | foreignPodsList.forEach(foreignPod => { |
310 | const foreignPodHost = foreignPod.host | 276 | const foreignPodHost = foreignPod.host |
311 | 277 | ||
312 | if (podsScore[foreignPodHost]) podsScore[foreignPodHost]++ | 278 | if (podsScore[foreignPodHost]) podsScore[foreignPodHost]++ |
313 | else podsScore[foreignPodHost] = 1 | 279 | else podsScore[foreignPodHost] = 1 |
314 | }) | 280 | }) |
315 | 281 | ||
316 | return callback(null) | 282 | return undefined |
317 | }) | 283 | }) |
318 | } | 284 | } |
319 | 285 | ||
@@ -323,7 +289,7 @@ function computeWinningPods (hosts: string[], podsScore: { [ host: string ]: num | |||
323 | const podsList = [] | 289 | const podsList = [] |
324 | const baseScore = hosts.length / 2 | 290 | const baseScore = hosts.length / 2 |
325 | 291 | ||
326 | Object.keys(podsScore).forEach(function (podHost) { | 292 | Object.keys(podsScore).forEach(podHost => { |
327 | // If the pod is not me and with a good score we add it | 293 | // If the pod is not me and with a good score we add it |
328 | if (isMe(podHost) === false && podsScore[podHost] > baseScore) { | 294 | if (isMe(podHost) === false && podsScore[podHost] > baseScore) { |
329 | podsList.push({ host: podHost }) | 295 | podsList.push({ host: podHost }) |
@@ -333,28 +299,30 @@ function computeWinningPods (hosts: string[], podsScore: { [ host: string ]: num | |||
333 | return podsList | 299 | return podsList |
334 | } | 300 | } |
335 | 301 | ||
336 | function getForeignPodsList (host: string, callback: (err: Error, foreignPodsList?: any) => void) { | 302 | function getForeignPodsList (host: string) { |
337 | const path = '/api/' + API_VERSION + '/pods' | 303 | return new Promise((res, rej) => { |
304 | const path = '/api/' + API_VERSION + '/pods' | ||
338 | 305 | ||
339 | request.get(REMOTE_SCHEME.HTTP + '://' + host + path, function (err, response, body) { | 306 | request.get(REMOTE_SCHEME.HTTP + '://' + host + path, function (err, response, body) { |
340 | if (err) return callback(err) | 307 | if (err) return rej(err) |
341 | 308 | ||
342 | try { | 309 | try { |
343 | const json = JSON.parse(body) | 310 | const json = JSON.parse(body) |
344 | return callback(null, json) | 311 | return res(json) |
345 | } catch (err) { | 312 | } catch (err) { |
346 | return callback(err) | 313 | return rej(err) |
347 | } | 314 | } |
315 | }) | ||
348 | }) | 316 | }) |
349 | } | 317 | } |
350 | 318 | ||
351 | function makeRequestsToWinningPods (cert: string, podsList: PodInstance[], callback: (err: Error) => void) { | 319 | function makeRequestsToWinningPods (cert: string, podsList: PodInstance[]) { |
352 | // Stop pool requests | 320 | // Stop pool requests |
353 | requestScheduler.deactivate() | 321 | requestScheduler.deactivate() |
354 | // Flush pool requests | 322 | // Flush pool requests |
355 | requestScheduler.forceSend() | 323 | requestScheduler.forceSend() |
356 | 324 | ||
357 | eachLimit(podsList, REQUESTS_IN_PARALLEL, function (pod: PodInstance, callbackEach) { | 325 | return Promise.map(podsList, pod => { |
358 | const params = { | 326 | const params = { |
359 | url: REMOTE_SCHEME.HTTP + '://' + pod.host + '/api/' + API_VERSION + '/pods/', | 327 | url: REMOTE_SCHEME.HTTP + '://' + pod.host + '/api/' + API_VERSION + '/pods/', |
360 | method: 'POST' as 'POST', | 328 | method: 'POST' as 'POST', |
@@ -365,38 +333,35 @@ function makeRequestsToWinningPods (cert: string, podsList: PodInstance[], callb | |||
365 | } | 333 | } |
366 | } | 334 | } |
367 | 335 | ||
368 | makeRetryRequest(params, function (err, res, body: { cert: string, email: string }) { | 336 | return makeRetryRequest(params) |
369 | if (err) { | 337 | .then(({ response, body }) => { |
370 | logger.error('Error with adding %s pod.', pod.host, { error: err }) | 338 | body = body as { cert: string, email: string } |
339 | |||
340 | if (response.statusCode === 200) { | ||
341 | const podObj = db.Pod.build({ host: pod.host, publicKey: body.cert, email: body.email }) | ||
342 | return podObj.save() | ||
343 | .then(podCreated => { | ||
344 | |||
345 | // Add our videos to the request scheduler | ||
346 | sendOwnedVideosToPod(podCreated.id) | ||
347 | }) | ||
348 | .catch(err => { | ||
349 | logger.error('Cannot add friend %s pod.', pod.host, { error: err }) | ||
350 | }) | ||
351 | } else { | ||
352 | logger.error('Status not 200 for %s pod.', pod.host) | ||
353 | } | ||
354 | }) | ||
355 | .catch(err => { | ||
356 | logger.error('Error with adding %s pod.', pod.host, { error: err.stack }) | ||
371 | // Don't break the process | 357 | // Don't break the process |
372 | return callbackEach() | 358 | }) |
373 | } | 359 | }, { concurrency: REQUESTS_IN_PARALLEL }) |
374 | 360 | .then(() => logger.debug('makeRequestsToWinningPods finished.')) | |
375 | if (res.statusCode === 200) { | 361 | .finally(() => { |
376 | const podObj = db.Pod.build({ host: pod.host, publicKey: body.cert, email: body.email }) | ||
377 | podObj.save().asCallback(function (err, podCreated) { | ||
378 | if (err) { | ||
379 | logger.error('Cannot add friend %s pod.', pod.host, { error: err }) | ||
380 | return callbackEach() | ||
381 | } | ||
382 | |||
383 | // Add our videos to the request scheduler | ||
384 | sendOwnedVideosToPod(podCreated.id) | ||
385 | |||
386 | return callbackEach() | ||
387 | }) | ||
388 | } else { | ||
389 | logger.error('Status not 200 for %s pod.', pod.host) | ||
390 | return callbackEach() | ||
391 | } | ||
392 | }) | ||
393 | }, function endRequests () { | ||
394 | // Final callback, we've ended all the requests | 362 | // Final callback, we've ended all the requests |
395 | // Now we made new friends, we can re activate the pool of requests | 363 | // Now we made new friends, we can re activate the pool of requests |
396 | requestScheduler.activate() | 364 | requestScheduler.activate() |
397 | |||
398 | logger.debug('makeRequestsToWinningPods finished.') | ||
399 | return callback(null) | ||
400 | }) | 365 | }) |
401 | } | 366 | } |
402 | 367 | ||
@@ -408,33 +373,22 @@ type CreateRequestOptions = { | |||
408 | toIds?: number[] | 373 | toIds?: number[] |
409 | transaction: Sequelize.Transaction | 374 | transaction: Sequelize.Transaction |
410 | } | 375 | } |
411 | function createRequest (options: CreateRequestOptions, callback?: (err: Error) => void) { | 376 | function createRequest (options: CreateRequestOptions) { |
412 | if (!callback) callback = function () { /* empty */ } | 377 | if (options.toIds !== undefined) return requestScheduler.createRequest(options as RequestSchedulerOptions) |
413 | |||
414 | if (options.toIds !== undefined) return requestScheduler.createRequest(options as RequestSchedulerOptions, callback) | ||
415 | 378 | ||
416 | // If the "toIds" pods is not specified, we send the request to all our friends | 379 | // If the "toIds" pods is not specified, we send the request to all our friends |
417 | db.Pod.listAllIds(options.transaction, function (err, podIds) { | 380 | return db.Pod.listAllIds(options.transaction).then(podIds => { |
418 | if (err) { | ||
419 | logger.error('Cannot get pod ids', { error: err }) | ||
420 | return | ||
421 | } | ||
422 | |||
423 | const newOptions = Object.assign(options, { toIds: podIds }) | 381 | const newOptions = Object.assign(options, { toIds: podIds }) |
424 | return requestScheduler.createRequest(newOptions, callback) | 382 | return requestScheduler.createRequest(newOptions) |
425 | }) | 383 | }) |
426 | } | 384 | } |
427 | 385 | ||
428 | function createVideoQaduRequest (options: RequestVideoQaduSchedulerOptions, callback: (err: Error) => void) { | 386 | function createVideoQaduRequest (options: RequestVideoQaduSchedulerOptions) { |
429 | if (!callback) callback = createEmptyCallback() | 387 | return requestVideoQaduScheduler.createRequest(options) |
430 | |||
431 | requestVideoQaduScheduler.createRequest(options, callback) | ||
432 | } | 388 | } |
433 | 389 | ||
434 | function createVideoEventRequest (options: RequestVideoEventSchedulerOptions, callback: (err: Error) => void) { | 390 | function createVideoEventRequest (options: RequestVideoEventSchedulerOptions) { |
435 | if (!callback) callback = createEmptyCallback() | 391 | return requestVideoEventScheduler.createRequest(options) |
436 | |||
437 | requestVideoEventScheduler.createRequest(options, callback) | ||
438 | } | 392 | } |
439 | 393 | ||
440 | function isMe (host: string) { | 394 | function isMe (host: string) { |
diff --git a/server/lib/jobs/handlers/index.ts b/server/lib/jobs/handlers/index.ts index 7d0263b15..8abddae35 100644 --- a/server/lib/jobs/handlers/index.ts +++ b/server/lib/jobs/handlers/index.ts | |||
@@ -1,11 +1,9 @@ | |||
1 | import * as videoTranscoder from './video-transcoder' | 1 | import * as videoTranscoder from './video-transcoder' |
2 | 2 | ||
3 | import { VideoInstance } from '../../../models' | ||
4 | |||
5 | export interface JobHandler<T> { | 3 | export interface JobHandler<T> { |
6 | process (data: object, callback: (err: Error, videoInstance?: T) => void) | 4 | process (data: object): T |
7 | onError (err: Error, jobId: number, video: T, callback: (err: Error) => void) | 5 | onError (err: Error, jobId: number) |
8 | onSuccess (data: any, jobId: number, video: T, callback: (err: Error) => void) | 6 | onSuccess (jobId: number, jobResult: T) |
9 | } | 7 | } |
10 | 8 | ||
11 | const jobHandlers: { [ handlerName: string ]: JobHandler<any> } = { | 9 | const jobHandlers: { [ handlerName: string ]: JobHandler<any> } = { |
diff --git a/server/lib/jobs/handlers/video-transcoder.ts b/server/lib/jobs/handlers/video-transcoder.ts index 6f606a7d3..e829ca813 100644 --- a/server/lib/jobs/handlers/video-transcoder.ts +++ b/server/lib/jobs/handlers/video-transcoder.ts | |||
@@ -3,29 +3,23 @@ import { logger } from '../../../helpers' | |||
3 | import { addVideoToFriends } from '../../../lib' | 3 | import { addVideoToFriends } from '../../../lib' |
4 | import { VideoInstance } from '../../../models' | 4 | import { VideoInstance } from '../../../models' |
5 | 5 | ||
6 | function process (data: { id: string }, callback: (err: Error, videoInstance?: VideoInstance) => void) { | 6 | function process (data: { id: string }) { |
7 | db.Video.loadAndPopulateAuthorAndPodAndTags(data.id, function (err, video) { | 7 | return db.Video.loadAndPopulateAuthorAndPodAndTags(data.id).then(video => { |
8 | if (err) return callback(err) | 8 | return video.transcodeVideofile().then(() => video) |
9 | |||
10 | video.transcodeVideofile(function (err) { | ||
11 | return callback(err, video) | ||
12 | }) | ||
13 | }) | 9 | }) |
14 | } | 10 | } |
15 | 11 | ||
16 | function onError (err: Error, jobId: number, video: VideoInstance, callback: (err: Error) => void) { | 12 | function onError (err: Error, jobId: number) { |
17 | logger.error('Error when transcoding video file in job %d.', jobId, { error: err }) | 13 | logger.error('Error when transcoding video file in job %d.', jobId, { error: err }) |
18 | return callback(null) | 14 | return Promise.resolve() |
19 | } | 15 | } |
20 | 16 | ||
21 | function onSuccess (data: any, jobId: number, video: VideoInstance, callback: (err: Error) => void) { | 17 | function onSuccess (jobId: number, video: VideoInstance) { |
22 | logger.info('Job %d is a success.', jobId) | 18 | logger.info('Job %d is a success.', jobId) |
23 | 19 | ||
24 | video.toAddRemoteJSON(function (err, remoteVideo) { | 20 | video.toAddRemoteJSON().then(remoteVideo => { |
25 | if (err) return callback(err) | ||
26 | |||
27 | // Now we'll add the video's meta data to our friends | 21 | // Now we'll add the video's meta data to our friends |
28 | addVideoToFriends(remoteVideo, null, callback) | 22 | return addVideoToFriends(remoteVideo, null) |
29 | }) | 23 | }) |
30 | } | 24 | } |
31 | 25 | ||
diff --git a/server/lib/jobs/job-scheduler.ts b/server/lib/jobs/job-scheduler.ts index 2f01387e7..248dc7978 100644 --- a/server/lib/jobs/job-scheduler.ts +++ b/server/lib/jobs/job-scheduler.ts | |||
@@ -32,37 +32,35 @@ class JobScheduler { | |||
32 | 32 | ||
33 | // Finish processing jobs from a previous start | 33 | // Finish processing jobs from a previous start |
34 | const state = JOB_STATES.PROCESSING | 34 | const state = JOB_STATES.PROCESSING |
35 | db.Job.listWithLimit(limit, state, (err, jobs) => { | 35 | db.Job.listWithLimit(limit, state) |
36 | this.enqueueJobs(err, jobsQueue, jobs) | 36 | .then(jobs => { |
37 | 37 | this.enqueueJobs(jobsQueue, jobs) | |
38 | forever( | 38 | |
39 | next => { | 39 | forever( |
40 | if (jobsQueue.length() !== 0) { | 40 | next => { |
41 | // Finish processing the queue first | 41 | if (jobsQueue.length() !== 0) { |
42 | return setTimeout(next, JOBS_FETCHING_INTERVAL) | 42 | // Finish processing the queue first |
43 | } | 43 | return setTimeout(next, JOBS_FETCHING_INTERVAL) |
44 | |||
45 | const state = JOB_STATES.PENDING | ||
46 | db.Job.listWithLimit(limit, state, (err, jobs) => { | ||
47 | if (err) { | ||
48 | logger.error('Cannot list pending jobs.', { error: err }) | ||
49 | } else { | ||
50 | jobs.forEach(job => { | ||
51 | jobsQueue.push(job) | ||
52 | }) | ||
53 | } | 44 | } |
54 | 45 | ||
55 | // Optimization: we could use "drain" from queue object | 46 | const state = JOB_STATES.PENDING |
56 | return setTimeout(next, JOBS_FETCHING_INTERVAL) | 47 | db.Job.listWithLimit(limit, state) |
57 | }) | 48 | .then(jobs => { |
58 | }, | 49 | this.enqueueJobs(jobsQueue, jobs) |
59 | 50 | ||
60 | err => { logger.error('Error in job scheduler queue.', { error: err }) } | 51 | // Optimization: we could use "drain" from queue object |
61 | ) | 52 | return setTimeout(next, JOBS_FETCHING_INTERVAL) |
62 | }) | 53 | }) |
54 | .catch(err => logger.error('Cannot list pending jobs.', { error: err })) | ||
55 | }, | ||
56 | |||
57 | err => logger.error('Error in job scheduler queue.', { error: err }) | ||
58 | ) | ||
59 | }) | ||
60 | .catch(err => logger.error('Cannot list pending jobs.', { error: err })) | ||
63 | } | 61 | } |
64 | 62 | ||
65 | createJob (transaction: Sequelize.Transaction, handlerName: string, handlerInputData: object, callback: (err: Error) => void) { | 63 | createJob (transaction: Sequelize.Transaction, handlerName: string, handlerInputData: object) { |
66 | const createQuery = { | 64 | const createQuery = { |
67 | state: JOB_STATES.PENDING, | 65 | state: JOB_STATES.PENDING, |
68 | handlerName, | 66 | handlerName, |
@@ -70,67 +68,62 @@ class JobScheduler { | |||
70 | } | 68 | } |
71 | const options = { transaction } | 69 | const options = { transaction } |
72 | 70 | ||
73 | db.Job.create(createQuery, options).asCallback(callback) | 71 | return db.Job.create(createQuery, options) |
74 | } | 72 | } |
75 | 73 | ||
76 | private enqueueJobs (err: Error, jobsQueue: AsyncQueue<JobInstance>, jobs: JobInstance[]) { | 74 | private enqueueJobs (jobsQueue: AsyncQueue<JobInstance>, jobs: JobInstance[]) { |
77 | if (err) { | 75 | jobs.forEach(job => jobsQueue.push(job)) |
78 | logger.error('Cannot list pending jobs.', { error: err }) | ||
79 | } else { | ||
80 | jobs.forEach(job => { | ||
81 | jobsQueue.push(job) | ||
82 | }) | ||
83 | } | ||
84 | } | 76 | } |
85 | 77 | ||
86 | private processJob (job: JobInstance, callback: (err: Error) => void) { | 78 | private processJob (job: JobInstance, callback: (err: Error) => void) { |
87 | const jobHandler = jobHandlers[job.handlerName] | 79 | const jobHandler = jobHandlers[job.handlerName] |
80 | if (jobHandler === undefined) { | ||
81 | logger.error('Unknown job handler for job %s.', job.handlerName) | ||
82 | return callback(null) | ||
83 | } | ||
88 | 84 | ||
89 | logger.info('Processing job %d with handler %s.', job.id, job.handlerName) | 85 | logger.info('Processing job %d with handler %s.', job.id, job.handlerName) |
90 | 86 | ||
91 | job.state = JOB_STATES.PROCESSING | 87 | job.state = JOB_STATES.PROCESSING |
92 | job.save().asCallback(err => { | 88 | return job.save() |
93 | if (err) return this.cannotSaveJobError(err, callback) | 89 | .then(() => { |
94 | 90 | return jobHandler.process(job.handlerInputData) | |
95 | if (jobHandler === undefined) { | 91 | }) |
96 | logger.error('Unknown job handler for job %s.', job.handlerName) | 92 | .then( |
97 | return callback(null) | 93 | result => { |
98 | } | 94 | return this.onJobSuccess(jobHandler, job, result) |
95 | }, | ||
99 | 96 | ||
100 | return jobHandler.process(job.handlerInputData, (err, result) => { | 97 | err => { |
101 | if (err) { | ||
102 | logger.error('Error in job handler %s.', job.handlerName, { error: err }) | 98 | logger.error('Error in job handler %s.', job.handlerName, { error: err }) |
103 | return this.onJobError(jobHandler, job, result, callback) | 99 | return this.onJobError(jobHandler, job, err) |
104 | } | 100 | } |
105 | 101 | ) | |
106 | return this.onJobSuccess(jobHandler, job, result, callback) | 102 | .then(() => callback(null)) |
103 | .catch(err => { | ||
104 | this.cannotSaveJobError(err) | ||
105 | return callback(err) | ||
107 | }) | 106 | }) |
108 | }) | ||
109 | } | 107 | } |
110 | 108 | ||
111 | private onJobError (jobHandler: JobHandler<any>, job: JobInstance, jobResult: any, callback: (err: Error) => void) { | 109 | private onJobError (jobHandler: JobHandler<any>, job: JobInstance, err: Error) { |
112 | job.state = JOB_STATES.ERROR | 110 | job.state = JOB_STATES.ERROR |
113 | 111 | ||
114 | job.save().asCallback(err => { | 112 | return job.save() |
115 | if (err) return this.cannotSaveJobError(err, callback) | 113 | .then(() => jobHandler.onError(err, job.id)) |
116 | 114 | .catch(err => this.cannotSaveJobError(err)) | |
117 | return jobHandler.onError(err, job.id, jobResult, callback) | ||
118 | }) | ||
119 | } | 115 | } |
120 | 116 | ||
121 | private onJobSuccess (jobHandler: JobHandler<any>, job: JobInstance, jobResult: any, callback: (err: Error) => void) { | 117 | private onJobSuccess (jobHandler: JobHandler<any>, job: JobInstance, jobResult: any) { |
122 | job.state = JOB_STATES.SUCCESS | 118 | job.state = JOB_STATES.SUCCESS |
123 | 119 | ||
124 | job.save().asCallback(err => { | 120 | return job.save() |
125 | if (err) return this.cannotSaveJobError(err, callback) | 121 | .then(() => jobHandler.onSuccess(job.id, jobResult)) |
126 | 122 | .catch(err => this.cannotSaveJobError(err)) | |
127 | return jobHandler.onSuccess(err, job.id, jobResult, callback) | ||
128 | }) | ||
129 | } | 123 | } |
130 | 124 | ||
131 | private cannotSaveJobError (err: Error, callback: (err: Error) => void) { | 125 | private cannotSaveJobError (err: Error) { |
132 | logger.error('Cannot save new job state.', { error: err }) | 126 | logger.error('Cannot save new job state.', { error: err }) |
133 | return callback(err) | ||
134 | } | 127 | } |
135 | } | 128 | } |
136 | 129 | ||
diff --git a/server/lib/oauth-model.ts b/server/lib/oauth-model.ts index 7cf42e94c..f34c9c667 100644 --- a/server/lib/oauth-model.ts +++ b/server/lib/oauth-model.ts | |||
@@ -30,17 +30,10 @@ function getUser (username: string, password: string) { | |||
30 | return db.User.getByUsername(username).then(function (user) { | 30 | return db.User.getByUsername(username).then(function (user) { |
31 | if (!user) return null | 31 | if (!user) return null |
32 | 32 | ||
33 | // We need to return a promise | 33 | return user.isPasswordMatch(password).then(passwordMatch => { |
34 | return new Promise(function (resolve, reject) { | 34 | if (passwordMatch === false) return null |
35 | return user.isPasswordMatch(password, function (err, isPasswordMatch) { | ||
36 | if (err) return reject(err) | ||
37 | 35 | ||
38 | if (isPasswordMatch === true) { | 36 | return user |
39 | return resolve(user) | ||
40 | } | ||
41 | |||
42 | return resolve(null) | ||
43 | }) | ||
44 | }) | 37 | }) |
45 | }) | 38 | }) |
46 | } | 39 | } |
@@ -80,8 +73,6 @@ function saveToken (token: TokenInfo, client: OAuthClientInstance, user: UserIns | |||
80 | tokenCreated.user = user | 73 | tokenCreated.user = user |
81 | 74 | ||
82 | return tokenCreated | 75 | return tokenCreated |
83 | }).catch(function (err) { | ||
84 | throw err | ||
85 | }) | 76 | }) |
86 | } | 77 | } |
87 | 78 | ||
diff --git a/server/lib/request/abstract-request-scheduler.ts b/server/lib/request/abstract-request-scheduler.ts index e81ab9c36..dd77fddb7 100644 --- a/server/lib/request/abstract-request-scheduler.ts +++ b/server/lib/request/abstract-request-scheduler.ts | |||
@@ -1,15 +1,16 @@ | |||
1 | import * as eachLimit from 'async/eachLimit' | 1 | import { isEmpty } from 'lodash' |
2 | import * as Promise from 'bluebird' | ||
2 | 3 | ||
3 | import { database as db } from '../../initializers/database' | 4 | import { database as db } from '../../initializers/database' |
4 | import { logger, makeSecureRequest } from '../../helpers' | 5 | import { logger, makeSecureRequest } from '../../helpers' |
5 | import { PodInstance } from '../../models' | 6 | import { AbstractRequestClass, AbstractRequestToPodClass, PodInstance } from '../../models' |
6 | import { | 7 | import { |
7 | API_VERSION, | 8 | API_VERSION, |
8 | REQUESTS_IN_PARALLEL, | 9 | REQUESTS_IN_PARALLEL, |
9 | REQUESTS_INTERVAL | 10 | REQUESTS_INTERVAL |
10 | } from '../../initializers' | 11 | } from '../../initializers' |
11 | 12 | ||
12 | abstract class AbstractRequestScheduler { | 13 | abstract class AbstractRequestScheduler <T> { |
13 | requestInterval: number | 14 | requestInterval: number |
14 | limitPods: number | 15 | limitPods: number |
15 | limitPerPod: number | 16 | limitPerPod: number |
@@ -24,9 +25,9 @@ abstract class AbstractRequestScheduler { | |||
24 | this.requestInterval = REQUESTS_INTERVAL | 25 | this.requestInterval = REQUESTS_INTERVAL |
25 | } | 26 | } |
26 | 27 | ||
27 | abstract getRequestModel () | 28 | abstract getRequestModel (): AbstractRequestClass<T> |
28 | abstract getRequestToPodModel () | 29 | abstract getRequestToPodModel (): AbstractRequestToPodClass |
29 | abstract buildRequestObjects (requests: any) | 30 | abstract buildRequestObjects (requestsGrouped: T): {} |
30 | 31 | ||
31 | activate () { | 32 | activate () { |
32 | logger.info('Requests scheduler activated.') | 33 | logger.info('Requests scheduler activated.') |
@@ -55,20 +56,18 @@ abstract class AbstractRequestScheduler { | |||
55 | return REQUESTS_INTERVAL - (Date.now() - this.lastRequestTimestamp) | 56 | return REQUESTS_INTERVAL - (Date.now() - this.lastRequestTimestamp) |
56 | } | 57 | } |
57 | 58 | ||
58 | remainingRequestsCount (callback: (err: Error, total: number) => void) { | 59 | remainingRequestsCount () { |
59 | return this.getRequestModel().countTotalRequests(callback) | 60 | return this.getRequestModel().countTotalRequests() |
60 | } | 61 | } |
61 | 62 | ||
62 | flush (callback: (err: Error) => void) { | 63 | flush () { |
63 | this.getRequestModel().removeAll(callback) | 64 | return this.getRequestModel().removeAll() |
64 | } | 65 | } |
65 | 66 | ||
66 | // --------------------------------------------------------------------------- | 67 | // --------------------------------------------------------------------------- |
67 | 68 | ||
68 | // Make a requests to friends of a certain type | 69 | // Make a requests to friends of a certain type |
69 | protected makeRequest (toPod: PodInstance, requestEndpoint: string, requestsToMake: Object, callback) { | 70 | protected makeRequest (toPod: PodInstance, requestEndpoint: string, requestsToMake: Object) { |
70 | if (!callback) callback = function () { /* empty */ } | ||
71 | |||
72 | const params = { | 71 | const params = { |
73 | toPod: toPod, | 72 | toPod: toPod, |
74 | sign: true, // Prove our identity | 73 | sign: true, // Prove our identity |
@@ -79,65 +78,64 @@ abstract class AbstractRequestScheduler { | |||
79 | 78 | ||
80 | // Make multiple retry requests to all of pods | 79 | // Make multiple retry requests to all of pods |
81 | // The function fire some useful callbacks | 80 | // The function fire some useful callbacks |
82 | makeSecureRequest(params, (err, res) => { | 81 | return makeSecureRequest(params) |
83 | if (err || (res.statusCode !== 200 && res.statusCode !== 201 && res.statusCode !== 204)) { | 82 | .then(({ response, body }) => { |
84 | err = err ? err.message : 'Status code not 20x : ' + res.statusCode | 83 | if (response.statusCode !== 200 && response.statusCode !== 201 && response.statusCode !== 204) { |
84 | throw new Error('Status code not 20x : ' + response.statusCode) | ||
85 | } | ||
86 | }) | ||
87 | .catch(err => { | ||
85 | logger.error('Error sending secure request to %s pod.', toPod.host, { error: err }) | 88 | logger.error('Error sending secure request to %s pod.', toPod.host, { error: err }) |
86 | 89 | ||
87 | return callback(err) | 90 | throw err |
88 | } | 91 | }) |
89 | |||
90 | return callback(null) | ||
91 | }) | ||
92 | } | 92 | } |
93 | 93 | ||
94 | // Make all the requests of the scheduler | 94 | // Make all the requests of the scheduler |
95 | protected makeRequests () { | 95 | protected makeRequests () { |
96 | this.getRequestModel().listWithLimitAndRandom(this.limitPods, this.limitPerPod, (err, requests) => { | 96 | return this.getRequestModel().listWithLimitAndRandom(this.limitPods, this.limitPerPod) |
97 | if (err) { | 97 | .then((requestsGrouped: T) => { |
98 | logger.error('Cannot get the list of "%s".', this.description, { err: err }) | 98 | // We want to group requests by destinations pod and endpoint |
99 | return // Abort | 99 | const requestsToMake = this.buildRequestObjects(requestsGrouped) |
100 | } | 100 | |
101 | 101 | // If there are no requests, abort | |
102 | // If there are no requests, abort | 102 | if (isEmpty(requestsToMake) === true) { |
103 | if (requests.length === 0) { | 103 | logger.info('No "%s" to make.', this.description) |
104 | logger.info('No "%s" to make.', this.description) | 104 | return { goodPods: [], badPods: [] } |
105 | return | 105 | } |
106 | } | 106 | |
107 | 107 | logger.info('Making "%s" to friends.', this.description) | |
108 | // We want to group requests by destinations pod and endpoint | 108 | |
109 | const requestsToMakeGrouped = this.buildRequestObjects(requests) | 109 | const goodPods = [] |
110 | 110 | const badPods = [] | |
111 | logger.info('Making "%s" to friends.', this.description) | 111 | |
112 | 112 | return Promise.map(Object.keys(requestsToMake), hashKey => { | |
113 | const goodPods = [] | 113 | const requestToMake = requestsToMake[hashKey] |
114 | const badPods = [] | 114 | const toPod: PodInstance = requestToMake.toPod |
115 | 115 | ||
116 | eachLimit(Object.keys(requestsToMakeGrouped), REQUESTS_IN_PARALLEL, (hashKey, callbackEach) => { | 116 | return this.makeRequest(toPod, requestToMake.endpoint, requestToMake.datas) |
117 | const requestToMake = requestsToMakeGrouped[hashKey] | 117 | .then(() => { |
118 | const toPod = requestToMake.toPod | 118 | logger.debug('Removing requests for pod %s.', requestToMake.toPod.id, { requestsIds: requestToMake.ids }) |
119 | 119 | goodPods.push(requestToMake.toPod.id) | |
120 | this.makeRequest(toPod, requestToMake.endpoint, requestToMake.datas, (err) => { | 120 | |
121 | if (err) { | 121 | this.afterRequestHook() |
122 | badPods.push(requestToMake.toPod.id) | 122 | |
123 | return callbackEach() | 123 | // Remove the pod id of these request ids |
124 | } | 124 | return this.getRequestToPodModel().removeByRequestIdsAndPod(requestToMake.ids, requestToMake.toPod.id) |
125 | 125 | }) | |
126 | logger.debug('Removing requests for pod %s.', requestToMake.toPod.id, { requestsIds: requestToMake.ids }) | 126 | .catch(err => { |
127 | goodPods.push(requestToMake.toPod.id) | 127 | badPods.push(requestToMake.toPod.id) |
128 | 128 | logger.info('Cannot make request to %s.', toPod.host, { error: err }) | |
129 | // Remove the pod id of these request ids | 129 | }) |
130 | this.getRequestToPodModel().removeByRequestIdsAndPod(requestToMake.ids, requestToMake.toPod.id, callbackEach) | 130 | }, { concurrency: REQUESTS_IN_PARALLEL }).then(() => ({ goodPods, badPods })) |
131 | }) | ||
132 | .then(({ goodPods, badPods }) => { | ||
133 | this.afterRequestsHook() | ||
131 | 134 | ||
132 | this.afterRequestHook() | ||
133 | }) | ||
134 | }, () => { | ||
135 | // All the requests were made, we update the pods score | 135 | // All the requests were made, we update the pods score |
136 | db.Pod.updatePodsScore(goodPods, badPods) | 136 | return db.Pod.updatePodsScore(goodPods, badPods) |
137 | |||
138 | this.afterRequestsHook() | ||
139 | }) | 137 | }) |
140 | }) | 138 | .catch(err => logger.error('Cannot get the list of "%s".', this.description, { error: err.stack })) |
141 | } | 139 | } |
142 | 140 | ||
143 | protected afterRequestHook () { | 141 | protected afterRequestHook () { |
diff --git a/server/lib/request/request-scheduler.ts b/server/lib/request/request-scheduler.ts index 575e0227c..0dd796fb0 100644 --- a/server/lib/request/request-scheduler.ts +++ b/server/lib/request/request-scheduler.ts | |||
@@ -3,10 +3,8 @@ import * as Sequelize from 'sequelize' | |||
3 | import { database as db } from '../../initializers/database' | 3 | import { database as db } from '../../initializers/database' |
4 | import { AbstractRequestScheduler } from './abstract-request-scheduler' | 4 | import { AbstractRequestScheduler } from './abstract-request-scheduler' |
5 | import { logger } from '../../helpers' | 5 | import { logger } from '../../helpers' |
6 | import { | 6 | import { REQUESTS_LIMIT_PODS, REQUESTS_LIMIT_PER_POD } from '../../initializers' |
7 | REQUESTS_LIMIT_PODS, | 7 | import { RequestsGrouped } from '../../models' |
8 | REQUESTS_LIMIT_PER_POD | ||
9 | } from '../../initializers' | ||
10 | import { RequestEndpoint } from '../../../shared' | 8 | import { RequestEndpoint } from '../../../shared' |
11 | 9 | ||
12 | export type RequestSchedulerOptions = { | 10 | export type RequestSchedulerOptions = { |
@@ -17,7 +15,7 @@ export type RequestSchedulerOptions = { | |||
17 | transaction: Sequelize.Transaction | 15 | transaction: Sequelize.Transaction |
18 | } | 16 | } |
19 | 17 | ||
20 | class RequestScheduler extends AbstractRequestScheduler { | 18 | class RequestScheduler extends AbstractRequestScheduler<RequestsGrouped> { |
21 | constructor () { | 19 | constructor () { |
22 | super() | 20 | super() |
23 | 21 | ||
@@ -36,11 +34,11 @@ class RequestScheduler extends AbstractRequestScheduler { | |||
36 | return db.RequestToPod | 34 | return db.RequestToPod |
37 | } | 35 | } |
38 | 36 | ||
39 | buildRequestObjects (requests: { [ toPodId: number ]: any }) { | 37 | buildRequestObjects (requestsGrouped: RequestsGrouped) { |
40 | const requestsToMakeGrouped = {} | 38 | const requestsToMakeGrouped = {} |
41 | 39 | ||
42 | Object.keys(requests).forEach(toPodId => { | 40 | Object.keys(requestsGrouped).forEach(toPodId => { |
43 | requests[toPodId].forEach(data => { | 41 | requestsGrouped[toPodId].forEach(data => { |
44 | const request = data.request | 42 | const request = data.request |
45 | const pod = data.pod | 43 | const pod = data.pod |
46 | const hashKey = toPodId + request.endpoint | 44 | const hashKey = toPodId + request.endpoint |
@@ -62,12 +60,12 @@ class RequestScheduler extends AbstractRequestScheduler { | |||
62 | return requestsToMakeGrouped | 60 | return requestsToMakeGrouped |
63 | } | 61 | } |
64 | 62 | ||
65 | createRequest ({ type, endpoint, data, toIds, transaction }: RequestSchedulerOptions, callback: (err: Error) => void) { | 63 | createRequest ({ type, endpoint, data, toIds, transaction }: RequestSchedulerOptions) { |
66 | // TODO: check the setPods works | 64 | // TODO: check the setPods works |
67 | const podIds = [] | 65 | const podIds = [] |
68 | 66 | ||
69 | // If there are no destination pods abort | 67 | // If there are no destination pods abort |
70 | if (toIds.length === 0) return callback(null) | 68 | if (toIds.length === 0) return undefined |
71 | 69 | ||
72 | toIds.forEach(toPod => { | 70 | toIds.forEach(toPod => { |
73 | podIds.push(toPod) | 71 | podIds.push(toPod) |
@@ -85,20 +83,18 @@ class RequestScheduler extends AbstractRequestScheduler { | |||
85 | transaction | 83 | transaction |
86 | } | 84 | } |
87 | 85 | ||
88 | return db.Request.create(createQuery, dbRequestOptions).asCallback((err, request) => { | 86 | return db.Request.create(createQuery, dbRequestOptions) |
89 | if (err) return callback(err) | 87 | .then(request => { |
90 | 88 | return request.setPods(podIds, dbRequestOptions) | |
91 | return request.setPods(podIds, dbRequestOptions).asCallback(callback) | 89 | }) |
92 | }) | ||
93 | } | 90 | } |
94 | 91 | ||
95 | // --------------------------------------------------------------------------- | 92 | // --------------------------------------------------------------------------- |
96 | 93 | ||
97 | afterRequestsHook () { | 94 | afterRequestsHook () { |
98 | // Flush requests with no pod | 95 | // Flush requests with no pod |
99 | this.getRequestModel().removeWithEmptyTo(err => { | 96 | this.getRequestModel().removeWithEmptyTo() |
100 | if (err) logger.error('Error when removing requests with no pods.', { error: err }) | 97 | .catch(err => logger.error('Error when removing requests with no pods.', { error: err })) |
101 | }) | ||
102 | } | 98 | } |
103 | } | 99 | } |
104 | 100 | ||
diff --git a/server/lib/request/request-video-event-scheduler.ts b/server/lib/request/request-video-event-scheduler.ts index 4bb76f4c9..d4d714c02 100644 --- a/server/lib/request/request-video-event-scheduler.ts +++ b/server/lib/request/request-video-event-scheduler.ts | |||
@@ -7,6 +7,7 @@ import { | |||
7 | REQUESTS_VIDEO_EVENT_LIMIT_PER_POD, | 7 | REQUESTS_VIDEO_EVENT_LIMIT_PER_POD, |
8 | REQUEST_VIDEO_EVENT_ENDPOINT | 8 | REQUEST_VIDEO_EVENT_ENDPOINT |
9 | } from '../../initializers' | 9 | } from '../../initializers' |
10 | import { RequestsVideoEventGrouped } from '../../models' | ||
10 | import { RequestVideoEventType } from '../../../shared' | 11 | import { RequestVideoEventType } from '../../../shared' |
11 | 12 | ||
12 | export type RequestVideoEventSchedulerOptions = { | 13 | export type RequestVideoEventSchedulerOptions = { |
@@ -16,7 +17,7 @@ export type RequestVideoEventSchedulerOptions = { | |||
16 | transaction?: Sequelize.Transaction | 17 | transaction?: Sequelize.Transaction |
17 | } | 18 | } |
18 | 19 | ||
19 | class RequestVideoEventScheduler extends AbstractRequestScheduler { | 20 | class RequestVideoEventScheduler extends AbstractRequestScheduler<RequestsVideoEventGrouped> { |
20 | constructor () { | 21 | constructor () { |
21 | super() | 22 | super() |
22 | 23 | ||
@@ -35,7 +36,7 @@ class RequestVideoEventScheduler extends AbstractRequestScheduler { | |||
35 | return db.RequestVideoEvent | 36 | return db.RequestVideoEvent |
36 | } | 37 | } |
37 | 38 | ||
38 | buildRequestObjects (eventsToProcess: { [ toPodId: number ]: any }[]) { | 39 | buildRequestObjects (eventRequests: RequestsVideoEventGrouped) { |
39 | const requestsToMakeGrouped = {} | 40 | const requestsToMakeGrouped = {} |
40 | 41 | ||
41 | /* Example: | 42 | /* Example: |
@@ -50,8 +51,8 @@ class RequestVideoEventScheduler extends AbstractRequestScheduler { | |||
50 | 51 | ||
51 | // We group video events per video and per pod | 52 | // We group video events per video and per pod |
52 | // We add the counts of the same event types | 53 | // We add the counts of the same event types |
53 | Object.keys(eventsToProcess).forEach(toPodId => { | 54 | Object.keys(eventRequests).forEach(toPodId => { |
54 | eventsToProcess[toPodId].forEach(eventToProcess => { | 55 | eventRequests[toPodId].forEach(eventToProcess => { |
55 | if (!eventsPerVideoPerPod[toPodId]) eventsPerVideoPerPod[toPodId] = {} | 56 | if (!eventsPerVideoPerPod[toPodId]) eventsPerVideoPerPod[toPodId] = {} |
56 | 57 | ||
57 | if (!requestsToMakeGrouped[toPodId]) { | 58 | if (!requestsToMakeGrouped[toPodId]) { |
@@ -97,7 +98,7 @@ class RequestVideoEventScheduler extends AbstractRequestScheduler { | |||
97 | return requestsToMakeGrouped | 98 | return requestsToMakeGrouped |
98 | } | 99 | } |
99 | 100 | ||
100 | createRequest ({ type, videoId, count, transaction }: RequestVideoEventSchedulerOptions, callback: (err: Error) => void) { | 101 | createRequest ({ type, videoId, count, transaction }: RequestVideoEventSchedulerOptions) { |
101 | if (count === undefined) count = 1 | 102 | if (count === undefined) count = 1 |
102 | 103 | ||
103 | const dbRequestOptions: Sequelize.CreateOptions = {} | 104 | const dbRequestOptions: Sequelize.CreateOptions = {} |
@@ -109,7 +110,7 @@ class RequestVideoEventScheduler extends AbstractRequestScheduler { | |||
109 | videoId | 110 | videoId |
110 | } | 111 | } |
111 | 112 | ||
112 | return db.RequestVideoEvent.create(createQuery, dbRequestOptions).asCallback(callback) | 113 | return db.RequestVideoEvent.create(createQuery, dbRequestOptions) |
113 | } | 114 | } |
114 | } | 115 | } |
115 | 116 | ||
diff --git a/server/lib/request/request-video-qadu-scheduler.ts b/server/lib/request/request-video-qadu-scheduler.ts index d7169cc81..5ec7de9c2 100644 --- a/server/lib/request/request-video-qadu-scheduler.ts +++ b/server/lib/request/request-video-qadu-scheduler.ts | |||
@@ -9,6 +9,7 @@ import { | |||
9 | REQUEST_VIDEO_QADU_ENDPOINT, | 9 | REQUEST_VIDEO_QADU_ENDPOINT, |
10 | REQUEST_VIDEO_QADU_TYPES | 10 | REQUEST_VIDEO_QADU_TYPES |
11 | } from '../../initializers' | 11 | } from '../../initializers' |
12 | import { RequestsVideoQaduGrouped } from '../../models' | ||
12 | import { RequestVideoQaduType } from '../../../shared' | 13 | import { RequestVideoQaduType } from '../../../shared' |
13 | 14 | ||
14 | export type RequestVideoQaduSchedulerOptions = { | 15 | export type RequestVideoQaduSchedulerOptions = { |
@@ -17,7 +18,7 @@ export type RequestVideoQaduSchedulerOptions = { | |||
17 | transaction?: Sequelize.Transaction | 18 | transaction?: Sequelize.Transaction |
18 | } | 19 | } |
19 | 20 | ||
20 | class RequestVideoQaduScheduler extends AbstractRequestScheduler { | 21 | class RequestVideoQaduScheduler extends AbstractRequestScheduler<RequestsVideoQaduGrouped> { |
21 | constructor () { | 22 | constructor () { |
22 | super() | 23 | super() |
23 | 24 | ||
@@ -36,7 +37,7 @@ class RequestVideoQaduScheduler extends AbstractRequestScheduler { | |||
36 | return db.RequestVideoQadu | 37 | return db.RequestVideoQadu |
37 | } | 38 | } |
38 | 39 | ||
39 | buildRequestObjects (requests: { [ toPodId: number ]: any }[]) { | 40 | buildRequestObjects (requests: RequestsVideoQaduGrouped) { |
40 | const requestsToMakeGrouped = {} | 41 | const requestsToMakeGrouped = {} |
41 | 42 | ||
42 | Object.keys(requests).forEach(toPodId => { | 43 | Object.keys(requests).forEach(toPodId => { |
@@ -105,20 +106,18 @@ class RequestVideoQaduScheduler extends AbstractRequestScheduler { | |||
105 | return requestsToMakeGrouped | 106 | return requestsToMakeGrouped |
106 | } | 107 | } |
107 | 108 | ||
108 | createRequest ({ type, videoId, transaction }: RequestVideoQaduSchedulerOptions, callback: (err: Error) => void) { | 109 | createRequest ({ type, videoId, transaction }: RequestVideoQaduSchedulerOptions) { |
109 | const dbRequestOptions: Sequelize.BulkCreateOptions = {} | 110 | const dbRequestOptions: Sequelize.BulkCreateOptions = {} |
110 | if (transaction) dbRequestOptions.transaction = transaction | 111 | if (transaction) dbRequestOptions.transaction = transaction |
111 | 112 | ||
112 | // Send the update to all our friends | 113 | // Send the update to all our friends |
113 | db.Pod.listAllIds(transaction, function (err, podIds) { | 114 | return db.Pod.listAllIds(transaction).then(podIds => { |
114 | if (err) return callback(err) | ||
115 | |||
116 | const queries = [] | 115 | const queries = [] |
117 | podIds.forEach(podId => { | 116 | podIds.forEach(podId => { |
118 | queries.push({ type, videoId, podId }) | 117 | queries.push({ type, videoId, podId }) |
119 | }) | 118 | }) |
120 | 119 | ||
121 | return db.RequestVideoQadu.bulkCreate(queries, dbRequestOptions).asCallback(callback) | 120 | return db.RequestVideoQadu.bulkCreate(queries, dbRequestOptions) |
122 | }) | 121 | }) |
123 | } | 122 | } |
124 | } | 123 | } |
diff --git a/server/middlewares/secure.ts b/server/middlewares/secure.ts index fbfd08c7b..0fa9ee9d2 100644 --- a/server/middlewares/secure.ts +++ b/server/middlewares/secure.ts | |||
@@ -9,41 +9,41 @@ import { | |||
9 | 9 | ||
10 | function checkSignature (req: express.Request, res: express.Response, next: express.NextFunction) { | 10 | function checkSignature (req: express.Request, res: express.Response, next: express.NextFunction) { |
11 | const host = req.body.signature.host | 11 | const host = req.body.signature.host |
12 | db.Pod.loadByHost(host, function (err, pod) { | 12 | db.Pod.loadByHost(host) |
13 | if (err) { | 13 | .then(pod => { |
14 | logger.error('Cannot get signed host in body.', { error: err }) | 14 | if (pod === null) { |
15 | return res.sendStatus(500) | 15 | logger.error('Unknown pod %s.', host) |
16 | } | 16 | return res.sendStatus(403) |
17 | } | ||
17 | 18 | ||
18 | if (pod === null) { | 19 | logger.debug('Checking signature from %s.', host) |
19 | logger.error('Unknown pod %s.', host) | ||
20 | return res.sendStatus(403) | ||
21 | } | ||
22 | 20 | ||
23 | logger.debug('Checking signature from %s.', host) | 21 | let signatureShouldBe |
22 | // If there is data in the body the sender used it for its signature | ||
23 | // If there is no data we just use its host as signature | ||
24 | if (req.body.data) { | ||
25 | signatureShouldBe = req.body.data | ||
26 | } else { | ||
27 | signatureShouldBe = host | ||
28 | } | ||
24 | 29 | ||
25 | let signatureShouldBe | 30 | const signatureOk = peertubeCryptoCheckSignature(pod.publicKey, signatureShouldBe, req.body.signature.signature) |
26 | // If there is data in the body the sender used it for its signature | ||
27 | // If there is no data we just use its host as signature | ||
28 | if (req.body.data) { | ||
29 | signatureShouldBe = req.body.data | ||
30 | } else { | ||
31 | signatureShouldBe = host | ||
32 | } | ||
33 | 31 | ||
34 | const signatureOk = peertubeCryptoCheckSignature(pod.publicKey, signatureShouldBe, req.body.signature.signature) | 32 | if (signatureOk === true) { |
33 | res.locals.secure = { | ||
34 | pod | ||
35 | } | ||
35 | 36 | ||
36 | if (signatureOk === true) { | 37 | return next() |
37 | res.locals.secure = { | ||
38 | pod | ||
39 | } | 38 | } |
40 | 39 | ||
41 | return next() | 40 | logger.error('Signature is not okay in body for %s.', req.body.signature.host) |
42 | } | 41 | return res.sendStatus(403) |
43 | 42 | }) | |
44 | logger.error('Signature is not okay in body for %s.', req.body.signature.host) | 43 | .catch(err => { |
45 | return res.sendStatus(403) | 44 | logger.error('Cannot get signed host in body.', { error: err }) |
46 | }) | 45 | return res.sendStatus(500) |
46 | }) | ||
47 | } | 47 | } |
48 | 48 | ||
49 | // --------------------------------------------------------------------------- | 49 | // --------------------------------------------------------------------------- |
diff --git a/server/middlewares/validators/pods.ts b/server/middlewares/validators/pods.ts index d8eb90168..da7fc2bd6 100644 --- a/server/middlewares/validators/pods.ts +++ b/server/middlewares/validators/pods.ts | |||
@@ -19,19 +19,19 @@ function makeFriendsValidator (req: express.Request, res: express.Response, next | |||
19 | logger.debug('Checking makeFriends parameters', { parameters: req.body }) | 19 | logger.debug('Checking makeFriends parameters', { parameters: req.body }) |
20 | 20 | ||
21 | checkErrors(req, res, function () { | 21 | checkErrors(req, res, function () { |
22 | hasFriends(function (err, heHasFriends) { | 22 | hasFriends() |
23 | if (err) { | 23 | .then(heHasFriends => { |
24 | if (heHasFriends === true) { | ||
25 | // We need to quit our friends before make new ones | ||
26 | return res.sendStatus(409) | ||
27 | } | ||
28 | |||
29 | return next() | ||
30 | }) | ||
31 | .catch(err => { | ||
24 | logger.error('Cannot know if we have friends.', { error: err }) | 32 | logger.error('Cannot know if we have friends.', { error: err }) |
25 | res.sendStatus(500) | 33 | res.sendStatus(500) |
26 | } | 34 | }) |
27 | |||
28 | if (heHasFriends === true) { | ||
29 | // We need to quit our friends before make new ones | ||
30 | return res.sendStatus(409) | ||
31 | } | ||
32 | |||
33 | return next() | ||
34 | }) | ||
35 | }) | 35 | }) |
36 | } | 36 | } |
37 | 37 | ||
@@ -42,19 +42,19 @@ function podsAddValidator (req: express.Request, res: express.Response, next: ex | |||
42 | logger.debug('Checking podsAdd parameters', { parameters: req.body }) | 42 | logger.debug('Checking podsAdd parameters', { parameters: req.body }) |
43 | 43 | ||
44 | checkErrors(req, res, function () { | 44 | checkErrors(req, res, function () { |
45 | db.Pod.loadByHost(req.body.host, function (err, pod) { | 45 | db.Pod.loadByHost(req.body.host) |
46 | if (err) { | 46 | .then(pod => { |
47 | // Pod with this host already exists | ||
48 | if (pod) { | ||
49 | return res.sendStatus(409) | ||
50 | } | ||
51 | |||
52 | return next() | ||
53 | }) | ||
54 | .catch(err => { | ||
47 | logger.error('Cannot load pod by host.', { error: err }) | 55 | logger.error('Cannot load pod by host.', { error: err }) |
48 | res.sendStatus(500) | 56 | res.sendStatus(500) |
49 | } | 57 | }) |
50 | |||
51 | // Pod with this host already exists | ||
52 | if (pod) { | ||
53 | return res.sendStatus(409) | ||
54 | } | ||
55 | |||
56 | return next() | ||
57 | }) | ||
58 | }) | 58 | }) |
59 | } | 59 | } |
60 | 60 | ||
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts index b7b9ef370..c06735047 100644 --- a/server/middlewares/validators/users.ts +++ b/server/middlewares/validators/users.ts | |||
@@ -13,16 +13,16 @@ function usersAddValidator (req: express.Request, res: express.Response, next: e | |||
13 | logger.debug('Checking usersAdd parameters', { parameters: req.body }) | 13 | logger.debug('Checking usersAdd parameters', { parameters: req.body }) |
14 | 14 | ||
15 | checkErrors(req, res, function () { | 15 | checkErrors(req, res, function () { |
16 | db.User.loadByUsernameOrEmail(req.body.username, req.body.email, function (err, user) { | 16 | db.User.loadByUsernameOrEmail(req.body.username, req.body.email) |
17 | if (err) { | 17 | .then(user => { |
18 | if (user) return res.status(409).send('User already exists.') | ||
19 | |||
20 | next() | ||
21 | }) | ||
22 | .catch(err => { | ||
18 | logger.error('Error in usersAdd request validator.', { error: err }) | 23 | logger.error('Error in usersAdd request validator.', { error: err }) |
19 | return res.sendStatus(500) | 24 | return res.sendStatus(500) |
20 | } | 25 | }) |
21 | |||
22 | if (user) return res.status(409).send('User already exists.') | ||
23 | |||
24 | next() | ||
25 | }) | ||
26 | }) | 26 | }) |
27 | } | 27 | } |
28 | 28 | ||
@@ -32,18 +32,18 @@ function usersRemoveValidator (req: express.Request, res: express.Response, next | |||
32 | logger.debug('Checking usersRemove parameters', { parameters: req.params }) | 32 | logger.debug('Checking usersRemove parameters', { parameters: req.params }) |
33 | 33 | ||
34 | checkErrors(req, res, function () { | 34 | checkErrors(req, res, function () { |
35 | db.User.loadById(req.params.id, function (err, user) { | 35 | db.User.loadById(req.params.id) |
36 | if (err) { | 36 | .then(user => { |
37 | logger.error('Error in usersRemove request validator.', { error: err }) | 37 | if (!user) return res.status(404).send('User not found') |
38 | return res.sendStatus(500) | ||
39 | } | ||
40 | |||
41 | if (!user) return res.status(404).send('User not found') | ||
42 | 38 | ||
43 | if (user.username === 'root') return res.status(400).send('Cannot remove the root user') | 39 | if (user.username === 'root') return res.status(400).send('Cannot remove the root user') |
44 | 40 | ||
45 | next() | 41 | next() |
46 | }) | 42 | }) |
43 | .catch(err => { | ||
44 | logger.error('Error in usersRemove request validator.', { error: err }) | ||
45 | return res.sendStatus(500) | ||
46 | }) | ||
47 | }) | 47 | }) |
48 | } | 48 | } |
49 | 49 | ||
@@ -64,16 +64,16 @@ function usersVideoRatingValidator (req: express.Request, res: express.Response, | |||
64 | logger.debug('Checking usersVideoRating parameters', { parameters: req.params }) | 64 | logger.debug('Checking usersVideoRating parameters', { parameters: req.params }) |
65 | 65 | ||
66 | checkErrors(req, res, function () { | 66 | checkErrors(req, res, function () { |
67 | db.Video.load(req.params.videoId, function (err, video) { | 67 | db.Video.load(req.params.videoId) |
68 | if (err) { | 68 | .then(video => { |
69 | if (!video) return res.status(404).send('Video not found') | ||
70 | |||
71 | next() | ||
72 | }) | ||
73 | .catch(err => { | ||
69 | logger.error('Error in user request validator.', { error: err }) | 74 | logger.error('Error in user request validator.', { error: err }) |
70 | return res.sendStatus(500) | 75 | return res.sendStatus(500) |
71 | } | 76 | }) |
72 | |||
73 | if (!video) return res.status(404).send('Video not found') | ||
74 | |||
75 | next() | ||
76 | }) | ||
77 | }) | 77 | }) |
78 | } | 78 | } |
79 | 79 | ||
diff --git a/server/middlewares/validators/videos.ts b/server/middlewares/validators/videos.ts index 03742a522..ec452cade 100644 --- a/server/middlewares/validators/videos.ts +++ b/server/middlewares/validators/videos.ts | |||
@@ -1,5 +1,4 @@ | |||
1 | import 'express-validator' | 1 | import 'express-validator' |
2 | import * as multer from 'multer' | ||
3 | import * as express from 'express' | 2 | import * as express from 'express' |
4 | 3 | ||
5 | import { database as db } from '../../initializers/database' | 4 | import { database as db } from '../../initializers/database' |
@@ -24,18 +23,19 @@ function videosAddValidator (req: express.Request, res: express.Response, next: | |||
24 | checkErrors(req, res, function () { | 23 | checkErrors(req, res, function () { |
25 | const videoFile = req.files.videofile[0] | 24 | const videoFile = req.files.videofile[0] |
26 | 25 | ||
27 | db.Video.getDurationFromFile(videoFile.path, function (err, duration) { | 26 | db.Video.getDurationFromFile(videoFile.path) |
28 | if (err) { | 27 | .then(duration => { |
29 | return res.status(400).send('Cannot retrieve metadata of the file.') | 28 | if (!isVideoDurationValid('' + duration)) { |
30 | } | 29 | return res.status(400).send('Duration of the video file is too big (max: ' + CONSTRAINTS_FIELDS.VIDEOS.DURATION.max + 's).') |
31 | 30 | } | |
32 | if (!isVideoDurationValid(duration)) { | ||
33 | return res.status(400).send('Duration of the video file is too big (max: ' + CONSTRAINTS_FIELDS.VIDEOS.DURATION.max + 's).') | ||
34 | } | ||
35 | 31 | ||
36 | videoFile['duration'] = duration | 32 | videoFile['duration'] = duration |
37 | next() | 33 | next() |
38 | }) | 34 | }) |
35 | .catch(err => { | ||
36 | logger.error('Error in getting duration from file.', { error: err }) | ||
37 | res.status(400).send('Cannot retrieve metadata of the file.') | ||
38 | }) | ||
39 | }) | 39 | }) |
40 | } | 40 | } |
41 | 41 | ||
@@ -157,43 +157,42 @@ export { | |||
157 | // --------------------------------------------------------------------------- | 157 | // --------------------------------------------------------------------------- |
158 | 158 | ||
159 | function checkVideoExists (id: string, res: express.Response, callback: () => void) { | 159 | function checkVideoExists (id: string, res: express.Response, callback: () => void) { |
160 | db.Video.loadAndPopulateAuthorAndPodAndTags(id, function (err, video) { | 160 | db.Video.loadAndPopulateAuthorAndPodAndTags(id).then(video => { |
161 | if (err) { | ||
162 | logger.error('Error in video request validator.', { error: err }) | ||
163 | return res.sendStatus(500) | ||
164 | } | ||
165 | |||
166 | if (!video) return res.status(404).send('Video not found') | 161 | if (!video) return res.status(404).send('Video not found') |
167 | 162 | ||
168 | res.locals.video = video | 163 | res.locals.video = video |
169 | callback() | 164 | callback() |
170 | }) | 165 | }) |
166 | .catch(err => { | ||
167 | logger.error('Error in video request validator.', { error: err }) | ||
168 | return res.sendStatus(500) | ||
169 | }) | ||
171 | } | 170 | } |
172 | 171 | ||
173 | function checkUserCanDeleteVideo (userId: number, res: express.Response, callback: () => void) { | 172 | function checkUserCanDeleteVideo (userId: number, res: express.Response, callback: () => void) { |
174 | // Retrieve the user who did the request | 173 | // Retrieve the user who did the request |
175 | db.User.loadById(userId, function (err, user) { | 174 | db.User.loadById(userId) |
176 | if (err) { | 175 | .then(user => { |
177 | logger.error('Error in video request validator.', { error: err }) | 176 | // Check if the user can delete the video |
178 | return res.sendStatus(500) | 177 | // The user can delete it if s/he is an admin |
179 | } | 178 | // Or if s/he is the video's author |
180 | 179 | if (user.isAdmin() === false) { | |
181 | // Check if the user can delete the video | 180 | if (res.locals.video.isOwned() === false) { |
182 | // The user can delete it if s/he is an admin | 181 | return res.status(403).send('Cannot remove video of another pod') |
183 | // Or if s/he is the video's author | 182 | } |
184 | if (user.isAdmin() === false) { | 183 | |
185 | if (res.locals.video.isOwned() === false) { | 184 | if (res.locals.video.Author.userId !== res.locals.oauth.token.User.id) { |
186 | return res.status(403).send('Cannot remove video of another pod') | 185 | return res.status(403).send('Cannot remove video of another user') |
187 | } | 186 | } |
188 | |||
189 | if (res.locals.video.Author.userId !== res.locals.oauth.token.User.id) { | ||
190 | return res.status(403).send('Cannot remove video of another user') | ||
191 | } | 187 | } |
192 | } | ||
193 | 188 | ||
194 | // If we reach this comment, we can delete the video | 189 | // If we reach this comment, we can delete the video |
195 | callback() | 190 | callback() |
196 | }) | 191 | }) |
192 | .catch(err => { | ||
193 | logger.error('Error in video request validator.', { error: err }) | ||
194 | return res.sendStatus(500) | ||
195 | }) | ||
197 | } | 196 | } |
198 | 197 | ||
199 | function checkVideoIsBlacklistable (req: express.Request, res: express.Response, callback: () => void) { | 198 | function checkVideoIsBlacklistable (req: express.Request, res: express.Response, callback: () => void) { |
diff --git a/server/models/application/application-interface.ts b/server/models/application/application-interface.ts index c03513db1..33254ba2d 100644 --- a/server/models/application/application-interface.ts +++ b/server/models/application/application-interface.ts | |||
@@ -1,11 +1,13 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import * as Promise from 'bluebird' | ||
2 | 3 | ||
3 | export namespace ApplicationMethods { | 4 | export namespace ApplicationMethods { |
4 | export type LoadMigrationVersionCallback = (err: Error, version: number) => void | 5 | export type LoadMigrationVersion = () => Promise<number> |
5 | export type LoadMigrationVersion = (callback: LoadMigrationVersionCallback) => void | ||
6 | 6 | ||
7 | export type UpdateMigrationVersionCallback = (err: Error, applicationInstance: ApplicationAttributes) => void | 7 | export type UpdateMigrationVersion = ( |
8 | export type UpdateMigrationVersion = (newVersion: number, transaction: Sequelize.Transaction, callback: UpdateMigrationVersionCallback) => void | 8 | newVersion: number, |
9 | transaction: Sequelize.Transaction | ||
10 | ) => Promise<[ number, ApplicationInstance[] ]> | ||
9 | } | 11 | } |
10 | 12 | ||
11 | export interface ApplicationClass { | 13 | export interface ApplicationClass { |
diff --git a/server/models/application/application.ts b/server/models/application/application.ts index 0e9a1ebb3..507b7a843 100644 --- a/server/models/application/application.ts +++ b/server/models/application/application.ts | |||
@@ -2,7 +2,6 @@ import * as Sequelize from 'sequelize' | |||
2 | 2 | ||
3 | import { addMethodsToModel } from '../utils' | 3 | import { addMethodsToModel } from '../utils' |
4 | import { | 4 | import { |
5 | ApplicationClass, | ||
6 | ApplicationAttributes, | 5 | ApplicationAttributes, |
7 | ApplicationInstance, | 6 | ApplicationInstance, |
8 | 7 | ||
@@ -35,23 +34,19 @@ export default function defineApplication (sequelize: Sequelize.Sequelize, DataT | |||
35 | 34 | ||
36 | // --------------------------------------------------------------------------- | 35 | // --------------------------------------------------------------------------- |
37 | 36 | ||
38 | loadMigrationVersion = function (callback: ApplicationMethods.LoadMigrationVersionCallback) { | 37 | loadMigrationVersion = function () { |
39 | const query = { | 38 | const query = { |
40 | attributes: [ 'migrationVersion' ] | 39 | attributes: [ 'migrationVersion' ] |
41 | } | 40 | } |
42 | 41 | ||
43 | return Application.findOne(query).asCallback(function (err, data) { | 42 | return Application.findOne(query).then(data => data ? data.migrationVersion : null) |
44 | const version = data ? data.migrationVersion : null | ||
45 | |||
46 | return callback(err, version) | ||
47 | }) | ||
48 | } | 43 | } |
49 | 44 | ||
50 | updateMigrationVersion = function (newVersion: number, transaction: Sequelize.Transaction, callback: ApplicationMethods.UpdateMigrationVersionCallback) { | 45 | updateMigrationVersion = function (newVersion: number, transaction: Sequelize.Transaction) { |
51 | const options: Sequelize.UpdateOptions = { | 46 | const options: Sequelize.UpdateOptions = { |
52 | where: {}, | 47 | where: {}, |
53 | transaction: transaction | 48 | transaction: transaction |
54 | } | 49 | } |
55 | 50 | ||
56 | return Application.update({ migrationVersion: newVersion }, options).asCallback(callback) | 51 | return Application.update({ migrationVersion: newVersion }, options) |
57 | } | 52 | } |
diff --git a/server/models/job/job-interface.ts b/server/models/job/job-interface.ts index 31b377367..ba5622977 100644 --- a/server/models/job/job-interface.ts +++ b/server/models/job/job-interface.ts | |||
@@ -1,10 +1,10 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import * as Promise from 'bluebird' | ||
2 | 3 | ||
3 | import { JobState } from '../../../shared/models/job.model' | 4 | import { JobState } from '../../../shared/models/job.model' |
4 | 5 | ||
5 | export namespace JobMethods { | 6 | export namespace JobMethods { |
6 | export type ListWithLimitCallback = (err: Error, jobInstances: JobInstance[]) => void | 7 | export type ListWithLimit = (limit: number, state: JobState) => Promise<JobInstance[]> |
7 | export type ListWithLimit = (limit: number, state: JobState, callback: ListWithLimitCallback) => void | ||
8 | } | 8 | } |
9 | 9 | ||
10 | export interface JobClass { | 10 | export interface JobClass { |
diff --git a/server/models/job/job.ts b/server/models/job/job.ts index 38e4e8f30..968f9d71d 100644 --- a/server/models/job/job.ts +++ b/server/models/job/job.ts | |||
@@ -5,7 +5,6 @@ import { JOB_STATES } from '../../initializers' | |||
5 | 5 | ||
6 | import { addMethodsToModel } from '../utils' | 6 | import { addMethodsToModel } from '../utils' |
7 | import { | 7 | import { |
8 | JobClass, | ||
9 | JobInstance, | 8 | JobInstance, |
10 | JobAttributes, | 9 | JobAttributes, |
11 | 10 | ||
@@ -49,7 +48,7 @@ export default function defineJob (sequelize: Sequelize.Sequelize, DataTypes: Se | |||
49 | 48 | ||
50 | // --------------------------------------------------------------------------- | 49 | // --------------------------------------------------------------------------- |
51 | 50 | ||
52 | listWithLimit = function (limit: number, state: JobState, callback: JobMethods.ListWithLimitCallback) { | 51 | listWithLimit = function (limit: number, state: JobState) { |
53 | const query = { | 52 | const query = { |
54 | order: [ | 53 | order: [ |
55 | [ 'id', 'ASC' ] | 54 | [ 'id', 'ASC' ] |
@@ -60,5 +59,5 @@ listWithLimit = function (limit: number, state: JobState, callback: JobMethods.L | |||
60 | } | 59 | } |
61 | } | 60 | } |
62 | 61 | ||
63 | return Job.findAll(query).asCallback(callback) | 62 | return Job.findAll(query) |
64 | } | 63 | } |
diff --git a/server/models/oauth/oauth-client-interface.ts b/server/models/oauth/oauth-client-interface.ts index 3b4325740..3526e4159 100644 --- a/server/models/oauth/oauth-client-interface.ts +++ b/server/models/oauth/oauth-client-interface.ts | |||
@@ -1,13 +1,12 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import * as Promise from 'bluebird' | ||
2 | 3 | ||
3 | export namespace OAuthClientMethods { | 4 | export namespace OAuthClientMethods { |
4 | export type CountTotalCallback = (err: Error, total: number) => void | 5 | export type CountTotal = () => Promise<number> |
5 | export type CountTotal = (callback: CountTotalCallback) => void | ||
6 | 6 | ||
7 | export type LoadFirstClientCallback = (err: Error, client: OAuthClientInstance) => void | 7 | export type LoadFirstClient = () => Promise<OAuthClientInstance> |
8 | export type LoadFirstClient = (callback: LoadFirstClientCallback) => void | ||
9 | 8 | ||
10 | export type GetByIdAndSecret = (clientId, clientSecret) => void | 9 | export type GetByIdAndSecret = (clientId: string, clientSecret: string) => Promise<OAuthClientInstance> |
11 | } | 10 | } |
12 | 11 | ||
13 | export interface OAuthClientClass { | 12 | export interface OAuthClientClass { |
diff --git a/server/models/oauth/oauth-client.ts b/server/models/oauth/oauth-client.ts index fbc2a3393..9cc68771d 100644 --- a/server/models/oauth/oauth-client.ts +++ b/server/models/oauth/oauth-client.ts | |||
@@ -2,7 +2,6 @@ import * as Sequelize from 'sequelize' | |||
2 | 2 | ||
3 | import { addMethodsToModel } from '../utils' | 3 | import { addMethodsToModel } from '../utils' |
4 | import { | 4 | import { |
5 | OAuthClientClass, | ||
6 | OAuthClientInstance, | 5 | OAuthClientInstance, |
7 | OAuthClientAttributes, | 6 | OAuthClientAttributes, |
8 | 7 | ||
@@ -67,12 +66,12 @@ function associate (models) { | |||
67 | }) | 66 | }) |
68 | } | 67 | } |
69 | 68 | ||
70 | countTotal = function (callback: OAuthClientMethods.CountTotalCallback) { | 69 | countTotal = function () { |
71 | return OAuthClient.count().asCallback(callback) | 70 | return OAuthClient.count() |
72 | } | 71 | } |
73 | 72 | ||
74 | loadFirstClient = function (callback: OAuthClientMethods.LoadFirstClientCallback) { | 73 | loadFirstClient = function () { |
75 | return OAuthClient.findOne().asCallback(callback) | 74 | return OAuthClient.findOne() |
76 | } | 75 | } |
77 | 76 | ||
78 | getByIdAndSecret = function (clientId: string, clientSecret: string) { | 77 | getByIdAndSecret = function (clientId: string, clientSecret: string) { |
diff --git a/server/models/oauth/oauth-token-interface.ts b/server/models/oauth/oauth-token-interface.ts index 815ad5eef..f2ddafa54 100644 --- a/server/models/oauth/oauth-token-interface.ts +++ b/server/models/oauth/oauth-token-interface.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import * as Bluebird from 'bluebird' | 2 | import * as Promise from 'bluebird' |
3 | 3 | ||
4 | import { UserModel } from '../user' | 4 | import { UserModel } from '../user' |
5 | 5 | ||
@@ -15,12 +15,11 @@ export type OAuthTokenInfo = { | |||
15 | } | 15 | } |
16 | 16 | ||
17 | export namespace OAuthTokenMethods { | 17 | export namespace OAuthTokenMethods { |
18 | export type GetByRefreshTokenAndPopulateClient = (refreshToken: string) => Bluebird<OAuthTokenInfo> | 18 | export type GetByRefreshTokenAndPopulateClient = (refreshToken: string) => Promise<OAuthTokenInfo> |
19 | export type GetByTokenAndPopulateUser = (bearerToken: string) => Bluebird<OAuthTokenInstance> | 19 | export type GetByTokenAndPopulateUser = (bearerToken: string) => Promise<OAuthTokenInstance> |
20 | export type GetByRefreshTokenAndPopulateUser = (refreshToken: string) => Bluebird<OAuthTokenInstance> | 20 | export type GetByRefreshTokenAndPopulateUser = (refreshToken: string) => Promise<OAuthTokenInstance> |
21 | 21 | ||
22 | export type RemoveByUserIdCallback = (err: Error) => void | 22 | export type RemoveByUserId = (userId) => Promise<number> |
23 | export type RemoveByUserId = (userId, callback) => void | ||
24 | } | 23 | } |
25 | 24 | ||
26 | export interface OAuthTokenClass { | 25 | export interface OAuthTokenClass { |
diff --git a/server/models/oauth/oauth-token.ts b/server/models/oauth/oauth-token.ts index eab9cf858..8c6883465 100644 --- a/server/models/oauth/oauth-token.ts +++ b/server/models/oauth/oauth-token.ts | |||
@@ -4,7 +4,6 @@ import { logger } from '../../helpers' | |||
4 | 4 | ||
5 | import { addMethodsToModel } from '../utils' | 5 | import { addMethodsToModel } from '../utils' |
6 | import { | 6 | import { |
7 | OAuthTokenClass, | ||
8 | OAuthTokenInstance, | 7 | OAuthTokenInstance, |
9 | OAuthTokenAttributes, | 8 | OAuthTokenAttributes, |
10 | 9 | ||
@@ -149,12 +148,12 @@ getByRefreshTokenAndPopulateUser = function (refreshToken: string) { | |||
149 | }) | 148 | }) |
150 | } | 149 | } |
151 | 150 | ||
152 | removeByUserId = function (userId, callback) { | 151 | removeByUserId = function (userId: number) { |
153 | const query = { | 152 | const query = { |
154 | where: { | 153 | where: { |
155 | userId: userId | 154 | userId: userId |
156 | } | 155 | } |
157 | } | 156 | } |
158 | 157 | ||
159 | return OAuthToken.destroy(query).asCallback(callback) | 158 | return OAuthToken.destroy(query) |
160 | } | 159 | } |
diff --git a/server/models/pod/pod-interface.ts b/server/models/pod/pod-interface.ts index d88847c45..f6963d47e 100644 --- a/server/models/pod/pod-interface.ts +++ b/server/models/pod/pod-interface.ts | |||
@@ -1,4 +1,5 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import * as Promise from 'bluebird' | ||
2 | 3 | ||
3 | // Don't use barrel, import just what we need | 4 | // Don't use barrel, import just what we need |
4 | import { Pod as FormatedPod } from '../../../shared/models/pod.model' | 5 | import { Pod as FormatedPod } from '../../../shared/models/pod.model' |
@@ -6,32 +7,23 @@ import { Pod as FormatedPod } from '../../../shared/models/pod.model' | |||
6 | export namespace PodMethods { | 7 | export namespace PodMethods { |
7 | export type ToFormatedJSON = (this: PodInstance) => FormatedPod | 8 | export type ToFormatedJSON = (this: PodInstance) => FormatedPod |
8 | 9 | ||
9 | export type CountAllCallback = (err: Error, total: number) => void | 10 | export type CountAll = () => Promise<number> |
10 | export type CountAll = (callback) => void | ||
11 | 11 | ||
12 | export type IncrementScoresCallback = (err: Error) => void | 12 | export type IncrementScores = (ids: number[], value: number) => Promise<[ number, PodInstance[] ]> |
13 | export type IncrementScores = (ids: number[], value: number, callback?: IncrementScoresCallback) => void | ||
14 | 13 | ||
15 | export type ListCallback = (err: Error, podInstances?: PodInstance[]) => void | 14 | export type List = () => Promise<PodInstance[]> |
16 | export type List = (callback: ListCallback) => void | ||
17 | 15 | ||
18 | export type ListAllIdsCallback = (err: Error, ids?: number[]) => void | 16 | export type ListAllIds = (transaction: Sequelize.Transaction) => Promise<number[]> |
19 | export type ListAllIds = (transaction: Sequelize.Transaction, callback: ListAllIdsCallback) => void | ||
20 | 17 | ||
21 | export type ListRandomPodIdsWithRequestCallback = (err: Error, podInstanceIds?: number[]) => void | 18 | export type ListRandomPodIdsWithRequest = (limit: number, tableWithPods: string, tableWithPodsJoins: string) => Promise<number[]> |
22 | export type ListRandomPodIdsWithRequest = (limit: number, tableWithPods: string, tableWithPodsJoins: string, callback: ListRandomPodIdsWithRequestCallback) => void | ||
23 | 19 | ||
24 | export type ListBadPodsCallback = (err: Error, podInstances?: PodInstance[]) => void | 20 | export type ListBadPods = () => Promise<PodInstance[]> |
25 | export type ListBadPods = (callback: ListBadPodsCallback) => void | ||
26 | 21 | ||
27 | export type LoadCallback = (err: Error, podInstance: PodInstance) => void | 22 | export type Load = (id: number) => Promise<PodInstance> |
28 | export type Load = (id: number, callback: LoadCallback) => void | ||
29 | 23 | ||
30 | export type LoadByHostCallback = (err: Error, podInstance: PodInstance) => void | 24 | export type LoadByHost = (host: string) => Promise<PodInstance> |
31 | export type LoadByHost = (host: string, callback: LoadByHostCallback) => void | ||
32 | 25 | ||
33 | export type RemoveAllCallback = (err: Error) => void | 26 | export type RemoveAll = () => Promise<number> |
34 | export type RemoveAll = (callback: RemoveAllCallback) => void | ||
35 | 27 | ||
36 | export type UpdatePodsScore = (goodPods: number[], badPods: number[]) => void | 28 | export type UpdatePodsScore = (goodPods: number[], badPods: number[]) => void |
37 | } | 29 | } |
diff --git a/server/models/pod/pod.ts b/server/models/pod/pod.ts index 4fe7fda1c..9209380fc 100644 --- a/server/models/pod/pod.ts +++ b/server/models/pod/pod.ts | |||
@@ -1,4 +1,3 @@ | |||
1 | import { each, waterfall } from 'async' | ||
2 | import { map } from 'lodash' | 1 | import { map } from 'lodash' |
3 | import * as Sequelize from 'sequelize' | 2 | import * as Sequelize from 'sequelize' |
4 | 3 | ||
@@ -7,7 +6,6 @@ import { logger, isHostValid } from '../../helpers' | |||
7 | 6 | ||
8 | import { addMethodsToModel } from '../utils' | 7 | import { addMethodsToModel } from '../utils' |
9 | import { | 8 | import { |
10 | PodClass, | ||
11 | PodInstance, | 9 | PodInstance, |
12 | PodAttributes, | 10 | PodAttributes, |
13 | 11 | ||
@@ -118,13 +116,11 @@ function associate (models) { | |||
118 | }) | 116 | }) |
119 | } | 117 | } |
120 | 118 | ||
121 | countAll = function (callback: PodMethods.CountAllCallback) { | 119 | countAll = function () { |
122 | return Pod.count().asCallback(callback) | 120 | return Pod.count() |
123 | } | 121 | } |
124 | 122 | ||
125 | incrementScores = function (ids: number[], value: number, callback?: PodMethods.IncrementScoresCallback) { | 123 | incrementScores = function (ids: number[], value: number) { |
126 | if (!callback) callback = function () { /* empty */ } | ||
127 | |||
128 | const update = { | 124 | const update = { |
129 | score: Sequelize.literal('score +' + value) | 125 | score: Sequelize.literal('score +' + value) |
130 | } | 126 | } |
@@ -139,33 +135,28 @@ incrementScores = function (ids: number[], value: number, callback?: PodMethods. | |||
139 | validate: false | 135 | validate: false |
140 | } | 136 | } |
141 | 137 | ||
142 | return Pod.update(update, options).asCallback(callback) | 138 | return Pod.update(update, options) |
143 | } | 139 | } |
144 | 140 | ||
145 | list = function (callback: PodMethods.ListCallback) { | 141 | list = function () { |
146 | return Pod.findAll().asCallback(callback) | 142 | return Pod.findAll() |
147 | } | 143 | } |
148 | 144 | ||
149 | listAllIds = function (transaction: Sequelize.Transaction, callback: PodMethods.ListAllIdsCallback) { | 145 | listAllIds = function (transaction: Sequelize.Transaction) { |
150 | const query: any = { | 146 | const query: Sequelize.FindOptions = { |
151 | attributes: [ 'id' ] | 147 | attributes: [ 'id' ], |
148 | transaction | ||
152 | } | 149 | } |
153 | 150 | ||
154 | if (transaction !== null) query.transaction = transaction | 151 | return Pod.findAll(query).then(pods => { |
155 | 152 | return map(pods, 'id') | |
156 | return Pod.findAll(query).asCallback(function (err: Error, pods) { | ||
157 | if (err) return callback(err) | ||
158 | |||
159 | return callback(null, map(pods, 'id')) | ||
160 | }) | 153 | }) |
161 | } | 154 | } |
162 | 155 | ||
163 | listRandomPodIdsWithRequest = function (limit: number, tableWithPods: string, tableWithPodsJoins: string, callback: PodMethods.ListRandomPodIdsWithRequestCallback) { | 156 | listRandomPodIdsWithRequest = function (limit: number, tableWithPods: string, tableWithPodsJoins: string) { |
164 | Pod.count().asCallback(function (err, count) { | 157 | return Pod.count().then(count => { |
165 | if (err) return callback(err) | ||
166 | |||
167 | // Optimization... | 158 | // Optimization... |
168 | if (count === 0) return callback(null, []) | 159 | if (count === 0) return [] |
169 | 160 | ||
170 | let start = Math.floor(Math.random() * count) - limit | 161 | let start = Math.floor(Math.random() * count) - limit |
171 | if (start < 0) start = 0 | 162 | if (start < 0) start = 0 |
@@ -186,56 +177,55 @@ listRandomPodIdsWithRequest = function (limit: number, tableWithPods: string, ta | |||
186 | } | 177 | } |
187 | } | 178 | } |
188 | 179 | ||
189 | return Pod.findAll(query).asCallback(function (err, pods) { | 180 | return Pod.findAll(query).then(pods => { |
190 | if (err) return callback(err) | 181 | return map(pods, 'id') |
191 | |||
192 | return callback(null, map(pods, 'id')) | ||
193 | }) | 182 | }) |
194 | }) | 183 | }) |
195 | } | 184 | } |
196 | 185 | ||
197 | listBadPods = function (callback: PodMethods.ListBadPodsCallback) { | 186 | listBadPods = function () { |
198 | const query = { | 187 | const query = { |
199 | where: { | 188 | where: { |
200 | score: { $lte: 0 } | 189 | score: { $lte: 0 } |
201 | } | 190 | } |
202 | } | 191 | } |
203 | 192 | ||
204 | return Pod.findAll(query).asCallback(callback) | 193 | return Pod.findAll(query) |
205 | } | 194 | } |
206 | 195 | ||
207 | load = function (id: number, callback: PodMethods.LoadCallback) { | 196 | load = function (id: number) { |
208 | return Pod.findById(id).asCallback(callback) | 197 | return Pod.findById(id) |
209 | } | 198 | } |
210 | 199 | ||
211 | loadByHost = function (host: string, callback: PodMethods.LoadByHostCallback) { | 200 | loadByHost = function (host: string) { |
212 | const query = { | 201 | const query = { |
213 | where: { | 202 | where: { |
214 | host: host | 203 | host: host |
215 | } | 204 | } |
216 | } | 205 | } |
217 | 206 | ||
218 | return Pod.findOne(query).asCallback(callback) | 207 | return Pod.findOne(query) |
219 | } | 208 | } |
220 | 209 | ||
221 | removeAll = function (callback: PodMethods.RemoveAllCallback) { | 210 | removeAll = function () { |
222 | return Pod.destroy().asCallback(callback) | 211 | return Pod.destroy() |
223 | } | 212 | } |
224 | 213 | ||
225 | updatePodsScore = function (goodPods: number[], badPods: number[]) { | 214 | updatePodsScore = function (goodPods: number[], badPods: number[]) { |
226 | logger.info('Updating %d good pods and %d bad pods scores.', goodPods.length, badPods.length) | 215 | logger.info('Updating %d good pods and %d bad pods scores.', goodPods.length, badPods.length) |
227 | 216 | ||
228 | if (goodPods.length !== 0) { | 217 | if (goodPods.length !== 0) { |
229 | incrementScores(goodPods, PODS_SCORE.BONUS, function (err) { | 218 | incrementScores(goodPods, PODS_SCORE.BONUS).catch(err => { |
230 | if (err) logger.error('Cannot increment scores of good pods.', { error: err }) | 219 | logger.error('Cannot increment scores of good pods.', { error: err }) |
231 | }) | 220 | }) |
232 | } | 221 | } |
233 | 222 | ||
234 | if (badPods.length !== 0) { | 223 | if (badPods.length !== 0) { |
235 | incrementScores(badPods, PODS_SCORE.MALUS, function (err) { | 224 | incrementScores(badPods, PODS_SCORE.MALUS) |
236 | if (err) logger.error('Cannot decrement scores of bad pods.', { error: err }) | 225 | .then(() => removeBadPods()) |
237 | removeBadPods() | 226 | .catch(err => { |
238 | }) | 227 | if (err) logger.error('Cannot decrement scores of bad pods.', { error: err }) |
228 | }) | ||
239 | } | 229 | } |
240 | } | 230 | } |
241 | 231 | ||
@@ -243,32 +233,19 @@ updatePodsScore = function (goodPods: number[], badPods: number[]) { | |||
243 | 233 | ||
244 | // Remove pods with a score of 0 (too many requests where they were unreachable) | 234 | // Remove pods with a score of 0 (too many requests where they were unreachable) |
245 | function removeBadPods () { | 235 | function removeBadPods () { |
246 | waterfall([ | 236 | return listBadPods() |
247 | function findBadPods (callback) { | 237 | .then(pods => { |
248 | listBadPods(function (err, pods) { | 238 | const podsRemovePromises = pods.map(pod => pod.destroy()) |
249 | if (err) { | 239 | return Promise.all(podsRemovePromises).then(() => pods.length) |
250 | logger.error('Cannot find bad pods.', { error: err }) | 240 | }) |
251 | return callback(err) | 241 | .then(numberOfPodsRemoved => { |
252 | } | 242 | if (numberOfPodsRemoved) { |
253 | 243 | logger.info('Removed %d pods.', numberOfPodsRemoved) | |
254 | return callback(null, pods) | 244 | } else { |
255 | }) | 245 | logger.info('No need to remove bad pods.') |
256 | }, | 246 | } |
257 | 247 | }) | |
258 | function removeTheseBadPods (pods, callback) { | 248 | .catch(err => { |
259 | each(pods, function (pod: any, callbackEach) { | ||
260 | pod.destroy().asCallback(callbackEach) | ||
261 | }, function (err) { | ||
262 | return callback(err, pods.length) | ||
263 | }) | ||
264 | } | ||
265 | ], function (err, numberOfPodsRemoved) { | ||
266 | if (err) { | ||
267 | logger.error('Cannot remove bad pods.', { error: err }) | 249 | logger.error('Cannot remove bad pods.', { error: err }) |
268 | } else if (numberOfPodsRemoved) { | 250 | }) |
269 | logger.info('Removed %d pods.', numberOfPodsRemoved) | ||
270 | } else { | ||
271 | logger.info('No need to remove bad pods.') | ||
272 | } | ||
273 | }) | ||
274 | } | 251 | } |
diff --git a/server/models/request/abstract-request-interface.ts b/server/models/request/abstract-request-interface.ts new file mode 100644 index 000000000..a384f4d27 --- /dev/null +++ b/server/models/request/abstract-request-interface.ts | |||
@@ -0,0 +1,12 @@ | |||
1 | import * as Promise from 'bluebird' | ||
2 | |||
3 | export interface AbstractRequestClass <T> { | ||
4 | countTotalRequests: () => Promise<number> | ||
5 | listWithLimitAndRandom: (limitPods: number, limitRequestsPerPod: number) => Promise<T> | ||
6 | removeWithEmptyTo: () => Promise<number> | ||
7 | removeAll: () => Promise<void> | ||
8 | } | ||
9 | |||
10 | export interface AbstractRequestToPodClass { | ||
11 | removeByRequestIdsAndPod: (ids: number[], podId: number) => Promise<number> | ||
12 | } | ||
diff --git a/server/models/request/index.ts b/server/models/request/index.ts index 824c140f5..3dd6aedc2 100644 --- a/server/models/request/index.ts +++ b/server/models/request/index.ts | |||
@@ -1,3 +1,4 @@ | |||
1 | export * from './abstract-request-interface' | ||
1 | export * from './request-interface' | 2 | export * from './request-interface' |
2 | export * from './request-to-pod-interface' | 3 | export * from './request-to-pod-interface' |
3 | export * from './request-video-event-interface' | 4 | export * from './request-video-event-interface' |
diff --git a/server/models/request/request-interface.ts b/server/models/request/request-interface.ts index 483850633..7b0ee4df9 100644 --- a/server/models/request/request-interface.ts +++ b/server/models/request/request-interface.ts | |||
@@ -1,5 +1,7 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import * as Promise from 'bluebird' | ||
2 | 3 | ||
4 | import { AbstractRequestClass } from './abstract-request-interface' | ||
3 | import { PodInstance, PodAttributes } from '../pod' | 5 | import { PodInstance, PodAttributes } from '../pod' |
4 | import { RequestEndpoint } from '../../../shared/models/request-scheduler.model' | 6 | import { RequestEndpoint } from '../../../shared/models/request-scheduler.model' |
5 | 7 | ||
@@ -11,20 +13,16 @@ export type RequestsGrouped = { | |||
11 | } | 13 | } |
12 | 14 | ||
13 | export namespace RequestMethods { | 15 | export namespace RequestMethods { |
14 | export type CountTotalRequestsCallback = (err: Error, total: number) => void | 16 | export type CountTotalRequests = () => Promise<number> |
15 | export type CountTotalRequests = (callback: CountTotalRequestsCallback) => void | ||
16 | 17 | ||
17 | export type ListWithLimitAndRandomCallback = (err: Error, requestsGrouped?: RequestsGrouped) => void | 18 | export type ListWithLimitAndRandom = (limitPods: number, limitRequestsPerPod: number) => Promise<RequestsGrouped> |
18 | export type ListWithLimitAndRandom = (limitPods, limitRequestsPerPod, callback: ListWithLimitAndRandomCallback) => void | ||
19 | 19 | ||
20 | export type RemoveWithEmptyToCallback = (err: Error) => void | 20 | export type RemoveWithEmptyTo = () => Promise<number> |
21 | export type RemoveWithEmptyTo = (callback: RemoveWithEmptyToCallback) => void | ||
22 | 21 | ||
23 | export type RemoveAllCallback = (err: Error) => void | 22 | export type RemoveAll = () => Promise<void> |
24 | export type RemoveAll = (callback: RemoveAllCallback) => void | ||
25 | } | 23 | } |
26 | 24 | ||
27 | export interface RequestClass { | 25 | export interface RequestClass extends AbstractRequestClass<RequestsGrouped> { |
28 | countTotalRequests: RequestMethods.CountTotalRequests | 26 | countTotalRequests: RequestMethods.CountTotalRequests |
29 | listWithLimitAndRandom: RequestMethods.ListWithLimitAndRandom | 27 | listWithLimitAndRandom: RequestMethods.ListWithLimitAndRandom |
30 | removeWithEmptyTo: RequestMethods.RemoveWithEmptyTo | 28 | removeWithEmptyTo: RequestMethods.RemoveWithEmptyTo |
diff --git a/server/models/request/request-to-pod-interface.ts b/server/models/request/request-to-pod-interface.ts index 6d75ca6e5..7ca99f4d4 100644 --- a/server/models/request/request-to-pod-interface.ts +++ b/server/models/request/request-to-pod-interface.ts | |||
@@ -1,11 +1,13 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import * as Promise from 'bluebird' | ||
3 | |||
4 | import { AbstractRequestToPodClass } from './abstract-request-interface' | ||
2 | 5 | ||
3 | export namespace RequestToPodMethods { | 6 | export namespace RequestToPodMethods { |
4 | export type RemoveByRequestIdsAndPodCallback = (err: Error) => void | 7 | export type RemoveByRequestIdsAndPod = (requestsIds: number[], podId: number) => Promise<number> |
5 | export type RemoveByRequestIdsAndPod = (requestsIds: number[], podId: number, callback?: RemoveByRequestIdsAndPodCallback) => void | ||
6 | } | 8 | } |
7 | 9 | ||
8 | export interface RequestToPodClass { | 10 | export interface RequestToPodClass extends AbstractRequestToPodClass { |
9 | removeByRequestIdsAndPod: RequestToPodMethods.RemoveByRequestIdsAndPod | 11 | removeByRequestIdsAndPod: RequestToPodMethods.RemoveByRequestIdsAndPod |
10 | } | 12 | } |
11 | 13 | ||
diff --git a/server/models/request/request-to-pod.ts b/server/models/request/request-to-pod.ts index 67331be1d..6678ed290 100644 --- a/server/models/request/request-to-pod.ts +++ b/server/models/request/request-to-pod.ts | |||
@@ -2,7 +2,6 @@ import * as Sequelize from 'sequelize' | |||
2 | 2 | ||
3 | import { addMethodsToModel } from '../utils' | 3 | import { addMethodsToModel } from '../utils' |
4 | import { | 4 | import { |
5 | RequestToPodClass, | ||
6 | RequestToPodInstance, | 5 | RequestToPodInstance, |
7 | RequestToPodAttributes, | 6 | RequestToPodAttributes, |
8 | 7 | ||
@@ -38,9 +37,7 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da | |||
38 | 37 | ||
39 | // --------------------------------------------------------------------------- | 38 | // --------------------------------------------------------------------------- |
40 | 39 | ||
41 | removeByRequestIdsAndPod = function (requestsIds: number[], podId: number, callback?: RequestToPodMethods.RemoveByRequestIdsAndPodCallback) { | 40 | removeByRequestIdsAndPod = function (requestsIds: number[], podId: number) { |
42 | if (!callback) callback = function () { /* empty */ } | ||
43 | |||
44 | const query = { | 41 | const query = { |
45 | where: { | 42 | where: { |
46 | requestId: { | 43 | requestId: { |
@@ -50,5 +47,5 @@ removeByRequestIdsAndPod = function (requestsIds: number[], podId: number, callb | |||
50 | } | 47 | } |
51 | } | 48 | } |
52 | 49 | ||
53 | RequestToPod.destroy(query).asCallback(callback) | 50 | return RequestToPod.destroy(query) |
54 | } | 51 | } |
diff --git a/server/models/request/request-video-event-interface.ts b/server/models/request/request-video-event-interface.ts index 3ed03339a..a5032e1b1 100644 --- a/server/models/request/request-video-event-interface.ts +++ b/server/models/request/request-video-event-interface.ts | |||
@@ -1,5 +1,7 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import * as Promise from 'bluebird' | ||
2 | 3 | ||
4 | import { AbstractRequestClass, AbstractRequestToPodClass } from './abstract-request-interface' | ||
3 | import { VideoInstance } from '../video' | 5 | import { VideoInstance } from '../video' |
4 | import { PodInstance } from '../pod' | 6 | import { PodInstance } from '../pod' |
5 | 7 | ||
@@ -16,20 +18,16 @@ export type RequestsVideoEventGrouped = { | |||
16 | } | 18 | } |
17 | 19 | ||
18 | export namespace RequestVideoEventMethods { | 20 | export namespace RequestVideoEventMethods { |
19 | export type CountTotalRequestsCallback = (err: Error, total: number) => void | 21 | export type CountTotalRequests = () => Promise<number> |
20 | export type CountTotalRequests = (callback: CountTotalRequestsCallback) => void | ||
21 | 22 | ||
22 | export type ListWithLimitAndRandomCallback = (err: Error, requestsGrouped?: RequestsVideoEventGrouped) => void | 23 | export type ListWithLimitAndRandom = (limitPods: number, limitRequestsPerPod: number) => Promise<RequestsVideoEventGrouped> |
23 | export type ListWithLimitAndRandom = (limitPods: number, limitRequestsPerPod: number, callback: ListWithLimitAndRandomCallback) => void | ||
24 | 24 | ||
25 | export type RemoveByRequestIdsAndPodCallback = () => void | 25 | export type RemoveByRequestIdsAndPod = (ids: number[], podId: number) => Promise<number> |
26 | export type RemoveByRequestIdsAndPod = (ids: number[], podId: number, callback: RemoveByRequestIdsAndPodCallback) => void | ||
27 | 26 | ||
28 | export type RemoveAllCallback = () => void | 27 | export type RemoveAll = () => Promise<void> |
29 | export type RemoveAll = (callback: RemoveAllCallback) => void | ||
30 | } | 28 | } |
31 | 29 | ||
32 | export interface RequestVideoEventClass { | 30 | export interface RequestVideoEventClass extends AbstractRequestClass<RequestsVideoEventGrouped>, AbstractRequestToPodClass { |
33 | countTotalRequests: RequestVideoEventMethods.CountTotalRequests | 31 | countTotalRequests: RequestVideoEventMethods.CountTotalRequests |
34 | listWithLimitAndRandom: RequestVideoEventMethods.ListWithLimitAndRandom | 32 | listWithLimitAndRandom: RequestVideoEventMethods.ListWithLimitAndRandom |
35 | removeByRequestIdsAndPod: RequestVideoEventMethods.RemoveByRequestIdsAndPod | 33 | removeByRequestIdsAndPod: RequestVideoEventMethods.RemoveByRequestIdsAndPod |
@@ -41,10 +39,12 @@ export interface RequestVideoEventAttributes { | |||
41 | count: number | 39 | count: number |
42 | } | 40 | } |
43 | 41 | ||
44 | export interface RequestVideoEventInstance extends RequestVideoEventClass, RequestVideoEventAttributes, Sequelize.Instance<RequestVideoEventAttributes> { | 42 | export interface RequestVideoEventInstance |
43 | extends RequestVideoEventClass, RequestVideoEventAttributes, Sequelize.Instance<RequestVideoEventAttributes> { | ||
45 | id: number | 44 | id: number |
46 | 45 | ||
47 | Video: VideoInstance | 46 | Video: VideoInstance |
48 | } | 47 | } |
49 | 48 | ||
50 | export interface RequestVideoEventModel extends RequestVideoEventClass, Sequelize.Model<RequestVideoEventInstance, RequestVideoEventAttributes> {} | 49 | export interface RequestVideoEventModel |
50 | extends RequestVideoEventClass, Sequelize.Model<RequestVideoEventInstance, RequestVideoEventAttributes> {} | ||
diff --git a/server/models/request/request-video-event.ts b/server/models/request/request-video-event.ts index f552ef50b..90ea15702 100644 --- a/server/models/request/request-video-event.ts +++ b/server/models/request/request-video-event.ts | |||
@@ -10,7 +10,6 @@ import { REQUEST_VIDEO_EVENT_TYPES } from '../../initializers' | |||
10 | import { isVideoEventCountValid } from '../../helpers' | 10 | import { isVideoEventCountValid } from '../../helpers' |
11 | import { addMethodsToModel } from '../utils' | 11 | import { addMethodsToModel } from '../utils' |
12 | import { | 12 | import { |
13 | RequestVideoEventClass, | ||
14 | RequestVideoEventInstance, | 13 | RequestVideoEventInstance, |
15 | RequestVideoEventAttributes, | 14 | RequestVideoEventAttributes, |
16 | 15 | ||
@@ -77,23 +76,21 @@ function associate (models) { | |||
77 | }) | 76 | }) |
78 | } | 77 | } |
79 | 78 | ||
80 | countTotalRequests = function (callback: RequestVideoEventMethods.CountTotalRequestsCallback) { | 79 | countTotalRequests = function () { |
81 | const query = {} | 80 | const query = {} |
82 | return RequestVideoEvent.count(query).asCallback(callback) | 81 | return RequestVideoEvent.count(query) |
83 | } | 82 | } |
84 | 83 | ||
85 | listWithLimitAndRandom = function (limitPods: number, limitRequestsPerPod: number, callback: RequestVideoEventMethods.ListWithLimitAndRandomCallback) { | 84 | listWithLimitAndRandom = function (limitPods: number, limitRequestsPerPod: number) { |
86 | const Pod = db.Pod | 85 | const Pod = db.Pod |
87 | 86 | ||
88 | // We make a join between videos and authors to find the podId of our video event requests | 87 | // We make a join between videos and authors to find the podId of our video event requests |
89 | const podJoins = 'INNER JOIN "Videos" ON "Videos"."authorId" = "Authors"."id" ' + | 88 | const podJoins = 'INNER JOIN "Videos" ON "Videos"."authorId" = "Authors"."id" ' + |
90 | 'INNER JOIN "RequestVideoEvents" ON "RequestVideoEvents"."videoId" = "Videos"."id"' | 89 | 'INNER JOIN "RequestVideoEvents" ON "RequestVideoEvents"."videoId" = "Videos"."id"' |
91 | 90 | ||
92 | Pod.listRandomPodIdsWithRequest(limitPods, 'Authors', podJoins, function (err, podIds) { | 91 | return Pod.listRandomPodIdsWithRequest(limitPods, 'Authors', podJoins).then(podIds => { |
93 | if (err) return callback(err) | ||
94 | |||
95 | // We don't have friends that have requests | 92 | // We don't have friends that have requests |
96 | if (podIds.length === 0) return callback(null, []) | 93 | if (podIds.length === 0) return [] |
97 | 94 | ||
98 | const query = { | 95 | const query = { |
99 | order: [ | 96 | order: [ |
@@ -121,16 +118,14 @@ listWithLimitAndRandom = function (limitPods: number, limitRequestsPerPod: numbe | |||
121 | ] | 118 | ] |
122 | } | 119 | } |
123 | 120 | ||
124 | RequestVideoEvent.findAll(query).asCallback(function (err, requests) { | 121 | return RequestVideoEvent.findAll(query).then(requests => { |
125 | if (err) return callback(err) | ||
126 | |||
127 | const requestsGrouped = groupAndTruncateRequests(requests, limitRequestsPerPod) | 122 | const requestsGrouped = groupAndTruncateRequests(requests, limitRequestsPerPod) |
128 | return callback(err, requestsGrouped) | 123 | return requestsGrouped |
129 | }) | 124 | }) |
130 | }) | 125 | }) |
131 | } | 126 | } |
132 | 127 | ||
133 | removeByRequestIdsAndPod = function (ids: number[], podId: number, callback: RequestVideoEventMethods.RemoveByRequestIdsAndPodCallback) { | 128 | removeByRequestIdsAndPod = function (ids: number[], podId: number) { |
134 | const query = { | 129 | const query = { |
135 | where: { | 130 | where: { |
136 | id: { | 131 | id: { |
@@ -152,12 +147,12 @@ removeByRequestIdsAndPod = function (ids: number[], podId: number, callback: Req | |||
152 | ] | 147 | ] |
153 | } | 148 | } |
154 | 149 | ||
155 | RequestVideoEvent.destroy(query).asCallback(callback) | 150 | return RequestVideoEvent.destroy(query) |
156 | } | 151 | } |
157 | 152 | ||
158 | removeAll = function (callback: RequestVideoEventMethods.RemoveAllCallback) { | 153 | removeAll = function () { |
159 | // Delete all requests | 154 | // Delete all requests |
160 | RequestVideoEvent.truncate({ cascade: true }).asCallback(callback) | 155 | return RequestVideoEvent.truncate({ cascade: true }) |
161 | } | 156 | } |
162 | 157 | ||
163 | // --------------------------------------------------------------------------- | 158 | // --------------------------------------------------------------------------- |
diff --git a/server/models/request/request-video-qadu-interface.ts b/server/models/request/request-video-qadu-interface.ts index 805771cda..9a172a4d4 100644 --- a/server/models/request/request-video-qadu-interface.ts +++ b/server/models/request/request-video-qadu-interface.ts | |||
@@ -1,5 +1,7 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import * as Promise from 'bluebird' | ||
2 | 3 | ||
4 | import { AbstractRequestClass, AbstractRequestToPodClass } from './abstract-request-interface' | ||
3 | import { VideoInstance } from '../video' | 5 | import { VideoInstance } from '../video' |
4 | import { PodInstance } from '../pod' | 6 | import { PodInstance } from '../pod' |
5 | 7 | ||
@@ -14,20 +16,16 @@ export type RequestsVideoQaduGrouped = { | |||
14 | } | 16 | } |
15 | 17 | ||
16 | export namespace RequestVideoQaduMethods { | 18 | export namespace RequestVideoQaduMethods { |
17 | export type CountTotalRequestsCallback = (err: Error, total: number) => void | 19 | export type CountTotalRequests = () => Promise<number> |
18 | export type CountTotalRequests = (callback: CountTotalRequestsCallback) => void | ||
19 | 20 | ||
20 | export type ListWithLimitAndRandomCallback = (err: Error, requestsGrouped?: RequestsVideoQaduGrouped) => void | 21 | export type ListWithLimitAndRandom = (limitPods: number, limitRequestsPerPod: number) => Promise<RequestsVideoQaduGrouped> |
21 | export type ListWithLimitAndRandom = (limitPods: number, limitRequestsPerPod: number, callback: ListWithLimitAndRandomCallback) => void | ||
22 | 22 | ||
23 | export type RemoveByRequestIdsAndPodCallback = () => void | 23 | export type RemoveByRequestIdsAndPod = (ids: number[], podId: number) => Promise<number> |
24 | export type RemoveByRequestIdsAndPod = (ids: number[], podId: number, callback: RemoveByRequestIdsAndPodCallback) => void | ||
25 | 24 | ||
26 | export type RemoveAllCallback = () => void | 25 | export type RemoveAll = () => Promise<void> |
27 | export type RemoveAll = (callback: RemoveAllCallback) => void | ||
28 | } | 26 | } |
29 | 27 | ||
30 | export interface RequestVideoQaduClass { | 28 | export interface RequestVideoQaduClass extends AbstractRequestClass<RequestsVideoQaduGrouped>, AbstractRequestToPodClass { |
31 | countTotalRequests: RequestVideoQaduMethods.CountTotalRequests | 29 | countTotalRequests: RequestVideoQaduMethods.CountTotalRequests |
32 | listWithLimitAndRandom: RequestVideoQaduMethods.ListWithLimitAndRandom | 30 | listWithLimitAndRandom: RequestVideoQaduMethods.ListWithLimitAndRandom |
33 | removeByRequestIdsAndPod: RequestVideoQaduMethods.RemoveByRequestIdsAndPod | 31 | removeByRequestIdsAndPod: RequestVideoQaduMethods.RemoveByRequestIdsAndPod |
@@ -38,11 +36,13 @@ export interface RequestVideoQaduAttributes { | |||
38 | type: RequestVideoQaduType | 36 | type: RequestVideoQaduType |
39 | } | 37 | } |
40 | 38 | ||
41 | export interface RequestVideoQaduInstance extends RequestVideoQaduClass, RequestVideoQaduAttributes, Sequelize.Instance<RequestVideoQaduAttributes> { | 39 | export interface RequestVideoQaduInstance |
40 | extends RequestVideoQaduClass, RequestVideoQaduAttributes, Sequelize.Instance<RequestVideoQaduAttributes> { | ||
42 | id: number | 41 | id: number |
43 | 42 | ||
44 | Pod: PodInstance | 43 | Pod: PodInstance |
45 | Video: VideoInstance | 44 | Video: VideoInstance |
46 | } | 45 | } |
47 | 46 | ||
48 | export interface RequestVideoQaduModel extends RequestVideoQaduClass, Sequelize.Model<RequestVideoQaduInstance, RequestVideoQaduAttributes> {} | 47 | export interface RequestVideoQaduModel |
48 | extends RequestVideoQaduClass, Sequelize.Model<RequestVideoQaduInstance, RequestVideoQaduAttributes> {} | ||
diff --git a/server/models/request/request-video-qadu.ts b/server/models/request/request-video-qadu.ts index da62239f5..74e28f129 100644 --- a/server/models/request/request-video-qadu.ts +++ b/server/models/request/request-video-qadu.ts | |||
@@ -16,7 +16,6 @@ import { database as db } from '../../initializers/database' | |||
16 | import { REQUEST_VIDEO_QADU_TYPES } from '../../initializers' | 16 | import { REQUEST_VIDEO_QADU_TYPES } from '../../initializers' |
17 | import { addMethodsToModel } from '../utils' | 17 | import { addMethodsToModel } from '../utils' |
18 | import { | 18 | import { |
19 | RequestVideoQaduClass, | ||
20 | RequestVideoQaduInstance, | 19 | RequestVideoQaduInstance, |
21 | RequestVideoQaduAttributes, | 20 | RequestVideoQaduAttributes, |
22 | 21 | ||
@@ -83,20 +82,18 @@ function associate (models) { | |||
83 | }) | 82 | }) |
84 | } | 83 | } |
85 | 84 | ||
86 | countTotalRequests = function (callback: RequestVideoQaduMethods.CountTotalRequestsCallback) { | 85 | countTotalRequests = function () { |
87 | const query = {} | 86 | const query = {} |
88 | return RequestVideoQadu.count(query).asCallback(callback) | 87 | return RequestVideoQadu.count(query) |
89 | } | 88 | } |
90 | 89 | ||
91 | listWithLimitAndRandom = function (limitPods: number, limitRequestsPerPod: number, callback: RequestVideoQaduMethods.ListWithLimitAndRandomCallback) { | 90 | listWithLimitAndRandom = function (limitPods: number, limitRequestsPerPod: number) { |
92 | const Pod = db.Pod | 91 | const Pod = db.Pod |
93 | const tableJoin = '' | 92 | const tableJoin = '' |
94 | 93 | ||
95 | Pod.listRandomPodIdsWithRequest(limitPods, 'RequestVideoQadus', tableJoin, function (err, podIds) { | 94 | return Pod.listRandomPodIdsWithRequest(limitPods, 'RequestVideoQadus', tableJoin).then(podIds => { |
96 | if (err) return callback(err) | ||
97 | |||
98 | // We don't have friends that have requests | 95 | // We don't have friends that have requests |
99 | if (podIds.length === 0) return callback(null, []) | 96 | if (podIds.length === 0) return [] |
100 | 97 | ||
101 | const query = { | 98 | const query = { |
102 | include: [ | 99 | include: [ |
@@ -114,16 +111,14 @@ listWithLimitAndRandom = function (limitPods: number, limitRequestsPerPod: numbe | |||
114 | ] | 111 | ] |
115 | } | 112 | } |
116 | 113 | ||
117 | RequestVideoQadu.findAll(query).asCallback(function (err, requests) { | 114 | return RequestVideoQadu.findAll(query).then(requests => { |
118 | if (err) return callback(err) | ||
119 | |||
120 | const requestsGrouped = groupAndTruncateRequests(requests, limitRequestsPerPod) | 115 | const requestsGrouped = groupAndTruncateRequests(requests, limitRequestsPerPod) |
121 | return callback(err, requestsGrouped) | 116 | return requestsGrouped |
122 | }) | 117 | }) |
123 | }) | 118 | }) |
124 | } | 119 | } |
125 | 120 | ||
126 | removeByRequestIdsAndPod = function (ids: number[], podId: number, callback: RequestVideoQaduMethods.RemoveByRequestIdsAndPodCallback) { | 121 | removeByRequestIdsAndPod = function (ids: number[], podId: number) { |
127 | const query = { | 122 | const query = { |
128 | where: { | 123 | where: { |
129 | id: { | 124 | id: { |
@@ -133,12 +128,12 @@ removeByRequestIdsAndPod = function (ids: number[], podId: number, callback: Req | |||
133 | } | 128 | } |
134 | } | 129 | } |
135 | 130 | ||
136 | RequestVideoQadu.destroy(query).asCallback(callback) | 131 | return RequestVideoQadu.destroy(query) |
137 | } | 132 | } |
138 | 133 | ||
139 | removeAll = function (callback: RequestVideoQaduMethods.RemoveAllCallback) { | 134 | removeAll = function () { |
140 | // Delete all requests | 135 | // Delete all requests |
141 | RequestVideoQadu.truncate({ cascade: true }).asCallback(callback) | 136 | return RequestVideoQadu.truncate({ cascade: true }) |
142 | } | 137 | } |
143 | 138 | ||
144 | // --------------------------------------------------------------------------- | 139 | // --------------------------------------------------------------------------- |
diff --git a/server/models/request/request.ts b/server/models/request/request.ts index 66e7da845..c3ce2cd4e 100644 --- a/server/models/request/request.ts +++ b/server/models/request/request.ts | |||
@@ -5,7 +5,6 @@ import { database as db } from '../../initializers/database' | |||
5 | import { REQUEST_ENDPOINTS } from '../../initializers' | 5 | import { REQUEST_ENDPOINTS } from '../../initializers' |
6 | import { addMethodsToModel } from '../utils' | 6 | import { addMethodsToModel } from '../utils' |
7 | import { | 7 | import { |
8 | RequestClass, | ||
9 | RequestInstance, | 8 | RequestInstance, |
10 | RequestAttributes, | 9 | RequestAttributes, |
11 | 10 | ||
@@ -60,25 +59,23 @@ function associate (models) { | |||
60 | }) | 59 | }) |
61 | } | 60 | } |
62 | 61 | ||
63 | countTotalRequests = function (callback: RequestMethods.CountTotalRequestsCallback) { | 62 | countTotalRequests = function () { |
64 | // We need to include Pod because there are no cascade delete when a pod is removed | 63 | // We need to include Pod because there are no cascade delete when a pod is removed |
65 | // So we could count requests that do not have existing pod anymore | 64 | // So we could count requests that do not have existing pod anymore |
66 | const query = { | 65 | const query = { |
67 | include: [ Request['sequelize'].models.Pod ] | 66 | include: [ Request['sequelize'].models.Pod ] |
68 | } | 67 | } |
69 | 68 | ||
70 | return Request.count(query).asCallback(callback) | 69 | return Request.count(query) |
71 | } | 70 | } |
72 | 71 | ||
73 | listWithLimitAndRandom = function (limitPods: number, limitRequestsPerPod: number, callback: RequestMethods.ListWithLimitAndRandomCallback) { | 72 | listWithLimitAndRandom = function (limitPods: number, limitRequestsPerPod: number) { |
74 | const Pod = db.Pod | 73 | const Pod = db.Pod |
75 | const tableJoin = '' | 74 | const tableJoin = '' |
76 | 75 | ||
77 | Pod.listRandomPodIdsWithRequest(limitPods, 'RequestToPods', '', function (err, podIds) { | 76 | return Pod.listRandomPodIdsWithRequest(limitPods, 'RequestToPods', tableJoin).then(podIds => { |
78 | if (err) return callback(err) | ||
79 | |||
80 | // We don't have friends that have requests | 77 | // We don't have friends that have requests |
81 | if (podIds.length === 0) return callback(null, []) | 78 | if (podIds.length === 0) return [] |
82 | 79 | ||
83 | // The first x requests of these pods | 80 | // The first x requests of these pods |
84 | // It is very important to sort by id ASC to keep the requests order! | 81 | // It is very important to sort by id ASC to keep the requests order! |
@@ -98,23 +95,20 @@ listWithLimitAndRandom = function (limitPods: number, limitRequestsPerPod: numbe | |||
98 | ] | 95 | ] |
99 | } | 96 | } |
100 | 97 | ||
101 | Request.findAll(query).asCallback(function (err, requests) { | 98 | return Request.findAll(query).then(requests => { |
102 | if (err) return callback(err) | ||
103 | 99 | ||
104 | const requestsGrouped = groupAndTruncateRequests(requests, limitRequestsPerPod) | 100 | const requestsGrouped = groupAndTruncateRequests(requests, limitRequestsPerPod) |
105 | return callback(err, requestsGrouped) | 101 | return requestsGrouped |
106 | }) | 102 | }) |
107 | }) | 103 | }) |
108 | } | 104 | } |
109 | 105 | ||
110 | removeAll = function (callback: RequestMethods.RemoveAllCallback) { | 106 | removeAll = function () { |
111 | // Delete all requests | 107 | // Delete all requests |
112 | Request.truncate({ cascade: true }).asCallback(callback) | 108 | return Request.truncate({ cascade: true }) |
113 | } | 109 | } |
114 | 110 | ||
115 | removeWithEmptyTo = function (callback?: RequestMethods.RemoveWithEmptyToCallback) { | 111 | removeWithEmptyTo = function () { |
116 | if (!callback) callback = function () { /* empty */ } | ||
117 | |||
118 | const query = { | 112 | const query = { |
119 | where: { | 113 | where: { |
120 | id: { | 114 | id: { |
@@ -125,7 +119,7 @@ removeWithEmptyTo = function (callback?: RequestMethods.RemoveWithEmptyToCallbac | |||
125 | } | 119 | } |
126 | } | 120 | } |
127 | 121 | ||
128 | Request.destroy(query).asCallback(callback) | 122 | return Request.destroy(query) |
129 | } | 123 | } |
130 | 124 | ||
131 | // --------------------------------------------------------------------------- | 125 | // --------------------------------------------------------------------------- |
diff --git a/server/models/user/user-interface.ts b/server/models/user/user-interface.ts index 48c67678b..f743945f8 100644 --- a/server/models/user/user-interface.ts +++ b/server/models/user/user-interface.ts | |||
@@ -1,35 +1,29 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import * as Bluebird from 'bluebird' | 2 | import * as Promise from 'bluebird' |
3 | 3 | ||
4 | // Don't use barrel, import just what we need | 4 | // Don't use barrel, import just what we need |
5 | import { UserRole, User as FormatedUser } from '../../../shared/models/user.model' | 5 | import { UserRole, User as FormatedUser } from '../../../shared/models/user.model' |
6 | import { ResultList } from '../../../shared/models/result-list.model' | ||
6 | 7 | ||
7 | export namespace UserMethods { | 8 | export namespace UserMethods { |
8 | export type IsPasswordMatchCallback = (err: Error, same: boolean) => void | 9 | export type IsPasswordMatch = (this: UserInstance, password: string) => Promise<boolean> |
9 | export type IsPasswordMatch = (this: UserInstance, password: string, callback: IsPasswordMatchCallback) => void | ||
10 | 10 | ||
11 | export type ToFormatedJSON = (this: UserInstance) => FormatedUser | 11 | export type ToFormatedJSON = (this: UserInstance) => FormatedUser |
12 | export type IsAdmin = (this: UserInstance) => boolean | 12 | export type IsAdmin = (this: UserInstance) => boolean |
13 | 13 | ||
14 | export type CountTotalCallback = (err: Error, total: number) => void | 14 | export type CountTotal = () => Promise<number> |
15 | export type CountTotal = (callback: CountTotalCallback) => void | ||
16 | 15 | ||
17 | export type GetByUsername = (username: string) => Bluebird<UserInstance> | 16 | export type GetByUsername = (username: string) => Promise<UserInstance> |
18 | 17 | ||
19 | export type ListCallback = (err: Error, userInstances: UserInstance[]) => void | 18 | export type List = () => Promise<UserInstance[]> |
20 | export type List = (callback: ListCallback) => void | ||
21 | 19 | ||
22 | export type ListForApiCallback = (err: Error, userInstances?: UserInstance[], total?: number) => void | 20 | export type ListForApi = (start: number, count: number, sort: string) => Promise< ResultList<UserInstance> > |
23 | export type ListForApi = (start: number, count: number, sort: string, callback: ListForApiCallback) => void | ||
24 | 21 | ||
25 | export type LoadByIdCallback = (err: Error, userInstance: UserInstance) => void | 22 | export type LoadById = (id: number) => Promise<UserInstance> |
26 | export type LoadById = (id: number, callback: LoadByIdCallback) => void | ||
27 | 23 | ||
28 | export type LoadByUsernameCallback = (err: Error, userInstance: UserInstance) => void | 24 | export type LoadByUsername = (username: string) => Promise<UserInstance> |
29 | export type LoadByUsername = (username: string, callback: LoadByUsernameCallback) => void | ||
30 | 25 | ||
31 | export type LoadByUsernameOrEmailCallback = (err: Error, userInstance: UserInstance) => void | 26 | export type LoadByUsernameOrEmail = (username: string, email: string) => Promise<UserInstance> |
32 | export type LoadByUsernameOrEmail = (username: string, email: string, callback: LoadByUsernameOrEmailCallback) => void | ||
33 | } | 27 | } |
34 | 28 | ||
35 | export interface UserClass { | 29 | export interface UserClass { |
diff --git a/server/models/user/user-video-rate-interface.ts b/server/models/user/user-video-rate-interface.ts index a726639b1..e0b65a13d 100644 --- a/server/models/user/user-video-rate-interface.ts +++ b/server/models/user/user-video-rate-interface.ts | |||
@@ -1,10 +1,10 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import * as Promise from 'bluebird' | ||
2 | 3 | ||
3 | import { VideoRateType } from '../../../shared/models/user-video-rate.model' | 4 | import { VideoRateType } from '../../../shared/models/user-video-rate.model' |
4 | 5 | ||
5 | export namespace UserVideoRateMethods { | 6 | export namespace UserVideoRateMethods { |
6 | export type LoadCallback = (err: Error, userVideoRateInstance: UserVideoRateInstance) => void | 7 | export type Load = (userId: number, videoId: string, transaction: Sequelize.Transaction) => Promise<UserVideoRateInstance> |
7 | export type Load = (userId: number, videoId: string, transaction: Sequelize.Transaction, callback: LoadCallback) => void | ||
8 | } | 8 | } |
9 | 9 | ||
10 | export interface UserVideoRateClass { | 10 | export interface UserVideoRateClass { |
diff --git a/server/models/user/user-video-rate.ts b/server/models/user/user-video-rate.ts index 4bdd35bc9..37d0222cf 100644 --- a/server/models/user/user-video-rate.ts +++ b/server/models/user/user-video-rate.ts | |||
@@ -8,7 +8,6 @@ import { VIDEO_RATE_TYPES } from '../../initializers' | |||
8 | 8 | ||
9 | import { addMethodsToModel } from '../utils' | 9 | import { addMethodsToModel } from '../utils' |
10 | import { | 10 | import { |
11 | UserVideoRateClass, | ||
12 | UserVideoRateInstance, | 11 | UserVideoRateInstance, |
13 | UserVideoRateAttributes, | 12 | UserVideoRateAttributes, |
14 | 13 | ||
@@ -66,7 +65,7 @@ function associate (models) { | |||
66 | }) | 65 | }) |
67 | } | 66 | } |
68 | 67 | ||
69 | load = function (userId: number, videoId: string, transaction: Sequelize.Transaction, callback: UserVideoRateMethods.LoadCallback) { | 68 | load = function (userId: number, videoId: string, transaction: Sequelize.Transaction) { |
70 | const options: Sequelize.FindOptions = { | 69 | const options: Sequelize.FindOptions = { |
71 | where: { | 70 | where: { |
72 | userId, | 71 | userId, |
@@ -75,5 +74,5 @@ load = function (userId: number, videoId: string, transaction: Sequelize.Transac | |||
75 | } | 74 | } |
76 | if (transaction) options.transaction = transaction | 75 | if (transaction) options.transaction = transaction |
77 | 76 | ||
78 | return UserVideoRate.findOne(options).asCallback(callback) | 77 | return UserVideoRate.findOne(options) |
79 | } | 78 | } |
diff --git a/server/models/user/user.ts b/server/models/user/user.ts index 6b2410259..5ff81e741 100644 --- a/server/models/user/user.ts +++ b/server/models/user/user.ts | |||
@@ -13,7 +13,6 @@ import { | |||
13 | 13 | ||
14 | import { addMethodsToModel } from '../utils' | 14 | import { addMethodsToModel } from '../utils' |
15 | import { | 15 | import { |
16 | UserClass, | ||
17 | UserInstance, | 16 | UserInstance, |
18 | UserAttributes, | 17 | UserAttributes, |
19 | 18 | ||
@@ -118,21 +117,16 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da | |||
118 | } | 117 | } |
119 | 118 | ||
120 | function beforeCreateOrUpdate (user: UserInstance) { | 119 | function beforeCreateOrUpdate (user: UserInstance) { |
121 | return new Promise(function (resolve, reject) { | 120 | return cryptPassword(user.password).then(hash => { |
122 | cryptPassword(user.password, function (err, hash) { | 121 | user.password = hash |
123 | if (err) return reject(err) | 122 | return undefined |
124 | |||
125 | user.password = hash | ||
126 | |||
127 | return resolve() | ||
128 | }) | ||
129 | }) | 123 | }) |
130 | } | 124 | } |
131 | 125 | ||
132 | // ------------------------------ METHODS ------------------------------ | 126 | // ------------------------------ METHODS ------------------------------ |
133 | 127 | ||
134 | isPasswordMatch = function (this: UserInstance, password: string, callback: UserMethods.IsPasswordMatchCallback) { | 128 | isPasswordMatch = function (this: UserInstance, password: string) { |
135 | return comparePassword(password, this.password, callback) | 129 | return comparePassword(password, this.password) |
136 | } | 130 | } |
137 | 131 | ||
138 | toFormatedJSON = function (this: UserInstance) { | 132 | toFormatedJSON = function (this: UserInstance) { |
@@ -164,8 +158,8 @@ function associate (models) { | |||
164 | }) | 158 | }) |
165 | } | 159 | } |
166 | 160 | ||
167 | countTotal = function (callback: UserMethods.CountTotalCallback) { | 161 | countTotal = function () { |
168 | return this.count().asCallback(callback) | 162 | return this.count() |
169 | } | 163 | } |
170 | 164 | ||
171 | getByUsername = function (username: string) { | 165 | getByUsername = function (username: string) { |
@@ -178,44 +172,45 @@ getByUsername = function (username: string) { | |||
178 | return User.findOne(query) | 172 | return User.findOne(query) |
179 | } | 173 | } |
180 | 174 | ||
181 | list = function (callback: UserMethods.ListCallback) { | 175 | list = function () { |
182 | return User.find().asCallback(callback) | 176 | return User.findAll() |
183 | } | 177 | } |
184 | 178 | ||
185 | listForApi = function (start: number, count: number, sort: string, callback: UserMethods.ListForApiCallback) { | 179 | listForApi = function (start: number, count: number, sort: string) { |
186 | const query = { | 180 | const query = { |
187 | offset: start, | 181 | offset: start, |
188 | limit: count, | 182 | limit: count, |
189 | order: [ getSort(sort) ] | 183 | order: [ getSort(sort) ] |
190 | } | 184 | } |
191 | 185 | ||
192 | return User.findAndCountAll(query).asCallback(function (err, result) { | 186 | return User.findAndCountAll(query).then(({ rows, count }) => { |
193 | if (err) return callback(err) | 187 | return { |
194 | 188 | data: rows, | |
195 | return callback(null, result.rows, result.count) | 189 | total: count |
190 | } | ||
196 | }) | 191 | }) |
197 | } | 192 | } |
198 | 193 | ||
199 | loadById = function (id: number, callback: UserMethods.LoadByIdCallback) { | 194 | loadById = function (id: number) { |
200 | return User.findById(id).asCallback(callback) | 195 | return User.findById(id) |
201 | } | 196 | } |
202 | 197 | ||
203 | loadByUsername = function (username: string, callback: UserMethods.LoadByUsernameCallback) { | 198 | loadByUsername = function (username: string) { |
204 | const query = { | 199 | const query = { |
205 | where: { | 200 | where: { |
206 | username: username | 201 | username: username |
207 | } | 202 | } |
208 | } | 203 | } |
209 | 204 | ||
210 | return User.findOne(query).asCallback(callback) | 205 | return User.findOne(query) |
211 | } | 206 | } |
212 | 207 | ||
213 | loadByUsernameOrEmail = function (username: string, email: string, callback: UserMethods.LoadByUsernameOrEmailCallback) { | 208 | loadByUsernameOrEmail = function (username: string, email: string) { |
214 | const query = { | 209 | const query = { |
215 | where: { | 210 | where: { |
216 | $or: [ { username }, { email } ] | 211 | $or: [ { username }, { email } ] |
217 | } | 212 | } |
218 | } | 213 | } |
219 | 214 | ||
220 | return User.findOne(query).asCallback(callback) | 215 | return User.findOne(query) |
221 | } | 216 | } |
diff --git a/server/models/video/author-interface.ts b/server/models/video/author-interface.ts index c1b30848c..dbcb85b17 100644 --- a/server/models/video/author-interface.ts +++ b/server/models/video/author-interface.ts | |||
@@ -1,10 +1,15 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import * as Promise from 'bluebird' | ||
2 | 3 | ||
3 | import { PodInstance } from '../pod' | 4 | import { PodInstance } from '../pod' |
4 | 5 | ||
5 | export namespace AuthorMethods { | 6 | export namespace AuthorMethods { |
6 | export type FindOrCreateAuthorCallback = (err: Error, authorInstance?: AuthorInstance) => void | 7 | export type FindOrCreateAuthor = ( |
7 | export type FindOrCreateAuthor = (name: string, podId: number, userId: number, transaction: Sequelize.Transaction, callback: FindOrCreateAuthorCallback) => void | 8 | name: string, |
9 | podId: number, | ||
10 | userId: number, | ||
11 | transaction: Sequelize.Transaction | ||
12 | ) => Promise<AuthorInstance> | ||
8 | } | 13 | } |
9 | 14 | ||
10 | export interface AuthorClass { | 15 | export interface AuthorClass { |
diff --git a/server/models/video/author.ts b/server/models/video/author.ts index 4a115e328..3222c4834 100644 --- a/server/models/video/author.ts +++ b/server/models/video/author.ts | |||
@@ -4,7 +4,6 @@ import { isUserUsernameValid } from '../../helpers' | |||
4 | 4 | ||
5 | import { addMethodsToModel } from '../utils' | 5 | import { addMethodsToModel } from '../utils' |
6 | import { | 6 | import { |
7 | AuthorClass, | ||
8 | AuthorInstance, | 7 | AuthorInstance, |
9 | AuthorAttributes, | 8 | AuthorAttributes, |
10 | 9 | ||
@@ -74,30 +73,18 @@ function associate (models) { | |||
74 | }) | 73 | }) |
75 | } | 74 | } |
76 | 75 | ||
77 | findOrCreateAuthor = function ( | 76 | findOrCreateAuthor = function (name: string, podId: number, userId: number, transaction: Sequelize.Transaction) { |
78 | name: string, | ||
79 | podId: number, | ||
80 | userId: number, | ||
81 | transaction: Sequelize.Transaction, | ||
82 | callback: AuthorMethods.FindOrCreateAuthorCallback | ||
83 | ) { | ||
84 | const author = { | 77 | const author = { |
85 | name, | 78 | name, |
86 | podId, | 79 | podId, |
87 | userId | 80 | userId |
88 | } | 81 | } |
89 | 82 | ||
90 | const query: any = { | 83 | const query: Sequelize.FindOrInitializeOptions<AuthorAttributes> = { |
91 | where: author, | 84 | where: author, |
92 | defaults: author | 85 | defaults: author, |
86 | transaction | ||
93 | } | 87 | } |
94 | 88 | ||
95 | if (transaction !== null) query.transaction = transaction | 89 | return Author.findOrCreate(query).then(([ authorInstance ]) => authorInstance) |
96 | |||
97 | Author.findOrCreate(query).asCallback(function (err, result) { | ||
98 | if (err) return callback(err) | ||
99 | |||
100 | // [ instance, wasCreated ] | ||
101 | return callback(null, result[0]) | ||
102 | }) | ||
103 | } | 90 | } |
diff --git a/server/models/video/tag-interface.ts b/server/models/video/tag-interface.ts index e045e7ca5..08e5c3246 100644 --- a/server/models/video/tag-interface.ts +++ b/server/models/video/tag-interface.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import * as Promise from 'bluebird' | ||
2 | 3 | ||
3 | export namespace TagMethods { | 4 | export namespace TagMethods { |
4 | export type FindOrCreateTagsCallback = (err: Error, tagInstances: TagInstance[]) => void | 5 | export type FindOrCreateTags = (tags: string[], transaction: Sequelize.Transaction) => Promise<TagInstance[]> |
5 | export type FindOrCreateTags = (tags: string[], transaction: Sequelize.Transaction, callback: FindOrCreateTagsCallback) => void | ||
6 | } | 6 | } |
7 | 7 | ||
8 | export interface TagClass { | 8 | export interface TagClass { |
diff --git a/server/models/video/tag.ts b/server/models/video/tag.ts index 3c657d751..d0d8353d7 100644 --- a/server/models/video/tag.ts +++ b/server/models/video/tag.ts | |||
@@ -1,9 +1,8 @@ | |||
1 | import { each } from 'async' | ||
2 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import * as Promise from 'bluebird' | ||
3 | 3 | ||
4 | import { addMethodsToModel } from '../utils' | 4 | import { addMethodsToModel } from '../utils' |
5 | import { | 5 | import { |
6 | TagClass, | ||
7 | TagInstance, | 6 | TagInstance, |
8 | TagAttributes, | 7 | TagAttributes, |
9 | 8 | ||
@@ -52,10 +51,9 @@ function associate (models) { | |||
52 | }) | 51 | }) |
53 | } | 52 | } |
54 | 53 | ||
55 | findOrCreateTags = function (tags: string[], transaction: Sequelize.Transaction, callback: TagMethods.FindOrCreateTagsCallback) { | 54 | findOrCreateTags = function (tags: string[], transaction: Sequelize.Transaction) { |
56 | const tagInstances = [] | 55 | const tasks: Promise<TagInstance>[] = [] |
57 | 56 | tags.forEach(tag => { | |
58 | each<string, Error>(tags, function (tag, callbackEach) { | ||
59 | const query: any = { | 57 | const query: any = { |
60 | where: { | 58 | where: { |
61 | name: tag | 59 | name: tag |
@@ -67,15 +65,9 @@ findOrCreateTags = function (tags: string[], transaction: Sequelize.Transaction, | |||
67 | 65 | ||
68 | if (transaction) query.transaction = transaction | 66 | if (transaction) query.transaction = transaction |
69 | 67 | ||
70 | Tag.findOrCreate(query).asCallback(function (err, res) { | 68 | const promise = Tag.findOrCreate(query).then(([ tagInstance ]) => tagInstance) |
71 | if (err) return callbackEach(err) | 69 | tasks.push(promise) |
72 | |||
73 | // res = [ tag, isCreated ] | ||
74 | const tag = res[0] | ||
75 | tagInstances.push(tag) | ||
76 | return callbackEach() | ||
77 | }) | ||
78 | }, function (err) { | ||
79 | return callback(err, tagInstances) | ||
80 | }) | 70 | }) |
71 | |||
72 | return Promise.all(tasks) | ||
81 | } | 73 | } |
diff --git a/server/models/video/video-abuse-interface.ts b/server/models/video/video-abuse-interface.ts index c85d09091..75647fe0e 100644 --- a/server/models/video/video-abuse-interface.ts +++ b/server/models/video/video-abuse-interface.ts | |||
@@ -1,6 +1,8 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import * as Promise from 'bluebird' | ||
2 | 3 | ||
3 | import { PodInstance } from '../pod' | 4 | import { PodInstance } from '../pod' |
5 | import { ResultList } from '../../../shared' | ||
4 | 6 | ||
5 | // Don't use barrel, import just what we need | 7 | // Don't use barrel, import just what we need |
6 | import { VideoAbuse as FormatedVideoAbuse } from '../../../shared/models/video-abuse.model' | 8 | import { VideoAbuse as FormatedVideoAbuse } from '../../../shared/models/video-abuse.model' |
@@ -8,8 +10,7 @@ import { VideoAbuse as FormatedVideoAbuse } from '../../../shared/models/video-a | |||
8 | export namespace VideoAbuseMethods { | 10 | export namespace VideoAbuseMethods { |
9 | export type ToFormatedJSON = (this: VideoAbuseInstance) => FormatedVideoAbuse | 11 | export type ToFormatedJSON = (this: VideoAbuseInstance) => FormatedVideoAbuse |
10 | 12 | ||
11 | export type ListForApiCallback = (err: Error, videoAbuseInstances?: VideoAbuseInstance[], total?: number) => void | 13 | export type ListForApi = (start: number, count: number, sort: string) => Promise< ResultList<VideoAbuseInstance> > |
12 | export type ListForApi = (start: number, count: number, sort: string, callback: ListForApiCallback) => void | ||
13 | } | 14 | } |
14 | 15 | ||
15 | export interface VideoAbuseClass { | 16 | export interface VideoAbuseClass { |
diff --git a/server/models/video/video-abuse.ts b/server/models/video/video-abuse.ts index bc5f01e21..ab1a3ea7d 100644 --- a/server/models/video/video-abuse.ts +++ b/server/models/video/video-abuse.ts | |||
@@ -5,7 +5,6 @@ import { isVideoAbuseReporterUsernameValid, isVideoAbuseReasonValid } from '../. | |||
5 | 5 | ||
6 | import { addMethodsToModel, getSort } from '../utils' | 6 | import { addMethodsToModel, getSort } from '../utils' |
7 | import { | 7 | import { |
8 | VideoAbuseClass, | ||
9 | VideoAbuseInstance, | 8 | VideoAbuseInstance, |
10 | VideoAbuseAttributes, | 9 | VideoAbuseAttributes, |
11 | 10 | ||
@@ -109,7 +108,7 @@ function associate (models) { | |||
109 | }) | 108 | }) |
110 | } | 109 | } |
111 | 110 | ||
112 | listForApi = function (start: number, count: number, sort: string, callback: VideoAbuseMethods.ListForApiCallback) { | 111 | listForApi = function (start: number, count: number, sort: string) { |
113 | const query = { | 112 | const query = { |
114 | offset: start, | 113 | offset: start, |
115 | limit: count, | 114 | limit: count, |
@@ -122,11 +121,7 @@ listForApi = function (start: number, count: number, sort: string, callback: Vid | |||
122 | ] | 121 | ] |
123 | } | 122 | } |
124 | 123 | ||
125 | return VideoAbuse.findAndCountAll(query).asCallback(function (err, result) { | 124 | return VideoAbuse.findAndCountAll(query).then(({ rows, count }) => { |
126 | if (err) return callback(err) | 125 | return { total: count, data: rows } |
127 | |||
128 | return callback(null, result.rows, result.count) | ||
129 | }) | 126 | }) |
130 | } | 127 | } |
131 | |||
132 | |||
diff --git a/server/models/video/video-blacklist-interface.ts b/server/models/video/video-blacklist-interface.ts index d4d6528d1..5ca423801 100644 --- a/server/models/video/video-blacklist-interface.ts +++ b/server/models/video/video-blacklist-interface.ts | |||
@@ -1,4 +1,7 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import * as Promise from 'bluebird' | ||
3 | |||
4 | import { ResultList } from '../../../shared' | ||
2 | 5 | ||
3 | // Don't use barrel, import just what we need | 6 | // Don't use barrel, import just what we need |
4 | import { BlacklistedVideo as FormatedBlacklistedVideo } from '../../../shared/models/video-blacklist.model' | 7 | import { BlacklistedVideo as FormatedBlacklistedVideo } from '../../../shared/models/video-blacklist.model' |
@@ -6,20 +9,15 @@ import { BlacklistedVideo as FormatedBlacklistedVideo } from '../../../shared/mo | |||
6 | export namespace BlacklistedVideoMethods { | 9 | export namespace BlacklistedVideoMethods { |
7 | export type ToFormatedJSON = (this: BlacklistedVideoInstance) => FormatedBlacklistedVideo | 10 | export type ToFormatedJSON = (this: BlacklistedVideoInstance) => FormatedBlacklistedVideo |
8 | 11 | ||
9 | export type CountTotalCallback = (err: Error, total: number) => void | 12 | export type CountTotal = () => Promise<number> |
10 | export type CountTotal = (callback: CountTotalCallback) => void | ||
11 | 13 | ||
12 | export type ListCallback = (err: Error, backlistedVideoInstances: BlacklistedVideoInstance[]) => void | 14 | export type List = () => Promise<BlacklistedVideoInstance[]> |
13 | export type List = (callback: ListCallback) => void | ||
14 | 15 | ||
15 | export type ListForApiCallback = (err: Error, blacklistedVIdeoInstances?: BlacklistedVideoInstance[], total?: number) => void | 16 | export type ListForApi = (start: number, count: number, sort: string) => Promise< ResultList<BlacklistedVideoInstance> > |
16 | export type ListForApi = (start: number, count: number, sort: string, callback: ListForApiCallback) => void | ||
17 | 17 | ||
18 | export type LoadByIdCallback = (err: Error, blacklistedVideoInstance: BlacklistedVideoInstance) => void | 18 | export type LoadById = (id: number) => Promise<BlacklistedVideoInstance> |
19 | export type LoadById = (id: number, callback: LoadByIdCallback) => void | ||
20 | 19 | ||
21 | export type LoadByVideoIdCallback = (err: Error, blacklistedVideoInstance: BlacklistedVideoInstance) => void | 20 | export type LoadByVideoId = (id: string) => Promise<BlacklistedVideoInstance> |
22 | export type LoadByVideoId = (id: string, callback: LoadByVideoIdCallback) => void | ||
23 | } | 21 | } |
24 | 22 | ||
25 | export interface BlacklistedVideoClass { | 23 | export interface BlacklistedVideoClass { |
@@ -35,7 +33,8 @@ export interface BlacklistedVideoAttributes { | |||
35 | videoId: string | 33 | videoId: string |
36 | } | 34 | } |
37 | 35 | ||
38 | export interface BlacklistedVideoInstance extends BlacklistedVideoClass, BlacklistedVideoAttributes, Sequelize.Instance<BlacklistedVideoAttributes> { | 36 | export interface BlacklistedVideoInstance |
37 | extends BlacklistedVideoClass, BlacklistedVideoAttributes, Sequelize.Instance<BlacklistedVideoAttributes> { | ||
39 | id: number | 38 | id: number |
40 | createdAt: Date | 39 | createdAt: Date |
41 | updatedAt: Date | 40 | updatedAt: Date |
@@ -43,4 +42,5 @@ export interface BlacklistedVideoInstance extends BlacklistedVideoClass, Blackli | |||
43 | toFormatedJSON: BlacklistedVideoMethods.ToFormatedJSON | 42 | toFormatedJSON: BlacklistedVideoMethods.ToFormatedJSON |
44 | } | 43 | } |
45 | 44 | ||
46 | export interface BlacklistedVideoModel extends BlacklistedVideoClass, Sequelize.Model<BlacklistedVideoInstance, BlacklistedVideoAttributes> {} | 45 | export interface BlacklistedVideoModel |
46 | extends BlacklistedVideoClass, Sequelize.Model<BlacklistedVideoInstance, BlacklistedVideoAttributes> {} | ||
diff --git a/server/models/video/video-blacklist.ts b/server/models/video/video-blacklist.ts index 3576c96f6..8c42dbc21 100644 --- a/server/models/video/video-blacklist.ts +++ b/server/models/video/video-blacklist.ts | |||
@@ -2,7 +2,6 @@ import * as Sequelize from 'sequelize' | |||
2 | 2 | ||
3 | import { addMethodsToModel, getSort } from '../utils' | 3 | import { addMethodsToModel, getSort } from '../utils' |
4 | import { | 4 | import { |
5 | BlacklistedVideoClass, | ||
6 | BlacklistedVideoInstance, | 5 | BlacklistedVideoInstance, |
7 | BlacklistedVideoAttributes, | 6 | BlacklistedVideoAttributes, |
8 | 7 | ||
@@ -66,38 +65,39 @@ function associate (models) { | |||
66 | }) | 65 | }) |
67 | } | 66 | } |
68 | 67 | ||
69 | countTotal = function (callback: BlacklistedVideoMethods.CountTotalCallback) { | 68 | countTotal = function () { |
70 | return BlacklistedVideo.count().asCallback(callback) | 69 | return BlacklistedVideo.count() |
71 | } | 70 | } |
72 | 71 | ||
73 | list = function (callback: BlacklistedVideoMethods.ListCallback) { | 72 | list = function () { |
74 | return BlacklistedVideo.findAll().asCallback(callback) | 73 | return BlacklistedVideo.findAll() |
75 | } | 74 | } |
76 | 75 | ||
77 | listForApi = function (start: number, count: number, sort: string, callback: BlacklistedVideoMethods.ListForApiCallback) { | 76 | listForApi = function (start: number, count: number, sort: string) { |
78 | const query = { | 77 | const query = { |
79 | offset: start, | 78 | offset: start, |
80 | limit: count, | 79 | limit: count, |
81 | order: [ getSort(sort) ] | 80 | order: [ getSort(sort) ] |
82 | } | 81 | } |
83 | 82 | ||
84 | return BlacklistedVideo.findAndCountAll(query).asCallback(function (err, result) { | 83 | return BlacklistedVideo.findAndCountAll(query).then(({ rows, count }) => { |
85 | if (err) return callback(err) | 84 | return { |
86 | 85 | data: rows, | |
87 | return callback(null, result.rows, result.count) | 86 | total: count |
87 | } | ||
88 | }) | 88 | }) |
89 | } | 89 | } |
90 | 90 | ||
91 | loadById = function (id: number, callback: BlacklistedVideoMethods.LoadByIdCallback) { | 91 | loadById = function (id: number) { |
92 | return BlacklistedVideo.findById(id).asCallback(callback) | 92 | return BlacklistedVideo.findById(id) |
93 | } | 93 | } |
94 | 94 | ||
95 | loadByVideoId = function (id: string, callback: BlacklistedVideoMethods.LoadByIdCallback) { | 95 | loadByVideoId = function (id: string) { |
96 | const query = { | 96 | const query = { |
97 | where: { | 97 | where: { |
98 | videoId: id | 98 | videoId: id |
99 | } | 99 | } |
100 | } | 100 | } |
101 | 101 | ||
102 | return BlacklistedVideo.find(query).asCallback(callback) | 102 | return BlacklistedVideo.findOne(query) |
103 | } | 103 | } |
diff --git a/server/models/video/video-interface.ts b/server/models/video/video-interface.ts index 4b591b9e7..c3e3365d5 100644 --- a/server/models/video/video-interface.ts +++ b/server/models/video/video-interface.ts | |||
@@ -1,10 +1,12 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import * as Promise from 'bluebird' | ||
2 | 3 | ||
3 | import { AuthorInstance } from './author-interface' | 4 | import { AuthorInstance } from './author-interface' |
4 | import { VideoTagInstance } from './video-tag-interface' | 5 | import { TagAttributes, TagInstance } from './tag-interface' |
5 | 6 | ||
6 | // Don't use barrel, import just what we need | 7 | // Don't use barrel, import just what we need |
7 | import { Video as FormatedVideo } from '../../../shared/models/video.model' | 8 | import { Video as FormatedVideo } from '../../../shared/models/video.model' |
9 | import { ResultList } from '../../../shared/models/result-list.model' | ||
8 | 10 | ||
9 | export type FormatedAddRemoteVideo = { | 11 | export type FormatedAddRemoteVideo = { |
10 | name: string | 12 | name: string |
@@ -56,46 +58,32 @@ export namespace VideoMethods { | |||
56 | export type IsOwned = (this: VideoInstance) => boolean | 58 | export type IsOwned = (this: VideoInstance) => boolean |
57 | export type ToFormatedJSON = (this: VideoInstance) => FormatedVideo | 59 | export type ToFormatedJSON = (this: VideoInstance) => FormatedVideo |
58 | 60 | ||
59 | export type ToAddRemoteJSONCallback = (err: Error, videoFormated?: FormatedAddRemoteVideo) => void | 61 | export type ToAddRemoteJSON = (this: VideoInstance) => Promise<FormatedAddRemoteVideo> |
60 | export type ToAddRemoteJSON = (this: VideoInstance, callback: ToAddRemoteJSONCallback) => void | ||
61 | |||
62 | export type ToUpdateRemoteJSON = (this: VideoInstance) => FormatedUpdateRemoteVideo | 62 | export type ToUpdateRemoteJSON = (this: VideoInstance) => FormatedUpdateRemoteVideo |
63 | 63 | ||
64 | export type TranscodeVideofileCallback = (err: Error) => void | 64 | export type TranscodeVideofile = (this: VideoInstance) => Promise<void> |
65 | export type TranscodeVideofile = (this: VideoInstance, callback: TranscodeVideofileCallback) => void | 65 | |
66 | 66 | // Return thumbnail name | |
67 | export type GenerateThumbnailFromDataCallback = (err: Error, thumbnailName?: string) => void | 67 | export type GenerateThumbnailFromData = (video: VideoInstance, thumbnailData: string) => Promise<string> |
68 | export type GenerateThumbnailFromData = (video: VideoInstance, thumbnailData: string, callback: GenerateThumbnailFromDataCallback) => void | 68 | export type GetDurationFromFile = (videoPath: string) => Promise<number> |
69 | 69 | ||
70 | export type GetDurationFromFileCallback = (err: Error, duration?: number) => void | 70 | export type List = () => Promise<VideoInstance[]> |
71 | export type GetDurationFromFile = (videoPath, callback) => void | 71 | export type ListOwnedAndPopulateAuthorAndTags = () => Promise<VideoInstance[]> |
72 | 72 | export type ListOwnedByAuthor = (author: string) => Promise<VideoInstance[]> | |
73 | export type ListCallback = (err: Error, videoInstances: VideoInstance[]) => void | 73 | |
74 | export type List = (callback: ListCallback) => void | 74 | export type ListForApi = (start: number, count: number, sort: string) => Promise< ResultList<VideoInstance> > |
75 | 75 | export type SearchAndPopulateAuthorAndPodAndTags = ( | |
76 | export type ListForApiCallback = (err: Error, videoInstances?: VideoInstance[], total?: number) => void | 76 | value: string, |
77 | export type ListForApi = (start: number, count: number, sort: string, callback: ListForApiCallback) => void | 77 | field: string, |
78 | 78 | start: number, | |
79 | export type LoadByHostAndRemoteIdCallback = (err: Error, videoInstance: VideoInstance) => void | 79 | count: number, |
80 | export type LoadByHostAndRemoteId = (fromHost: string, remoteId: string, callback: LoadByHostAndRemoteIdCallback) => void | 80 | sort: string |
81 | 81 | ) => Promise< ResultList<VideoInstance> > | |
82 | export type ListOwnedAndPopulateAuthorAndTagsCallback = (err: Error, videoInstances: VideoInstance[]) => void | 82 | |
83 | export type ListOwnedAndPopulateAuthorAndTags = (callback: ListOwnedAndPopulateAuthorAndTagsCallback) => void | 83 | export type Load = (id: string) => Promise<VideoInstance> |
84 | 84 | export type LoadByHostAndRemoteId = (fromHost: string, remoteId: string) => Promise<VideoInstance> | |
85 | export type ListOwnedByAuthorCallback = (err: Error, videoInstances: VideoInstance[]) => void | 85 | export type LoadAndPopulateAuthor = (id: string) => Promise<VideoInstance> |
86 | export type ListOwnedByAuthor = (author: string, callback: ListOwnedByAuthorCallback) => void | 86 | export type LoadAndPopulateAuthorAndPodAndTags = (id: string) => Promise<VideoInstance> |
87 | |||
88 | export type LoadCallback = (err: Error, videoInstance: VideoInstance) => void | ||
89 | export type Load = (id: string, callback: LoadCallback) => void | ||
90 | |||
91 | export type LoadAndPopulateAuthorCallback = (err: Error, videoInstance: VideoInstance) => void | ||
92 | export type LoadAndPopulateAuthor = (id: string, callback: LoadAndPopulateAuthorCallback) => void | ||
93 | |||
94 | export type LoadAndPopulateAuthorAndPodAndTagsCallback = (err: Error, videoInstance: VideoInstance) => void | ||
95 | export type LoadAndPopulateAuthorAndPodAndTags = (id: string, callback: LoadAndPopulateAuthorAndPodAndTagsCallback) => void | ||
96 | |||
97 | export type SearchAndPopulateAuthorAndPodAndTagsCallback = (err: Error, videoInstances?: VideoInstance[], total?: number) => void | ||
98 | export type SearchAndPopulateAuthorAndPodAndTags = (value: string, field: string, start: number, count: number, sort: string, callback: SearchAndPopulateAuthorAndPodAndTagsCallback) => void | ||
99 | } | 87 | } |
100 | 88 | ||
101 | export interface VideoClass { | 89 | export interface VideoClass { |
@@ -139,7 +127,7 @@ export interface VideoAttributes { | |||
139 | dislikes?: number | 127 | dislikes?: number |
140 | 128 | ||
141 | Author?: AuthorInstance | 129 | Author?: AuthorInstance |
142 | Tags?: VideoTagInstance[] | 130 | Tags?: TagInstance[] |
143 | } | 131 | } |
144 | 132 | ||
145 | export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.Instance<VideoAttributes> { | 133 | export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.Instance<VideoAttributes> { |
@@ -157,6 +145,8 @@ export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.In | |||
157 | toAddRemoteJSON: VideoMethods.ToAddRemoteJSON | 145 | toAddRemoteJSON: VideoMethods.ToAddRemoteJSON |
158 | toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON | 146 | toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON |
159 | transcodeVideofile: VideoMethods.TranscodeVideofile | 147 | transcodeVideofile: VideoMethods.TranscodeVideofile |
148 | |||
149 | setTags: Sequelize.HasManySetAssociationsMixin<TagAttributes, string> | ||
160 | } | 150 | } |
161 | 151 | ||
162 | export interface VideoModel extends VideoClass, Sequelize.Model<VideoInstance, VideoAttributes> {} | 152 | export interface VideoModel extends VideoClass, Sequelize.Model<VideoInstance, VideoAttributes> {} |
diff --git a/server/models/video/video-tag.ts b/server/models/video/video-tag.ts index 71ca85332..ac45374f8 100644 --- a/server/models/video/video-tag.ts +++ b/server/models/video/video-tag.ts | |||
@@ -1,12 +1,8 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | import { addMethodsToModel } from '../utils' | ||
4 | import { | 3 | import { |
5 | VideoTagClass, | ||
6 | VideoTagInstance, | 4 | VideoTagInstance, |
7 | VideoTagAttributes, | 5 | VideoTagAttributes |
8 | |||
9 | VideoTagMethods | ||
10 | } from './video-tag-interface' | 6 | } from './video-tag-interface' |
11 | 7 | ||
12 | let VideoTag: Sequelize.Model<VideoTagInstance, VideoTagAttributes> | 8 | let VideoTag: Sequelize.Model<VideoTagInstance, VideoTagAttributes> |
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index e66ebee2d..629051ae4 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -1,17 +1,15 @@ | |||
1 | import * as safeBuffer from 'safe-buffer' | 1 | import * as safeBuffer from 'safe-buffer' |
2 | const Buffer = safeBuffer.Buffer | 2 | const Buffer = safeBuffer.Buffer |
3 | import * as createTorrent from 'create-torrent' | ||
4 | import * as ffmpeg from 'fluent-ffmpeg' | 3 | import * as ffmpeg from 'fluent-ffmpeg' |
5 | import * as fs from 'fs' | ||
6 | import * as magnetUtil from 'magnet-uri' | 4 | import * as magnetUtil from 'magnet-uri' |
7 | import { map, values } from 'lodash' | 5 | import { map, values } from 'lodash' |
8 | import { parallel, series } from 'async' | ||
9 | import * as parseTorrent from 'parse-torrent' | 6 | import * as parseTorrent from 'parse-torrent' |
10 | import { join } from 'path' | 7 | import { join } from 'path' |
11 | import * as Sequelize from 'sequelize' | 8 | import * as Sequelize from 'sequelize' |
9 | import * as Promise from 'bluebird' | ||
12 | 10 | ||
13 | import { database as db } from '../../initializers/database' | 11 | import { database as db } from '../../initializers/database' |
14 | import { VideoTagInstance } from './video-tag-interface' | 12 | import { TagInstance } from './tag-interface' |
15 | import { | 13 | import { |
16 | logger, | 14 | logger, |
17 | isVideoNameValid, | 15 | isVideoNameValid, |
@@ -21,7 +19,12 @@ import { | |||
21 | isVideoNSFWValid, | 19 | isVideoNSFWValid, |
22 | isVideoDescriptionValid, | 20 | isVideoDescriptionValid, |
23 | isVideoInfoHashValid, | 21 | isVideoInfoHashValid, |
24 | isVideoDurationValid | 22 | isVideoDurationValid, |
23 | readFileBufferPromise, | ||
24 | unlinkPromise, | ||
25 | renamePromise, | ||
26 | writeFilePromise, | ||
27 | createTorrentPromise | ||
25 | } from '../../helpers' | 28 | } from '../../helpers' |
26 | import { | 29 | import { |
27 | CONSTRAINTS_FIELDS, | 30 | CONSTRAINTS_FIELDS, |
@@ -37,7 +40,6 @@ import { JobScheduler, removeVideoToFriends } from '../../lib' | |||
37 | 40 | ||
38 | import { addMethodsToModel, getSort } from '../utils' | 41 | import { addMethodsToModel, getSort } from '../utils' |
39 | import { | 42 | import { |
40 | VideoClass, | ||
41 | VideoInstance, | 43 | VideoInstance, |
42 | VideoAttributes, | 44 | VideoAttributes, |
43 | 45 | ||
@@ -260,7 +262,7 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da | |||
260 | toFormatedJSON, | 262 | toFormatedJSON, |
261 | toAddRemoteJSON, | 263 | toAddRemoteJSON, |
262 | toUpdateRemoteJSON, | 264 | toUpdateRemoteJSON, |
263 | transcodeVideofile, | 265 | transcodeVideofile |
264 | ] | 266 | ] |
265 | addMethodsToModel(Video, classMethods, instanceMethods) | 267 | addMethodsToModel(Video, classMethods, instanceMethods) |
266 | 268 | ||
@@ -276,91 +278,53 @@ function beforeValidate (video: VideoInstance) { | |||
276 | } | 278 | } |
277 | 279 | ||
278 | function beforeCreate (video: VideoInstance, options: { transaction: Sequelize.Transaction }) { | 280 | function beforeCreate (video: VideoInstance, options: { transaction: Sequelize.Transaction }) { |
279 | return new Promise(function (resolve, reject) { | 281 | if (video.isOwned()) { |
282 | const videoPath = join(CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename()) | ||
280 | const tasks = [] | 283 | const tasks = [] |
281 | 284 | ||
282 | if (video.isOwned()) { | 285 | tasks.push( |
283 | const videoPath = join(CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename()) | 286 | createTorrentFromVideo(video, videoPath), |
284 | 287 | createThumbnail(video, videoPath), | |
285 | tasks.push( | 288 | createPreview(video, videoPath) |
286 | function createVideoTorrent (callback) { | 289 | ) |
287 | createTorrentFromVideo(video, videoPath, callback) | ||
288 | }, | ||
289 | |||
290 | function createVideoThumbnail (callback) { | ||
291 | createThumbnail(video, videoPath, callback) | ||
292 | }, | ||
293 | |||
294 | function createVideoPreview (callback) { | ||
295 | createPreview(video, videoPath, callback) | ||
296 | } | ||
297 | ) | ||
298 | |||
299 | if (CONFIG.TRANSCODING.ENABLED === true) { | ||
300 | tasks.push( | ||
301 | function createVideoTranscoderJob (callback) { | ||
302 | const dataInput = { | ||
303 | id: video.id | ||
304 | } | ||
305 | 290 | ||
306 | JobScheduler.Instance.createJob(options.transaction, 'videoTranscoder', dataInput, callback) | 291 | if (CONFIG.TRANSCODING.ENABLED === true) { |
307 | } | 292 | const dataInput = { |
308 | ) | 293 | id: video.id |
309 | } | 294 | } |
310 | 295 | ||
311 | return parallel(tasks, function (err) { | 296 | tasks.push( |
312 | if (err) return reject(err) | 297 | JobScheduler.Instance.createJob(options.transaction, 'videoTranscoder', dataInput) |
313 | 298 | ) | |
314 | return resolve() | ||
315 | }) | ||
316 | } | 299 | } |
317 | 300 | ||
318 | return resolve() | 301 | return Promise.all(tasks) |
319 | }) | 302 | } |
303 | |||
304 | return Promise.resolve() | ||
320 | } | 305 | } |
321 | 306 | ||
322 | function afterDestroy (video: VideoInstance) { | 307 | function afterDestroy (video: VideoInstance) { |
323 | return new Promise(function (resolve, reject) { | 308 | const tasks = [] |
324 | const tasks = [] | ||
325 | |||
326 | tasks.push( | ||
327 | function (callback) { | ||
328 | removeThumbnail(video, callback) | ||
329 | } | ||
330 | ) | ||
331 | |||
332 | if (video.isOwned()) { | ||
333 | tasks.push( | ||
334 | function removeVideoFile (callback) { | ||
335 | removeFile(video, callback) | ||
336 | }, | ||
337 | 309 | ||
338 | function removeVideoTorrent (callback) { | 310 | tasks.push( |
339 | removeTorrent(video, callback) | 311 | removeThumbnail(video) |
340 | }, | 312 | ) |
341 | |||
342 | function removeVideoPreview (callback) { | ||
343 | removePreview(video, callback) | ||
344 | }, | ||
345 | |||
346 | function notifyFriends (callback) { | ||
347 | const params = { | ||
348 | remoteId: video.id | ||
349 | } | ||
350 | |||
351 | removeVideoToFriends(params) | ||
352 | 313 | ||
353 | return callback() | 314 | if (video.isOwned()) { |
354 | } | 315 | const removeVideoToFriendsParams = { |
355 | ) | 316 | remoteId: video.id |
356 | } | 317 | } |
357 | 318 | ||
358 | parallel(tasks, function (err) { | 319 | tasks.push( |
359 | if (err) return reject(err) | 320 | removeFile(video), |
321 | removeTorrent(video), | ||
322 | removePreview(video), | ||
323 | removeVideoToFriends(removeVideoToFriendsParams) | ||
324 | ) | ||
325 | } | ||
360 | 326 | ||
361 | return resolve() | 327 | return Promise.all(tasks) |
362 | }) | ||
363 | }) | ||
364 | } | 328 | } |
365 | 329 | ||
366 | // ------------------------------ METHODS ------------------------------ | 330 | // ------------------------------ METHODS ------------------------------ |
@@ -488,7 +452,7 @@ toFormatedJSON = function (this: VideoInstance) { | |||
488 | views: this.views, | 452 | views: this.views, |
489 | likes: this.likes, | 453 | likes: this.likes, |
490 | dislikes: this.dislikes, | 454 | dislikes: this.dislikes, |
491 | tags: map<VideoTagInstance, string>(this.Tags, 'name'), | 455 | tags: map<TagInstance, string>(this.Tags, 'name'), |
492 | thumbnailPath: join(STATIC_PATHS.THUMBNAILS, this.getThumbnailName()), | 456 | thumbnailPath: join(STATIC_PATHS.THUMBNAILS, this.getThumbnailName()), |
493 | createdAt: this.createdAt, | 457 | createdAt: this.createdAt, |
494 | updatedAt: this.updatedAt | 458 | updatedAt: this.updatedAt |
@@ -497,15 +461,11 @@ toFormatedJSON = function (this: VideoInstance) { | |||
497 | return json | 461 | return json |
498 | } | 462 | } |
499 | 463 | ||
500 | toAddRemoteJSON = function (this: VideoInstance, callback: VideoMethods.ToAddRemoteJSONCallback) { | 464 | toAddRemoteJSON = function (this: VideoInstance) { |
501 | // Get thumbnail data to send to the other pod | 465 | // Get thumbnail data to send to the other pod |
502 | const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName()) | 466 | const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName()) |
503 | fs.readFile(thumbnailPath, (err, thumbnailData) => { | ||
504 | if (err) { | ||
505 | logger.error('Cannot read the thumbnail of the video') | ||
506 | return callback(err) | ||
507 | } | ||
508 | 467 | ||
468 | return readFileBufferPromise(thumbnailPath).then(thumbnailData => { | ||
509 | const remoteVideo = { | 469 | const remoteVideo = { |
510 | name: this.name, | 470 | name: this.name, |
511 | category: this.category, | 471 | category: this.category, |
@@ -518,7 +478,7 @@ toAddRemoteJSON = function (this: VideoInstance, callback: VideoMethods.ToAddRem | |||
518 | author: this.Author.name, | 478 | author: this.Author.name, |
519 | duration: this.duration, | 479 | duration: this.duration, |
520 | thumbnailData: thumbnailData.toString('binary'), | 480 | thumbnailData: thumbnailData.toString('binary'), |
521 | tags: map<VideoTagInstance, string>(this.Tags, 'name'), | 481 | tags: map<TagInstance, string>(this.Tags, 'name'), |
522 | createdAt: this.createdAt, | 482 | createdAt: this.createdAt, |
523 | updatedAt: this.updatedAt, | 483 | updatedAt: this.updatedAt, |
524 | extname: this.extname, | 484 | extname: this.extname, |
@@ -527,7 +487,7 @@ toAddRemoteJSON = function (this: VideoInstance, callback: VideoMethods.ToAddRem | |||
527 | dislikes: this.dislikes | 487 | dislikes: this.dislikes |
528 | } | 488 | } |
529 | 489 | ||
530 | return callback(null, remoteVideo) | 490 | return remoteVideo |
531 | }) | 491 | }) |
532 | } | 492 | } |
533 | 493 | ||
@@ -543,7 +503,7 @@ toUpdateRemoteJSON = function (this: VideoInstance) { | |||
543 | remoteId: this.id, | 503 | remoteId: this.id, |
544 | author: this.Author.name, | 504 | author: this.Author.name, |
545 | duration: this.duration, | 505 | duration: this.duration, |
546 | tags: map<VideoTagInstance, string>(this.Tags, 'name'), | 506 | tags: map<TagInstance, string>(this.Tags, 'name'), |
547 | createdAt: this.createdAt, | 507 | createdAt: this.createdAt, |
548 | updatedAt: this.updatedAt, | 508 | updatedAt: this.updatedAt, |
549 | extname: this.extname, | 509 | extname: this.extname, |
@@ -555,7 +515,7 @@ toUpdateRemoteJSON = function (this: VideoInstance) { | |||
555 | return json | 515 | return json |
556 | } | 516 | } |
557 | 517 | ||
558 | transcodeVideofile = function (this: VideoInstance, finalCallback: VideoMethods.TranscodeVideofileCallback) { | 518 | transcodeVideofile = function (this: VideoInstance) { |
559 | const video = this | 519 | const video = this |
560 | 520 | ||
561 | const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR | 521 | const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR |
@@ -563,78 +523,73 @@ transcodeVideofile = function (this: VideoInstance, finalCallback: VideoMethods. | |||
563 | const videoInputPath = join(videosDirectory, video.getVideoFilename()) | 523 | const videoInputPath = join(videosDirectory, video.getVideoFilename()) |
564 | const videoOutputPath = join(videosDirectory, video.id + '-transcoded' + newExtname) | 524 | const videoOutputPath = join(videosDirectory, video.id + '-transcoded' + newExtname) |
565 | 525 | ||
566 | ffmpeg(videoInputPath) | 526 | return new Promise<void>((res, rej) => { |
567 | .output(videoOutputPath) | 527 | ffmpeg(videoInputPath) |
568 | .videoCodec('libx264') | 528 | .output(videoOutputPath) |
569 | .outputOption('-threads ' + CONFIG.TRANSCODING.THREADS) | 529 | .videoCodec('libx264') |
570 | .outputOption('-movflags faststart') | 530 | .outputOption('-threads ' + CONFIG.TRANSCODING.THREADS) |
571 | .on('error', finalCallback) | 531 | .outputOption('-movflags faststart') |
572 | .on('end', function () { | 532 | .on('error', rej) |
573 | series([ | 533 | .on('end', () => { |
574 | function removeOldFile (callback) { | 534 | |
575 | fs.unlink(videoInputPath, callback) | 535 | return unlinkPromise(videoInputPath) |
576 | }, | 536 | .then(() => { |
577 | 537 | // Important to do this before getVideoFilename() to take in account the new file extension | |
578 | function moveNewFile (callback) { | 538 | video.set('extname', newExtname) |
579 | // Important to do this before getVideoFilename() to take in account the new file extension | 539 | |
580 | video.set('extname', newExtname) | 540 | const newVideoPath = join(videosDirectory, video.getVideoFilename()) |
581 | 541 | return renamePromise(videoOutputPath, newVideoPath) | |
582 | const newVideoPath = join(videosDirectory, video.getVideoFilename()) | ||
583 | fs.rename(videoOutputPath, newVideoPath, callback) | ||
584 | }, | ||
585 | |||
586 | function torrent (callback) { | ||
587 | const newVideoPath = join(videosDirectory, video.getVideoFilename()) | ||
588 | createTorrentFromVideo(video, newVideoPath, callback) | ||
589 | }, | ||
590 | |||
591 | function videoExtension (callback) { | ||
592 | video.save().asCallback(callback) | ||
593 | } | ||
594 | |||
595 | ], function (err: Error) { | ||
596 | if (err) { | ||
597 | // Autodesctruction... | ||
598 | video.destroy().asCallback(function (err) { | ||
599 | if (err) logger.error('Cannot destruct video after transcoding failure.', { error: err }) | ||
600 | }) | 542 | }) |
543 | .then(() => { | ||
544 | const newVideoPath = join(videosDirectory, video.getVideoFilename()) | ||
545 | return createTorrentFromVideo(video, newVideoPath) | ||
546 | }) | ||
547 | .then(() => { | ||
548 | return video.save() | ||
549 | }) | ||
550 | .then(() => { | ||
551 | return res() | ||
552 | }) | ||
553 | .catch(err => { | ||
554 | // Autodesctruction... | ||
555 | video.destroy().asCallback(function (err) { | ||
556 | if (err) logger.error('Cannot destruct video after transcoding failure.', { error: err }) | ||
557 | }) | ||
601 | 558 | ||
602 | return finalCallback(err) | 559 | return rej(err) |
603 | } | 560 | }) |
604 | |||
605 | return finalCallback(null) | ||
606 | }) | 561 | }) |
607 | }) | 562 | .run() |
608 | .run() | 563 | }) |
609 | } | 564 | } |
610 | 565 | ||
611 | // ------------------------------ STATICS ------------------------------ | 566 | // ------------------------------ STATICS ------------------------------ |
612 | 567 | ||
613 | generateThumbnailFromData = function (video: VideoInstance, thumbnailData: string, callback: VideoMethods.GenerateThumbnailFromDataCallback) { | 568 | generateThumbnailFromData = function (video: VideoInstance, thumbnailData: string) { |
614 | // Creating the thumbnail for a remote video | 569 | // Creating the thumbnail for a remote video |
615 | 570 | ||
616 | const thumbnailName = video.getThumbnailName() | 571 | const thumbnailName = video.getThumbnailName() |
617 | const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, thumbnailName) | 572 | const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, thumbnailName) |
618 | fs.writeFile(thumbnailPath, Buffer.from(thumbnailData, 'binary'), function (err) { | 573 | return writeFilePromise(thumbnailPath, Buffer.from(thumbnailData, 'binary')).then(() => { |
619 | if (err) return callback(err) | 574 | return thumbnailName |
620 | |||
621 | return callback(null, thumbnailName) | ||
622 | }) | 575 | }) |
623 | } | 576 | } |
624 | 577 | ||
625 | getDurationFromFile = function (videoPath: string, callback: VideoMethods.GetDurationFromFileCallback) { | 578 | getDurationFromFile = function (videoPath: string) { |
626 | ffmpeg.ffprobe(videoPath, function (err, metadata) { | 579 | return new Promise<number>((res, rej) => { |
627 | if (err) return callback(err) | 580 | ffmpeg.ffprobe(videoPath, function (err, metadata) { |
581 | if (err) return rej(err) | ||
628 | 582 | ||
629 | return callback(null, Math.floor(metadata.format.duration)) | 583 | return res(Math.floor(metadata.format.duration)) |
584 | }) | ||
630 | }) | 585 | }) |
631 | } | 586 | } |
632 | 587 | ||
633 | list = function (callback: VideoMethods.ListCallback) { | 588 | list = function () { |
634 | return Video.findAll().asCallback(callback) | 589 | return Video.findAll() |
635 | } | 590 | } |
636 | 591 | ||
637 | listForApi = function (start: number, count: number, sort: string, callback: VideoMethods.ListForApiCallback) { | 592 | listForApi = function (start: number, count: number, sort: string) { |
638 | // Exclude Blakclisted videos from the list | 593 | // Exclude Blakclisted videos from the list |
639 | const query = { | 594 | const query = { |
640 | distinct: true, | 595 | distinct: true, |
@@ -652,14 +607,15 @@ listForApi = function (start: number, count: number, sort: string, callback: Vid | |||
652 | where: createBaseVideosWhere() | 607 | where: createBaseVideosWhere() |
653 | } | 608 | } |
654 | 609 | ||
655 | return Video.findAndCountAll(query).asCallback(function (err, result) { | 610 | return Video.findAndCountAll(query).then(({ rows, count }) => { |
656 | if (err) return callback(err) | 611 | return { |
657 | 612 | data: rows, | |
658 | return callback(null, result.rows, result.count) | 613 | total: count |
614 | } | ||
659 | }) | 615 | }) |
660 | } | 616 | } |
661 | 617 | ||
662 | loadByHostAndRemoteId = function (fromHost: string, remoteId: string, callback: VideoMethods.LoadByHostAndRemoteIdCallback) { | 618 | loadByHostAndRemoteId = function (fromHost: string, remoteId: string) { |
663 | const query = { | 619 | const query = { |
664 | where: { | 620 | where: { |
665 | remoteId: remoteId | 621 | remoteId: remoteId |
@@ -680,10 +636,10 @@ loadByHostAndRemoteId = function (fromHost: string, remoteId: string, callback: | |||
680 | ] | 636 | ] |
681 | } | 637 | } |
682 | 638 | ||
683 | return Video.findOne(query).asCallback(callback) | 639 | return Video.findOne(query) |
684 | } | 640 | } |
685 | 641 | ||
686 | listOwnedAndPopulateAuthorAndTags = function (callback: VideoMethods.ListOwnedAndPopulateAuthorAndTagsCallback) { | 642 | listOwnedAndPopulateAuthorAndTags = function () { |
687 | // If remoteId is null this is *our* video | 643 | // If remoteId is null this is *our* video |
688 | const query = { | 644 | const query = { |
689 | where: { | 645 | where: { |
@@ -692,10 +648,10 @@ listOwnedAndPopulateAuthorAndTags = function (callback: VideoMethods.ListOwnedAn | |||
692 | include: [ Video['sequelize'].models.Author, Video['sequelize'].models.Tag ] | 648 | include: [ Video['sequelize'].models.Author, Video['sequelize'].models.Tag ] |
693 | } | 649 | } |
694 | 650 | ||
695 | return Video.findAll(query).asCallback(callback) | 651 | return Video.findAll(query) |
696 | } | 652 | } |
697 | 653 | ||
698 | listOwnedByAuthor = function (author: string, callback: VideoMethods.ListOwnedByAuthorCallback) { | 654 | listOwnedByAuthor = function (author: string) { |
699 | const query = { | 655 | const query = { |
700 | where: { | 656 | where: { |
701 | remoteId: null | 657 | remoteId: null |
@@ -710,22 +666,22 @@ listOwnedByAuthor = function (author: string, callback: VideoMethods.ListOwnedBy | |||
710 | ] | 666 | ] |
711 | } | 667 | } |
712 | 668 | ||
713 | return Video.findAll(query).asCallback(callback) | 669 | return Video.findAll(query) |
714 | } | 670 | } |
715 | 671 | ||
716 | load = function (id: string, callback: VideoMethods.LoadCallback) { | 672 | load = function (id: string) { |
717 | return Video.findById(id).asCallback(callback) | 673 | return Video.findById(id) |
718 | } | 674 | } |
719 | 675 | ||
720 | loadAndPopulateAuthor = function (id: string, callback: VideoMethods.LoadAndPopulateAuthorCallback) { | 676 | loadAndPopulateAuthor = function (id: string) { |
721 | const options = { | 677 | const options = { |
722 | include: [ Video['sequelize'].models.Author ] | 678 | include: [ Video['sequelize'].models.Author ] |
723 | } | 679 | } |
724 | 680 | ||
725 | return Video.findById(id, options).asCallback(callback) | 681 | return Video.findById(id, options) |
726 | } | 682 | } |
727 | 683 | ||
728 | loadAndPopulateAuthorAndPodAndTags = function (id: string, callback: VideoMethods.LoadAndPopulateAuthorAndPodAndTagsCallback) { | 684 | loadAndPopulateAuthorAndPodAndTags = function (id: string) { |
729 | const options = { | 685 | const options = { |
730 | include: [ | 686 | include: [ |
731 | { | 687 | { |
@@ -736,17 +692,10 @@ loadAndPopulateAuthorAndPodAndTags = function (id: string, callback: VideoMethod | |||
736 | ] | 692 | ] |
737 | } | 693 | } |
738 | 694 | ||
739 | return Video.findById(id, options).asCallback(callback) | 695 | return Video.findById(id, options) |
740 | } | 696 | } |
741 | 697 | ||
742 | searchAndPopulateAuthorAndPodAndTags = function ( | 698 | searchAndPopulateAuthorAndPodAndTags = function (value: string, field: string, start: number, count: number, sort: string) { |
743 | value: string, | ||
744 | field: string, | ||
745 | start: number, | ||
746 | count: number, | ||
747 | sort: string, | ||
748 | callback: VideoMethods.SearchAndPopulateAuthorAndPodAndTagsCallback | ||
749 | ) { | ||
750 | const podInclude: any = { | 699 | const podInclude: any = { |
751 | model: Video['sequelize'].models.Pod, | 700 | model: Video['sequelize'].models.Pod, |
752 | required: false | 701 | required: false |
@@ -778,7 +727,11 @@ searchAndPopulateAuthorAndPodAndTags = function ( | |||
778 | } else if (field === 'tags') { | 727 | } else if (field === 'tags') { |
779 | const escapedValue = Video['sequelize'].escape('%' + value + '%') | 728 | const escapedValue = Video['sequelize'].escape('%' + value + '%') |
780 | query.where.id.$in = Video['sequelize'].literal( | 729 | query.where.id.$in = Video['sequelize'].literal( |
781 | '(SELECT "VideoTags"."videoId" FROM "Tags" INNER JOIN "VideoTags" ON "Tags"."id" = "VideoTags"."tagId" WHERE name LIKE ' + escapedValue + ')' | 730 | `(SELECT "VideoTags"."videoId" |
731 | FROM "Tags" | ||
732 | INNER JOIN "VideoTags" ON "Tags"."id" = "VideoTags"."tagId" | ||
733 | WHERE name LIKE ${escapedValue} | ||
734 | )` | ||
782 | ) | 735 | ) |
783 | } else if (field === 'host') { | 736 | } else if (field === 'host') { |
784 | // FIXME: Include our pod? (not stored in the database) | 737 | // FIXME: Include our pod? (not stored in the database) |
@@ -810,10 +763,11 @@ searchAndPopulateAuthorAndPodAndTags = function ( | |||
810 | // query.include.push([ Video['sequelize'].models.Tag ]) | 763 | // query.include.push([ Video['sequelize'].models.Tag ]) |
811 | } | 764 | } |
812 | 765 | ||
813 | return Video.findAndCountAll(query).asCallback(function (err, result) { | 766 | return Video.findAndCountAll(query).then(({ rows, count }) => { |
814 | if (err) return callback(err) | 767 | return { |
815 | 768 | data: rows, | |
816 | return callback(null, result.rows, result.count) | 769 | total: count |
770 | } | ||
817 | }) | 771 | }) |
818 | } | 772 | } |
819 | 773 | ||
@@ -829,27 +783,27 @@ function createBaseVideosWhere () { | |||
829 | } | 783 | } |
830 | } | 784 | } |
831 | 785 | ||
832 | function removeThumbnail (video: VideoInstance, callback: (err: Error) => void) { | 786 | function removeThumbnail (video: VideoInstance) { |
833 | const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName()) | 787 | const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName()) |
834 | fs.unlink(thumbnailPath, callback) | 788 | return unlinkPromise(thumbnailPath) |
835 | } | 789 | } |
836 | 790 | ||
837 | function removeFile (video: VideoInstance, callback: (err: Error) => void) { | 791 | function removeFile (video: VideoInstance) { |
838 | const filePath = join(CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename()) | 792 | const filePath = join(CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename()) |
839 | fs.unlink(filePath, callback) | 793 | return unlinkPromise(filePath) |
840 | } | 794 | } |
841 | 795 | ||
842 | function removeTorrent (video: VideoInstance, callback: (err: Error) => void) { | 796 | function removeTorrent (video: VideoInstance) { |
843 | const torrenPath = join(CONFIG.STORAGE.TORRENTS_DIR, video.getTorrentName()) | 797 | const torrenPath = join(CONFIG.STORAGE.TORRENTS_DIR, video.getTorrentName()) |
844 | fs.unlink(torrenPath, callback) | 798 | return unlinkPromise(torrenPath) |
845 | } | 799 | } |
846 | 800 | ||
847 | function removePreview (video: VideoInstance, callback: (err: Error) => void) { | 801 | function removePreview (video: VideoInstance) { |
848 | // Same name than video thumnail | 802 | // Same name than video thumnail |
849 | fs.unlink(CONFIG.STORAGE.PREVIEWS_DIR + video.getPreviewName(), callback) | 803 | return unlinkPromise(CONFIG.STORAGE.PREVIEWS_DIR + video.getPreviewName()) |
850 | } | 804 | } |
851 | 805 | ||
852 | function createTorrentFromVideo (video: VideoInstance, videoPath: string, callback: (err: Error) => void) { | 806 | function createTorrentFromVideo (video: VideoInstance, videoPath: string) { |
853 | const options = { | 807 | const options = { |
854 | announceList: [ | 808 | announceList: [ |
855 | [ CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT + '/tracker/socket' ] | 809 | [ CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT + '/tracker/socket' ] |
@@ -859,30 +813,27 @@ function createTorrentFromVideo (video: VideoInstance, videoPath: string, callba | |||
859 | ] | 813 | ] |
860 | } | 814 | } |
861 | 815 | ||
862 | createTorrent(videoPath, options, function (err, torrent) { | 816 | return createTorrentPromise(videoPath, options) |
863 | if (err) return callback(err) | 817 | .then(torrent => { |
864 | 818 | const filePath = join(CONFIG.STORAGE.TORRENTS_DIR, video.getTorrentName()) | |
865 | const filePath = join(CONFIG.STORAGE.TORRENTS_DIR, video.getTorrentName()) | 819 | return writeFilePromise(filePath, torrent).then(() => torrent) |
866 | fs.writeFile(filePath, torrent, function (err) { | 820 | }) |
867 | if (err) return callback(err) | 821 | .then(torrent => { |
868 | |||
869 | const parsedTorrent = parseTorrent(torrent) | 822 | const parsedTorrent = parseTorrent(torrent) |
870 | video.set('infoHash', parsedTorrent.infoHash) | 823 | video.set('infoHash', parsedTorrent.infoHash) |
871 | video.validate().asCallback(callback) | 824 | return video.validate() |
872 | }) | 825 | }) |
873 | }) | ||
874 | } | 826 | } |
875 | 827 | ||
876 | function createPreview (video: VideoInstance, videoPath: string, callback: (err: Error) => void) { | 828 | function createPreview (video: VideoInstance, videoPath: string) { |
877 | generateImage(video, videoPath, CONFIG.STORAGE.PREVIEWS_DIR, video.getPreviewName(), null, callback) | 829 | return generateImage(video, videoPath, CONFIG.STORAGE.PREVIEWS_DIR, video.getPreviewName(), null) |
878 | } | 830 | } |
879 | 831 | ||
880 | function createThumbnail (video: VideoInstance, videoPath: string, callback: (err: Error) => void) { | 832 | function createThumbnail (video: VideoInstance, videoPath: string) { |
881 | generateImage(video, videoPath, CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName(), THUMBNAILS_SIZE, callback) | 833 | return generateImage(video, videoPath, CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName(), THUMBNAILS_SIZE) |
882 | } | 834 | } |
883 | 835 | ||
884 | type GenerateImageCallback = (err: Error, imageName: string) => void | 836 | function generateImage (video: VideoInstance, videoPath: string, folder: string, imageName: string, size: string) { |
885 | function generateImage (video: VideoInstance, videoPath: string, folder: string, imageName: string, size: string, callback?: GenerateImageCallback) { | ||
886 | const options: any = { | 837 | const options: any = { |
887 | filename: imageName, | 838 | filename: imageName, |
888 | count: 1, | 839 | count: 1, |
@@ -893,29 +844,25 @@ function generateImage (video: VideoInstance, videoPath: string, folder: string, | |||
893 | options.size = size | 844 | options.size = size |
894 | } | 845 | } |
895 | 846 | ||
896 | ffmpeg(videoPath) | 847 | return new Promise<string>((res, rej) => { |
897 | .on('error', callback) | 848 | ffmpeg(videoPath) |
898 | .on('end', function () { | 849 | .on('error', rej) |
899 | callback(null, imageName) | 850 | .on('end', function () { |
900 | }) | 851 | return res(imageName) |
901 | .thumbnail(options) | 852 | }) |
853 | .thumbnail(options) | ||
854 | }) | ||
902 | } | 855 | } |
903 | 856 | ||
904 | function removeFromBlacklist (video: VideoInstance, callback: (err: Error) => void) { | 857 | function removeFromBlacklist (video: VideoInstance) { |
905 | // Find the blacklisted video | 858 | // Find the blacklisted video |
906 | db.BlacklistedVideo.loadByVideoId(video.id, function (err, video) { | 859 | return db.BlacklistedVideo.loadByVideoId(video.id).then(video => { |
907 | // If an error occured, stop here | 860 | // Not found the video, skip |
908 | if (err) { | 861 | if (!video) { |
909 | logger.error('Error when fetching video from blacklist.', { error: err }) | 862 | return null |
910 | return callback(err) | ||
911 | } | 863 | } |
912 | 864 | ||
913 | // If we found the video, remove it from the blacklist | 865 | // If we found the video, remove it from the blacklist |
914 | if (video) { | 866 | return video.destroy() |
915 | video.destroy().asCallback(callback) | ||
916 | } else { | ||
917 | // If haven't found it, simply ignore it and do nothing | ||
918 | return callback(null) | ||
919 | } | ||
920 | }) | 867 | }) |
921 | } | 868 | } |
diff --git a/server/tests/api/friends-advanced.js b/server/tests/api/friends-advanced.js index 237cb533d..917583a42 100644 --- a/server/tests/api/friends-advanced.js +++ b/server/tests/api/friends-advanced.js | |||
@@ -171,6 +171,23 @@ describe('Test advanced friends', function () { | |||
171 | function (next) { | 171 | function (next) { |
172 | setTimeout(next, 22000) | 172 | setTimeout(next, 22000) |
173 | }, | 173 | }, |
174 | // Check the pods 1, 2 expulsed pod 4 | ||
175 | function (next) { | ||
176 | each([ 1, 2 ], function (i, callback) { | ||
177 | getFriendsList(i, function (err, res) { | ||
178 | if (err) throw err | ||
179 | |||
180 | // Pod 4 should not be our friend | ||
181 | const result = res.body.data | ||
182 | expect(result.length).to.equal(2) | ||
183 | for (const pod of result) { | ||
184 | expect(pod.host).not.equal(servers[3].host) | ||
185 | } | ||
186 | |||
187 | callback() | ||
188 | }) | ||
189 | }, next) | ||
190 | }, | ||
174 | // Rerun server 4 | 191 | // Rerun server 4 |
175 | function (next) { | 192 | function (next) { |
176 | serversUtils.runServer(4, function (server) { | 193 | serversUtils.runServer(4, function (server) { |
@@ -187,7 +204,7 @@ describe('Test advanced friends', function () { | |||
187 | next() | 204 | next() |
188 | }) | 205 | }) |
189 | }, | 206 | }, |
190 | // Pod 6 ask pod 1, 2 and 3 | 207 | // Pod 6 asks pod 1, 2 and 3 |
191 | function (next) { | 208 | function (next) { |
192 | makeFriends(6, next) | 209 | makeFriends(6, next) |
193 | }, | 210 | }, |
diff --git a/shared/models/index.ts b/shared/models/index.ts index 95d95ab47..cd885af89 100644 --- a/shared/models/index.ts +++ b/shared/models/index.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | export * from './job.model' | 1 | export * from './job.model' |
2 | export * from './oauth-client-local.model' | 2 | export * from './oauth-client-local.model' |
3 | export * from './pod.model' | 3 | export * from './pod.model' |
4 | export * from './result-list.model' | ||
4 | export * from './request-scheduler.model' | 5 | export * from './request-scheduler.model' |
5 | export * from './user-video-rate.model' | 6 | export * from './user-video-rate.model' |
6 | export * from './user.model' | 7 | export * from './user.model' |
diff --git a/shared/models/result-list.model.ts b/shared/models/result-list.model.ts new file mode 100644 index 000000000..2d5147a86 --- /dev/null +++ b/shared/models/result-list.model.ts | |||
@@ -0,0 +1,4 @@ | |||
1 | export interface ResultList<T> { | ||
2 | total: number | ||
3 | data: T[] | ||
4 | } | ||
@@ -84,9 +84,9 @@ | |||
84 | dependencies: | 84 | dependencies: |
85 | "@types/express" "*" | 85 | "@types/express" "*" |
86 | 86 | ||
87 | "@types/node@*", "@types/node@^7.0.18": | 87 | "@types/node@*", "@types/node@^8.0.3": |
88 | version "7.0.32" | 88 | version "8.0.3" |
89 | resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.32.tgz#6afe6c66520a4c316623a14aef123908d01b4bba" | 89 | resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.3.tgz#fca61c26f83e5f453166114f57d53a47feb36d45" |
90 | 90 | ||
91 | "@types/request@^0.0.44": | 91 | "@types/request@^0.0.44": |
92 | version "0.0.44" | 92 | version "0.0.44" |
@@ -453,7 +453,7 @@ bluebird@^2.10.0, bluebird@^2.9.13: | |||
453 | version "2.11.0" | 453 | version "2.11.0" |
454 | resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1" | 454 | resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1" |
455 | 455 | ||
456 | bluebird@^3.0.5, bluebird@^3.4.0, bluebird@^3.4.6: | 456 | bluebird@^3.0.5, bluebird@^3.4.0, bluebird@^3.4.6, bluebird@^3.5.0: |
457 | version "3.5.0" | 457 | version "3.5.0" |
458 | resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c" | 458 | resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c" |
459 | 459 | ||
@@ -3909,9 +3909,9 @@ typedarray@^0.0.6: | |||
3909 | version "0.0.6" | 3909 | version "0.0.6" |
3910 | resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" | 3910 | resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" |
3911 | 3911 | ||
3912 | typescript@^2.3.4: | 3912 | typescript@^2.4.1: |
3913 | version "2.4.0" | 3913 | version "2.4.1" |
3914 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.4.0.tgz#aef5a8d404beba36ad339abf079ddddfffba86dd" | 3914 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.4.1.tgz#c3ccb16ddaa0b2314de031e7e6fee89e5ba346bc" |
3915 | 3915 | ||
3916 | uid-number@^0.0.6, uid-number@~0.0.6: | 3916 | uid-number@^0.0.6, uid-number@~0.0.6: |
3917 | version "0.0.6" | 3917 | version "0.0.6" |