aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/models
diff options
context:
space:
mode:
Diffstat (limited to 'server/models')
-rw-r--r--server/models/application.js45
-rw-r--r--server/models/author.js28
-rw-r--r--server/models/oauth-client.js72
-rw-r--r--server/models/oauth-token.js109
-rw-r--r--server/models/pods.js156
-rw-r--r--server/models/request.js187
-rw-r--r--server/models/requestToPod.js30
-rw-r--r--server/models/user.js132
-rw-r--r--server/models/utils.js31
-rw-r--r--server/models/video.js388
10 files changed, 773 insertions, 405 deletions
diff --git a/server/models/application.js b/server/models/application.js
index 452ac4283..ec1d7b122 100644
--- a/server/models/application.js
+++ b/server/models/application.js
@@ -1,31 +1,36 @@
1const mongoose = require('mongoose') 1module.exports = function (sequelize, DataTypes) {
2 const Application = sequelize.define('Application',
3 {
4 sqlSchemaVersion: {
5 type: DataTypes.INTEGER,
6 defaultValue: 0
7 }
8 },
9 {
10 classMethods: {
11 loadSqlSchemaVersion,
12 updateSqlSchemaVersion
13 }
14 }
15 )
16
17 return Application
18}
2 19
3// --------------------------------------------------------------------------- 20// ---------------------------------------------------------------------------
4 21
5const ApplicationSchema = mongoose.Schema({ 22function loadSqlSchemaVersion (callback) {
6 mongoSchemaVersion: { 23 const query = {
7 type: Number, 24 attributes: [ 'sqlSchemaVersion' ]
8 default: 0
9 } 25 }
10})
11
12ApplicationSchema.statics = {
13 loadMongoSchemaVersion,
14 updateMongoSchemaVersion
15}
16
17mongoose.model('Application', ApplicationSchema)
18
19// ---------------------------------------------------------------------------
20 26
21function loadMongoSchemaVersion (callback) { 27 return this.findOne(query).asCallback(function (err, data) {
22 return this.findOne({}, { mongoSchemaVersion: 1 }, function (err, data) { 28 const version = data ? data.sqlSchemaVersion : 0
23 const version = data ? data.mongoSchemaVersion : 0
24 29
25 return callback(err, version) 30 return callback(err, version)
26 }) 31 })
27} 32}
28 33
29function updateMongoSchemaVersion (newVersion, callback) { 34function updateSqlSchemaVersion (newVersion, callback) {
30 return this.update({}, { mongoSchemaVersion: newVersion }, callback) 35 return this.update({ sqlSchemaVersion: newVersion }).asCallback(callback)
31} 36}
diff --git a/server/models/author.js b/server/models/author.js
new file mode 100644
index 000000000..493c2ca11
--- /dev/null
+++ b/server/models/author.js
@@ -0,0 +1,28 @@
1module.exports = function (sequelize, DataTypes) {
2 const Author = sequelize.define('Author',
3 {
4 name: {
5 type: DataTypes.STRING
6 }
7 },
8 {
9 classMethods: {
10 associate
11 }
12 }
13 )
14
15 return Author
16}
17
18// ---------------------------------------------------------------------------
19
20function associate (models) {
21 this.belongsTo(models.Pod, {
22 foreignKey: {
23 name: 'podId',
24 allowNull: true
25 },
26 onDelete: 'cascade'
27 })
28}
diff --git a/server/models/oauth-client.js b/server/models/oauth-client.js
index a1aefa985..15118591a 100644
--- a/server/models/oauth-client.js
+++ b/server/models/oauth-client.js
@@ -1,33 +1,63 @@
1const mongoose = require('mongoose') 1module.exports = function (sequelize, DataTypes) {
2 2 const OAuthClient = sequelize.define('OAuthClient',
3// --------------------------------------------------------------------------- 3 {
4 4 clientId: {
5const OAuthClientSchema = mongoose.Schema({ 5 type: DataTypes.STRING
6 clientSecret: String, 6 },
7 grants: Array, 7 clientSecret: {
8 redirectUris: Array 8 type: DataTypes.STRING
9}) 9 },
10 10 grants: {
11OAuthClientSchema.path('clientSecret').required(true) 11 type: DataTypes.ARRAY(DataTypes.STRING)
12 12 },
13OAuthClientSchema.statics = { 13 redirectUris: {
14 getByIdAndSecret, 14 type: DataTypes.ARRAY(DataTypes.STRING)
15 list, 15 }
16 loadFirstClient 16 },
17 {
18 classMethods: {
19 associate,
20
21 getByIdAndSecret,
22 list,
23 loadFirstClient
24 }
25 }
26 )
27
28 return OAuthClient
17} 29}
18 30
19mongoose.model('OAuthClient', OAuthClientSchema) 31// TODO: validation
32// OAuthClientSchema.path('clientSecret').required(true)
20 33
21// --------------------------------------------------------------------------- 34// ---------------------------------------------------------------------------
22 35
36function associate (models) {
37 this.hasMany(models.OAuthToken, {
38 foreignKey: {
39 name: 'oAuthClientId',
40 allowNull: false
41 },
42 onDelete: 'cascade'
43 })
44}
45
23function list (callback) { 46function list (callback) {
24 return this.find(callback) 47 return this.findAll().asCallback(callback)
25} 48}
26 49
27function loadFirstClient (callback) { 50function loadFirstClient (callback) {
28 return this.findOne({}, callback) 51 return this.findOne().asCallback(callback)
29} 52}
30 53
31function getByIdAndSecret (id, clientSecret) { 54function getByIdAndSecret (clientId, clientSecret) {
32 return this.findOne({ _id: id, clientSecret: clientSecret }).exec() 55 const query = {
56 where: {
57 clientId: clientId,
58 clientSecret: clientSecret
59 }
60 }
61
62 return this.findOne(query)
33} 63}
diff --git a/server/models/oauth-token.js b/server/models/oauth-token.js
index aff73bfb1..c9108bf95 100644
--- a/server/models/oauth-token.js
+++ b/server/models/oauth-token.js
@@ -1,42 +1,71 @@
1const mongoose = require('mongoose')
2
3const logger = require('../helpers/logger') 1const logger = require('../helpers/logger')
4 2
5// --------------------------------------------------------------------------- 3// ---------------------------------------------------------------------------
6 4
7const OAuthTokenSchema = mongoose.Schema({ 5module.exports = function (sequelize, DataTypes) {
8 accessToken: String, 6 const OAuthToken = sequelize.define('OAuthToken',
9 accessTokenExpiresAt: Date, 7 {
10 client: { type: mongoose.Schema.Types.ObjectId, ref: 'OAuthClient' }, 8 accessToken: {
11 refreshToken: String, 9 type: DataTypes.STRING
12 refreshTokenExpiresAt: Date, 10 },
13 user: { type: mongoose.Schema.Types.ObjectId, ref: 'User' } 11 accessTokenExpiresAt: {
14}) 12 type: DataTypes.DATE
15 13 },
16OAuthTokenSchema.path('accessToken').required(true) 14 refreshToken: {
17OAuthTokenSchema.path('client').required(true) 15 type: DataTypes.STRING
18OAuthTokenSchema.path('user').required(true) 16 },
19 17 refreshTokenExpiresAt: {
20OAuthTokenSchema.statics = { 18 type: DataTypes.DATE
21 getByRefreshTokenAndPopulateClient, 19 }
22 getByTokenAndPopulateUser, 20 },
23 getByRefreshTokenAndPopulateUser, 21 {
24 removeByUserId 22 classMethods: {
23 associate,
24
25 getByRefreshTokenAndPopulateClient,
26 getByTokenAndPopulateUser,
27 getByRefreshTokenAndPopulateUser,
28 removeByUserId
29 }
30 }
31 )
32
33 return OAuthToken
25} 34}
26 35
27mongoose.model('OAuthToken', OAuthTokenSchema) 36// TODO: validation
37// OAuthTokenSchema.path('accessToken').required(true)
38// OAuthTokenSchema.path('client').required(true)
39// OAuthTokenSchema.path('user').required(true)
28 40
29// --------------------------------------------------------------------------- 41// ---------------------------------------------------------------------------
30 42
43function associate (models) {
44 this.belongsTo(models.User, {
45 foreignKey: {
46 name: 'userId',
47 allowNull: false
48 },
49 onDelete: 'cascade'
50 })
51}
52
31function getByRefreshTokenAndPopulateClient (refreshToken) { 53function getByRefreshTokenAndPopulateClient (refreshToken) {
32 return this.findOne({ refreshToken: refreshToken }).populate('client').exec().then(function (token) { 54 const query = {
55 where: {
56 refreshToken: refreshToken
57 },
58 include: [ this.associations.OAuthClient ]
59 }
60
61 return this.findOne(query).then(function (token) {
33 if (!token) return token 62 if (!token) return token
34 63
35 const tokenInfos = { 64 const tokenInfos = {
36 refreshToken: token.refreshToken, 65 refreshToken: token.refreshToken,
37 refreshTokenExpiresAt: token.refreshTokenExpiresAt, 66 refreshTokenExpiresAt: token.refreshTokenExpiresAt,
38 client: { 67 client: {
39 id: token.client._id.toString() 68 id: token.client.id
40 }, 69 },
41 user: { 70 user: {
42 id: token.user 71 id: token.user
@@ -50,13 +79,41 @@ function getByRefreshTokenAndPopulateClient (refreshToken) {
50} 79}
51 80
52function getByTokenAndPopulateUser (bearerToken) { 81function getByTokenAndPopulateUser (bearerToken) {
53 return this.findOne({ accessToken: bearerToken }).populate('user').exec() 82 const query = {
83 where: {
84 accessToken: bearerToken
85 },
86 include: [ this.sequelize.models.User ]
87 }
88
89 return this.findOne(query).then(function (token) {
90 if (token) token.user = token.User
91
92 return token
93 })
54} 94}
55 95
56function getByRefreshTokenAndPopulateUser (refreshToken) { 96function getByRefreshTokenAndPopulateUser (refreshToken) {
57 return this.findOne({ refreshToken: refreshToken }).populate('user').exec() 97 const query = {
98 where: {
99 refreshToken: refreshToken
100 },
101 include: [ this.sequelize.models.User ]
102 }
103
104 return this.findOne(query).then(function (token) {
105 token.user = token.User
106
107 return token
108 })
58} 109}
59 110
60function removeByUserId (userId, callback) { 111function removeByUserId (userId, callback) {
61 return this.remove({ user: userId }, callback) 112 const query = {
113 where: {
114 userId: userId
115 }
116 }
117
118 return this.destroy(query).asCallback(callback)
62} 119}
diff --git a/server/models/pods.js b/server/models/pods.js
index 49c73472a..2c1f56203 100644
--- a/server/models/pods.js
+++ b/server/models/pods.js
@@ -1,79 +1,62 @@
1'use strict' 1'use strict'
2 2
3const each = require('async/each')
4const mongoose = require('mongoose')
5const map = require('lodash/map') 3const map = require('lodash/map')
6const validator = require('express-validator').validator
7 4
8const constants = require('../initializers/constants') 5const constants = require('../initializers/constants')
9 6
10const Video = mongoose.model('Video')
11
12// --------------------------------------------------------------------------- 7// ---------------------------------------------------------------------------
13 8
14const PodSchema = mongoose.Schema({ 9module.exports = function (sequelize, DataTypes) {
15 host: String, 10 const Pod = sequelize.define('Pod',
16 publicKey: String, 11 {
17 score: { type: Number, max: constants.FRIEND_SCORE.MAX }, 12 host: {
18 createdDate: { 13 type: DataTypes.STRING
19 type: Date, 14 },
20 default: Date.now 15 publicKey: {
21 } 16 type: DataTypes.STRING(5000)
22}) 17 },
23 18 score: {
24PodSchema.path('host').validate(validator.isURL) 19 type: DataTypes.INTEGER,
25PodSchema.path('publicKey').required(true) 20 defaultValue: constants.FRIEND_SCORE.BASE
26PodSchema.path('score').validate(function (value) { return !isNaN(value) }) 21 }
27 22 // Check createdAt
28PodSchema.methods = { 23 },
29 toFormatedJSON 24 {
25 classMethods: {
26 associate,
27
28 countAll,
29 incrementScores,
30 list,
31 listAllIds,
32 listBadPods,
33 load,
34 loadByHost,
35 removeAll
36 },
37 instanceMethods: {
38 toFormatedJSON
39 }
40 }
41 )
42
43 return Pod
30} 44}
31 45
32PodSchema.statics = { 46// TODO: max score -> constants.FRIENDS_SCORE.MAX
33 countAll, 47// TODO: validation
34 incrementScores, 48// PodSchema.path('host').validate(validator.isURL)
35 list, 49// PodSchema.path('publicKey').required(true)
36 listAllIds, 50// PodSchema.path('score').validate(function (value) { return !isNaN(value) })
37 listBadPods,
38 load,
39 loadByHost,
40 removeAll
41}
42
43PodSchema.pre('save', function (next) {
44 const self = this
45
46 Pod.loadByHost(this.host, function (err, pod) {
47 if (err) return next(err)
48
49 if (pod) return next(new Error('Pod already exists.'))
50
51 self.score = constants.FRIEND_SCORE.BASE
52 return next()
53 })
54})
55
56PodSchema.pre('remove', function (next) {
57 // Remove the videos owned by this pod too
58 Video.listByHost(this.host, function (err, videos) {
59 if (err) return next(err)
60
61 each(videos, function (video, callbackEach) {
62 video.remove(callbackEach)
63 }, next)
64 })
65})
66
67const Pod = mongoose.model('Pod', PodSchema)
68 51
69// ------------------------------ METHODS ------------------------------ 52// ------------------------------ METHODS ------------------------------
70 53
71function toFormatedJSON () { 54function toFormatedJSON () {
72 const json = { 55 const json = {
73 id: this._id, 56 id: this.id,
74 host: this.host, 57 host: this.host,
75 score: this.score, 58 score: this.score,
76 createdDate: this.createdDate 59 createdAt: this.createdAt
77 } 60 }
78 61
79 return json 62 return json
@@ -81,39 +64,76 @@ function toFormatedJSON () {
81 64
82// ------------------------------ Statics ------------------------------ 65// ------------------------------ Statics ------------------------------
83 66
67function associate (models) {
68 this.belongsToMany(models.Request, {
69 foreignKey: 'podId',
70 through: models.RequestToPod,
71 onDelete: 'CASCADE'
72 })
73}
74
84function countAll (callback) { 75function countAll (callback) {
85 return this.count(callback) 76 return this.count().asCallback(callback)
86} 77}
87 78
88function incrementScores (ids, value, callback) { 79function incrementScores (ids, value, callback) {
89 if (!callback) callback = function () {} 80 if (!callback) callback = function () {}
90 return this.update({ _id: { $in: ids } }, { $inc: { score: value } }, { multi: true }, callback) 81
82 const update = {
83 score: this.sequelize.literal('score +' + value)
84 }
85
86 const query = {
87 where: {
88 id: {
89 $in: ids
90 }
91 }
92 }
93
94 return this.update(update, query).asCallback(callback)
91} 95}
92 96
93function list (callback) { 97function list (callback) {
94 return this.find(callback) 98 return this.findAll().asCallback(callback)
95} 99}
96 100
97function listAllIds (callback) { 101function listAllIds (callback) {
98 return this.find({}, { _id: 1 }, function (err, pods) { 102 const query = {
103 attributes: [ 'id' ]
104 }
105
106 return this.findAll(query).asCallback(function (err, pods) {
99 if (err) return callback(err) 107 if (err) return callback(err)
100 108
101 return callback(null, map(pods, '_id')) 109 return callback(null, map(pods, 'id'))
102 }) 110 })
103} 111}
104 112
105function listBadPods (callback) { 113function listBadPods (callback) {
106 return this.find({ score: 0 }, callback) 114 const query = {
115 where: {
116 score: { $lte: 0 }
117 }
118 }
119
120 return this.findAll(query).asCallback(callback)
107} 121}
108 122
109function load (id, callback) { 123function load (id, callback) {
110 return this.findById(id, callback) 124 return this.findById(id).asCallback(callback)
111} 125}
112 126
113function loadByHost (host, callback) { 127function loadByHost (host, callback) {
114 return this.findOne({ host }, callback) 128 const query = {
129 where: {
130 host: host
131 }
132 }
133
134 return this.findOne(query).asCallback(callback)
115} 135}
116 136
117function removeAll (callback) { 137function removeAll (callback) {
118 return this.remove({}, callback) 138 return this.destroy().asCallback(callback)
119} 139}
diff --git a/server/models/request.js b/server/models/request.js
index c2cfe83ce..882f747b7 100644
--- a/server/models/request.js
+++ b/server/models/request.js
@@ -2,66 +2,58 @@
2 2
3const each = require('async/each') 3const each = require('async/each')
4const eachLimit = require('async/eachLimit') 4const eachLimit = require('async/eachLimit')
5const values = require('lodash/values')
6const mongoose = require('mongoose')
7const waterfall = require('async/waterfall') 5const waterfall = require('async/waterfall')
8 6
9const constants = require('../initializers/constants') 7const constants = require('../initializers/constants')
10const logger = require('../helpers/logger') 8const logger = require('../helpers/logger')
11const requests = require('../helpers/requests') 9const requests = require('../helpers/requests')
12 10
13const Pod = mongoose.model('Pod')
14
15let timer = null 11let timer = null
16let lastRequestTimestamp = 0 12let lastRequestTimestamp = 0
17 13
18// --------------------------------------------------------------------------- 14// ---------------------------------------------------------------------------
19 15
20const RequestSchema = mongoose.Schema({ 16module.exports = function (sequelize, DataTypes) {
21 request: mongoose.Schema.Types.Mixed, 17 const Request = sequelize.define('Request',
22 endpoint: { 18 {
23 type: String, 19 request: {
24 enum: [ values(constants.REQUEST_ENDPOINTS) ] 20 type: DataTypes.JSON
25 }, 21 },
26 to: [ 22 endpoint: {
23 // TODO: enum?
24 type: DataTypes.STRING
25 }
26 },
27 { 27 {
28 type: mongoose.Schema.Types.ObjectId, 28 classMethods: {
29 ref: 'Pod' 29 associate,
30
31 activate,
32 countTotalRequests,
33 deactivate,
34 flush,
35 forceSend,
36 remainingMilliSeconds
37 }
30 } 38 }
31 ] 39 )
32})
33
34RequestSchema.statics = {
35 activate,
36 deactivate,
37 flush,
38 forceSend,
39 list,
40 remainingMilliSeconds
41}
42
43RequestSchema.pre('save', function (next) {
44 const self = this
45
46 if (self.to.length === 0) {
47 Pod.listAllIds(function (err, podIds) {
48 if (err) return next(err)
49
50 // No friends
51 if (podIds.length === 0) return
52
53 self.to = podIds
54 return next()
55 })
56 } else {
57 return next()
58 }
59})
60 40
61mongoose.model('Request', RequestSchema) 41 return Request
42}
62 43
63// ------------------------------ STATICS ------------------------------ 44// ------------------------------ STATICS ------------------------------
64 45
46function associate (models) {
47 this.belongsToMany(models.Pod, {
48 foreignKey: {
49 name: 'requestId',
50 allowNull: false
51 },
52 through: models.RequestToPod,
53 onDelete: 'CASCADE'
54 })
55}
56
65function activate () { 57function activate () {
66 logger.info('Requests scheduler activated.') 58 logger.info('Requests scheduler activated.')
67 lastRequestTimestamp = Date.now() 59 lastRequestTimestamp = Date.now()
@@ -73,6 +65,14 @@ function activate () {
73 }, constants.REQUESTS_INTERVAL) 65 }, constants.REQUESTS_INTERVAL)
74} 66}
75 67
68function countTotalRequests (callback) {
69 const query = {
70 include: [ this.sequelize.models.Pod ]
71 }
72
73 return this.count(query).asCallback(callback)
74}
75
76function deactivate () { 76function deactivate () {
77 logger.info('Requests scheduler deactivated.') 77 logger.info('Requests scheduler deactivated.')
78 clearInterval(timer) 78 clearInterval(timer)
@@ -90,10 +90,6 @@ function forceSend () {
90 makeRequests.call(this) 90 makeRequests.call(this)
91} 91}
92 92
93function list (callback) {
94 this.find({ }, callback)
95}
96
97function remainingMilliSeconds () { 93function remainingMilliSeconds () {
98 if (timer === null) return -1 94 if (timer === null) return -1
99 95
@@ -136,6 +132,7 @@ function makeRequest (toPod, requestEndpoint, requestsToMake, callback) {
136// Make all the requests of the scheduler 132// Make all the requests of the scheduler
137function makeRequests () { 133function makeRequests () {
138 const self = this 134 const self = this
135 const RequestToPod = this.sequelize.models.RequestToPod
139 136
140 // We limit the size of the requests (REQUESTS_LIMIT) 137 // We limit the size of the requests (REQUESTS_LIMIT)
141 // We don't want to stuck with the same failing requests so we get a random list 138 // We don't want to stuck with the same failing requests so we get a random list
@@ -156,20 +153,20 @@ function makeRequests () {
156 // We want to group requests by destinations pod and endpoint 153 // We want to group requests by destinations pod and endpoint
157 const requestsToMakeGrouped = {} 154 const requestsToMakeGrouped = {}
158 155
159 requests.forEach(function (poolRequest) { 156 requests.forEach(function (request) {
160 poolRequest.to.forEach(function (toPodId) { 157 request.Pods.forEach(function (toPod) {
161 const hashKey = toPodId + poolRequest.endpoint 158 const hashKey = toPod.id + request.endpoint
162 if (!requestsToMakeGrouped[hashKey]) { 159 if (!requestsToMakeGrouped[hashKey]) {
163 requestsToMakeGrouped[hashKey] = { 160 requestsToMakeGrouped[hashKey] = {
164 toPodId, 161 toPodId: toPod.id,
165 endpoint: poolRequest.endpoint, 162 endpoint: request.endpoint,
166 ids: [], // pool request ids, to delete them from the DB in the future 163 ids: [], // request ids, to delete them from the DB in the future
167 datas: [] // requests data, 164 datas: [] // requests data,
168 } 165 }
169 } 166 }
170 167
171 requestsToMakeGrouped[hashKey].ids.push(poolRequest._id) 168 requestsToMakeGrouped[hashKey].ids.push(request.id)
172 requestsToMakeGrouped[hashKey].datas.push(poolRequest.request) 169 requestsToMakeGrouped[hashKey].datas.push(request.request)
173 }) 170 })
174 }) 171 })
175 172
@@ -179,8 +176,8 @@ function makeRequests () {
179 eachLimit(Object.keys(requestsToMakeGrouped), constants.REQUESTS_IN_PARALLEL, function (hashKey, callbackEach) { 176 eachLimit(Object.keys(requestsToMakeGrouped), constants.REQUESTS_IN_PARALLEL, function (hashKey, callbackEach) {
180 const requestToMake = requestsToMakeGrouped[hashKey] 177 const requestToMake = requestsToMakeGrouped[hashKey]
181 178
182 // FIXME: mongodb request inside a loop :/ 179 // FIXME: SQL request inside a loop :/
183 Pod.load(requestToMake.toPodId, function (err, toPod) { 180 self.sequelize.models.Pod.load(requestToMake.toPodId, function (err, toPod) {
184 if (err) { 181 if (err) {
185 logger.error('Error finding pod by id.', { err: err }) 182 logger.error('Error finding pod by id.', { err: err })
186 return callbackEach() 183 return callbackEach()
@@ -191,7 +188,7 @@ function makeRequests () {
191 const requestIdsToDelete = requestToMake.ids 188 const requestIdsToDelete = requestToMake.ids
192 189
193 logger.info('Removing %d requests of unexisting pod %s.', requestIdsToDelete.length, requestToMake.toPodId) 190 logger.info('Removing %d requests of unexisting pod %s.', requestIdsToDelete.length, requestToMake.toPodId)
194 removePodOf.call(self, requestIdsToDelete, requestToMake.toPodId) 191 RequestToPod.removePodOf.call(self, requestIdsToDelete, requestToMake.toPodId)
195 return callbackEach() 192 return callbackEach()
196 } 193 }
197 194
@@ -202,7 +199,7 @@ function makeRequests () {
202 goodPods.push(requestToMake.toPodId) 199 goodPods.push(requestToMake.toPodId)
203 200
204 // Remove the pod id of these request ids 201 // Remove the pod id of these request ids
205 removePodOf.call(self, requestToMake.ids, requestToMake.toPodId, callbackEach) 202 RequestToPod.removePodOf(requestToMake.ids, requestToMake.toPodId, callbackEach)
206 } else { 203 } else {
207 badPods.push(requestToMake.toPodId) 204 badPods.push(requestToMake.toPodId)
208 callbackEach() 205 callbackEach()
@@ -211,18 +208,22 @@ function makeRequests () {
211 }) 208 })
212 }, function () { 209 }, function () {
213 // All the requests were made, we update the pods score 210 // All the requests were made, we update the pods score
214 updatePodsScore(goodPods, badPods) 211 updatePodsScore.call(self, goodPods, badPods)
215 // Flush requests with no pod 212 // Flush requests with no pod
216 removeWithEmptyTo.call(self) 213 removeWithEmptyTo.call(self, function (err) {
214 if (err) logger.error('Error when removing requests with no pods.', { error: err })
215 })
217 }) 216 })
218 }) 217 })
219} 218}
220 219
221// Remove pods with a score of 0 (too many requests where they were unreachable) 220// Remove pods with a score of 0 (too many requests where they were unreachable)
222function removeBadPods () { 221function removeBadPods () {
222 const self = this
223
223 waterfall([ 224 waterfall([
224 function findBadPods (callback) { 225 function findBadPods (callback) {
225 Pod.listBadPods(function (err, pods) { 226 self.sequelize.models.Pod.listBadPods(function (err, pods) {
226 if (err) { 227 if (err) {
227 logger.error('Cannot find bad pods.', { error: err }) 228 logger.error('Cannot find bad pods.', { error: err })
228 return callback(err) 229 return callback(err)
@@ -233,10 +234,8 @@ function removeBadPods () {
233 }, 234 },
234 235
235 function removeTheseBadPods (pods, callback) { 236 function removeTheseBadPods (pods, callback) {
236 if (pods.length === 0) return callback(null, 0)
237
238 each(pods, function (pod, callbackEach) { 237 each(pods, function (pod, callbackEach) {
239 pod.remove(callbackEach) 238 pod.destroy().asCallback(callbackEach)
240 }, function (err) { 239 }, function (err) {
241 return callback(err, pods.length) 240 return callback(err, pods.length)
242 }) 241 })
@@ -253,43 +252,67 @@ function removeBadPods () {
253} 252}
254 253
255function updatePodsScore (goodPods, badPods) { 254function updatePodsScore (goodPods, badPods) {
255 const self = this
256 const Pod = this.sequelize.models.Pod
257
256 logger.info('Updating %d good pods and %d bad pods scores.', goodPods.length, badPods.length) 258 logger.info('Updating %d good pods and %d bad pods scores.', goodPods.length, badPods.length)
257 259
258 Pod.incrementScores(goodPods, constants.PODS_SCORE.BONUS, function (err) { 260 if (goodPods.length !== 0) {
259 if (err) logger.error('Cannot increment scores of good pods.') 261 Pod.incrementScores(goodPods, constants.PODS_SCORE.BONUS, function (err) {
260 }) 262 if (err) logger.error('Cannot increment scores of good pods.')
263 })
264 }
261 265
262 Pod.incrementScores(badPods, constants.PODS_SCORE.MALUS, function (err) { 266 if (badPods.length !== 0) {
263 if (err) logger.error('Cannot decrement scores of bad pods.') 267 Pod.incrementScores(badPods, constants.PODS_SCORE.MALUS, function (err) {
264 removeBadPods() 268 if (err) logger.error('Cannot decrement scores of bad pods.')
265 }) 269 removeBadPods.call(self)
270 })
271 }
266} 272}
267 273
268function listWithLimitAndRandom (limit, callback) { 274function listWithLimitAndRandom (limit, callback) {
269 const self = this 275 const self = this
270 276
271 self.count(function (err, count) { 277 self.count().asCallback(function (err, count) {
272 if (err) return callback(err) 278 if (err) return callback(err)
273 279
280 // Optimization...
281 if (count === 0) return callback(null, [])
282
274 let start = Math.floor(Math.random() * count) - limit 283 let start = Math.floor(Math.random() * count) - limit
275 if (start < 0) start = 0 284 if (start < 0) start = 0
276 285
277 self.find().sort({ _id: 1 }).skip(start).limit(limit).exec(callback) 286 const query = {
287 order: [
288 [ 'id', 'ASC' ]
289 ],
290 offset: start,
291 limit: limit,
292 include: [ this.sequelize.models.Pod ]
293 }
294
295 self.findAll(query).asCallback(callback)
278 }) 296 })
279} 297}
280 298
281function removeAll (callback) { 299function removeAll (callback) {
282 this.remove({ }, callback) 300 // Delete all requests
283} 301 this.destroy({ truncate: true }).asCallback(callback)
284
285function removePodOf (requestsIds, podId, callback) {
286 if (!callback) callback = function () {}
287
288 this.update({ _id: { $in: requestsIds } }, { $pull: { to: podId } }, { multi: true }, callback)
289} 302}
290 303
291function removeWithEmptyTo (callback) { 304function removeWithEmptyTo (callback) {
292 if (!callback) callback = function () {} 305 if (!callback) callback = function () {}
293 306
294 this.remove({ to: { $size: 0 } }, callback) 307 const query = {
308 where: {
309 id: {
310 $notIn: [
311 this.sequelize.literal('SELECT "requestId" FROM "RequestToPods"')
312 ]
313 }
314 }
315 }
316
317 this.destroy(query).asCallback(callback)
295} 318}
diff --git a/server/models/requestToPod.js b/server/models/requestToPod.js
new file mode 100644
index 000000000..378c2bdcf
--- /dev/null
+++ b/server/models/requestToPod.js
@@ -0,0 +1,30 @@
1'use strict'
2
3// ---------------------------------------------------------------------------
4
5module.exports = function (sequelize, DataTypes) {
6 const RequestToPod = sequelize.define('RequestToPod', {}, {
7 classMethods: {
8 removePodOf
9 }
10 })
11
12 return RequestToPod
13}
14
15// ---------------------------------------------------------------------------
16
17function removePodOf (requestsIds, podId, callback) {
18 if (!callback) callback = function () {}
19
20 const query = {
21 where: {
22 requestId: {
23 $in: requestsIds
24 },
25 podId: podId
26 }
27 }
28
29 this.destroy(query).asCallback(callback)
30}
diff --git a/server/models/user.js b/server/models/user.js
index a19de7072..e50eb96ea 100644
--- a/server/models/user.js
+++ b/server/models/user.js
@@ -1,60 +1,60 @@
1const mongoose = require('mongoose')
2
3const customUsersValidators = require('../helpers/custom-validators').users
4const modelUtils = require('./utils') 1const modelUtils = require('./utils')
5const peertubeCrypto = require('../helpers/peertube-crypto') 2const peertubeCrypto = require('../helpers/peertube-crypto')
6 3
7const OAuthToken = mongoose.model('OAuthToken')
8
9// --------------------------------------------------------------------------- 4// ---------------------------------------------------------------------------
10 5
11const UserSchema = mongoose.Schema({ 6module.exports = function (sequelize, DataTypes) {
12 createdDate: { 7 const User = sequelize.define('User',
13 type: Date, 8 {
14 default: Date.now 9 password: {
15 }, 10 type: DataTypes.STRING
16 password: String, 11 },
17 username: String, 12 username: {
18 role: String 13 type: DataTypes.STRING
19}) 14 },
20 15 role: {
21UserSchema.path('password').required(customUsersValidators.isUserPasswordValid) 16 type: DataTypes.STRING
22UserSchema.path('username').required(customUsersValidators.isUserUsernameValid) 17 }
23UserSchema.path('role').validate(customUsersValidators.isUserRoleValid) 18 },
24 19 {
25UserSchema.methods = { 20 classMethods: {
26 isPasswordMatch, 21 associate,
27 toFormatedJSON 22
23 countTotal,
24 getByUsername,
25 list,
26 listForApi,
27 loadById,
28 loadByUsername
29 },
30 instanceMethods: {
31 isPasswordMatch,
32 toFormatedJSON
33 },
34 hooks: {
35 beforeCreate: beforeCreateOrUpdate,
36 beforeUpdate: beforeCreateOrUpdate
37 }
38 }
39 )
40
41 return User
28} 42}
29 43
30UserSchema.statics = { 44// TODO: Validation
31 countTotal, 45// UserSchema.path('password').required(customUsersValidators.isUserPasswordValid)
32 getByUsername, 46// UserSchema.path('username').required(customUsersValidators.isUserUsernameValid)
33 list, 47// UserSchema.path('role').validate(customUsersValidators.isUserRoleValid)
34 listForApi,
35 loadById,
36 loadByUsername
37}
38 48
39UserSchema.pre('save', function (next) { 49function beforeCreateOrUpdate (user, options, next) {
40 const user = this 50 peertubeCrypto.cryptPassword(user.password, function (err, hash) {
41
42 peertubeCrypto.cryptPassword(this.password, function (err, hash) {
43 if (err) return next(err) 51 if (err) return next(err)
44 52
45 user.password = hash 53 user.password = hash
46 54
47 return next() 55 return next()
48 }) 56 })
49}) 57}
50
51UserSchema.pre('remove', function (next) {
52 const user = this
53
54 OAuthToken.removeByUserId(user._id, next)
55})
56
57mongoose.model('User', UserSchema)
58 58
59// ------------------------------ METHODS ------------------------------ 59// ------------------------------ METHODS ------------------------------
60 60
@@ -64,35 +64,63 @@ function isPasswordMatch (password, callback) {
64 64
65function toFormatedJSON () { 65function toFormatedJSON () {
66 return { 66 return {
67 id: this._id, 67 id: this.id,
68 username: this.username, 68 username: this.username,
69 role: this.role, 69 role: this.role,
70 createdDate: this.createdDate 70 createdAt: this.createdAt
71 } 71 }
72} 72}
73// ------------------------------ STATICS ------------------------------ 73// ------------------------------ STATICS ------------------------------
74 74
75function associate (models) {
76 this.hasMany(models.OAuthToken, {
77 foreignKey: 'userId',
78 onDelete: 'cascade'
79 })
80}
81
75function countTotal (callback) { 82function countTotal (callback) {
76 return this.count(callback) 83 return this.count().asCallback(callback)
77} 84}
78 85
79function getByUsername (username) { 86function getByUsername (username) {
80 return this.findOne({ username: username }) 87 const query = {
88 where: {
89 username: username
90 }
91 }
92
93 return this.findOne(query)
81} 94}
82 95
83function list (callback) { 96function list (callback) {
84 return this.find(callback) 97 return this.find().asCallback(callback)
85} 98}
86 99
87function listForApi (start, count, sort, callback) { 100function listForApi (start, count, sort, callback) {
88 const query = {} 101 const query = {
89 return modelUtils.listForApiWithCount.call(this, query, start, count, sort, callback) 102 offset: start,
103 limit: count,
104 order: [ modelUtils.getSort(sort) ]
105 }
106
107 return this.findAndCountAll(query).asCallback(function (err, result) {
108 if (err) return callback(err)
109
110 return callback(null, result.rows, result.count)
111 })
90} 112}
91 113
92function loadById (id, callback) { 114function loadById (id, callback) {
93 return this.findById(id, callback) 115 return this.findById(id).asCallback(callback)
94} 116}
95 117
96function loadByUsername (username, callback) { 118function loadByUsername (username, callback) {
97 return this.findOne({ username: username }, callback) 119 const query = {
120 where: {
121 username: username
122 }
123 }
124
125 return this.findOne(query).asCallback(callback)
98} 126}
diff --git a/server/models/utils.js b/server/models/utils.js
index e798aabe6..49636b3d8 100644
--- a/server/models/utils.js
+++ b/server/models/utils.js
@@ -1,28 +1,23 @@
1'use strict' 1'use strict'
2 2
3const parallel = require('async/parallel')
4
5const utils = { 3const utils = {
6 listForApiWithCount 4 getSort
7} 5}
8 6
9function listForApiWithCount (query, start, count, sort, callback) { 7// Translate for example "-name" to [ 'name', 'DESC' ]
10 const self = this 8function getSort (value) {
9 let field
10 let direction
11 11
12 parallel([ 12 if (value.substring(0, 1) === '-') {
13 function (asyncCallback) { 13 direction = 'DESC'
14 self.find(query).skip(start).limit(count).sort(sort).exec(asyncCallback) 14 field = value.substring(1)
15 }, 15 } else {
16 function (asyncCallback) { 16 direction = 'ASC'
17 self.count(query, asyncCallback) 17 field = value
18 } 18 }
19 ], function (err, results) {
20 if (err) return callback(err)
21 19
22 const data = results[0] 20 return [ field, direction ]
23 const total = results[1]
24 return callback(null, data, total)
25 })
26} 21}
27 22
28// --------------------------------------------------------------------------- 23// ---------------------------------------------------------------------------
diff --git a/server/models/video.js b/server/models/video.js
index 330067cdf..8ef07c9e6 100644
--- a/server/models/video.js
+++ b/server/models/video.js
@@ -7,102 +7,93 @@ const magnetUtil = require('magnet-uri')
7const parallel = require('async/parallel') 7const parallel = require('async/parallel')
8const parseTorrent = require('parse-torrent') 8const parseTorrent = require('parse-torrent')
9const pathUtils = require('path') 9const pathUtils = require('path')
10const mongoose = require('mongoose')
11 10
12const constants = require('../initializers/constants') 11const constants = require('../initializers/constants')
13const customVideosValidators = require('../helpers/custom-validators').videos
14const logger = require('../helpers/logger') 12const logger = require('../helpers/logger')
15const modelUtils = require('./utils') 13const modelUtils = require('./utils')
16 14
17// --------------------------------------------------------------------------- 15// ---------------------------------------------------------------------------
18 16
17module.exports = function (sequelize, DataTypes) {
19// TODO: add indexes on searchable columns 18// TODO: add indexes on searchable columns
20const VideoSchema = mongoose.Schema({ 19 const Video = sequelize.define('Video',
21 name: String, 20 {
22 extname: { 21 id: {
23 type: String, 22 type: DataTypes.UUID,
24 enum: [ '.mp4', '.webm', '.ogv' ] 23 defaultValue: DataTypes.UUIDV4,
25 }, 24 primaryKey: true
26 remoteId: mongoose.Schema.Types.ObjectId,
27 description: String,
28 magnet: {
29 infoHash: String
30 },
31 podHost: String,
32 author: String,
33 duration: Number,
34 tags: [ String ],
35 createdDate: {
36 type: Date,
37 default: Date.now
38 }
39})
40
41VideoSchema.path('name').validate(customVideosValidators.isVideoNameValid)
42VideoSchema.path('description').validate(customVideosValidators.isVideoDescriptionValid)
43VideoSchema.path('podHost').validate(customVideosValidators.isVideoPodHostValid)
44VideoSchema.path('author').validate(customVideosValidators.isVideoAuthorValid)
45VideoSchema.path('duration').validate(customVideosValidators.isVideoDurationValid)
46VideoSchema.path('tags').validate(customVideosValidators.isVideoTagsValid)
47
48VideoSchema.methods = {
49 generateMagnetUri,
50 getVideoFilename,
51 getThumbnailName,
52 getPreviewName,
53 getTorrentName,
54 isOwned,
55 toFormatedJSON,
56 toRemoteJSON
57}
58
59VideoSchema.statics = {
60 generateThumbnailFromBase64,
61 getDurationFromFile,
62 listForApi,
63 listByHostAndRemoteId,
64 listByHost,
65 listOwned,
66 listOwnedByAuthor,
67 listRemotes,
68 load,
69 search
70}
71
72VideoSchema.pre('remove', function (next) {
73 const video = this
74 const tasks = []
75
76 tasks.push(
77 function (callback) {
78 removeThumbnail(video, callback)
79 }
80 )
81
82 if (video.isOwned()) {
83 tasks.push(
84 function (callback) {
85 removeFile(video, callback)
86 }, 25 },
87 function (callback) { 26 name: {
88 removeTorrent(video, callback) 27 type: DataTypes.STRING
89 }, 28 },
90 function (callback) { 29 extname: {
91 removePreview(video, callback) 30 // TODO: enum?
31 type: DataTypes.STRING
32 },
33 remoteId: {
34 type: DataTypes.UUID
35 },
36 description: {
37 type: DataTypes.STRING
38 },
39 infoHash: {
40 type: DataTypes.STRING
41 },
42 duration: {
43 type: DataTypes.INTEGER
44 },
45 tags: {
46 type: DataTypes.ARRAY(DataTypes.STRING)
92 } 47 }
93 ) 48 },
94 } 49 {
50 classMethods: {
51 associate,
52
53 generateThumbnailFromBase64,
54 getDurationFromFile,
55 listForApi,
56 listByHostAndRemoteId,
57 listOwnedAndPopulateAuthor,
58 listOwnedByAuthor,
59 load,
60 loadAndPopulateAuthor,
61 loadAndPopulateAuthorAndPod,
62 searchAndPopulateAuthorAndPod
63 },
64 instanceMethods: {
65 generateMagnetUri,
66 getVideoFilename,
67 getThumbnailName,
68 getPreviewName,
69 getTorrentName,
70 isOwned,
71 toFormatedJSON,
72 toRemoteJSON
73 },
74 hooks: {
75 beforeCreate,
76 afterDestroy
77 }
78 }
79 )
95 80
96 parallel(tasks, next) 81 return Video
97}) 82}
98 83
99VideoSchema.pre('save', function (next) { 84// TODO: Validation
100 const video = this 85// VideoSchema.path('name').validate(customVideosValidators.isVideoNameValid)
86// VideoSchema.path('description').validate(customVideosValidators.isVideoDescriptionValid)
87// VideoSchema.path('podHost').validate(customVideosValidators.isVideoPodHostValid)
88// VideoSchema.path('author').validate(customVideosValidators.isVideoAuthorValid)
89// VideoSchema.path('duration').validate(customVideosValidators.isVideoDurationValid)
90// VideoSchema.path('tags').validate(customVideosValidators.isVideoTagsValid)
91
92function beforeCreate (video, options, next) {
101 const tasks = [] 93 const tasks = []
102 94
103 if (video.isOwned()) { 95 if (video.isOwned()) {
104 const videoPath = pathUtils.join(constants.CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename()) 96 const videoPath = pathUtils.join(constants.CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename())
105 this.podHost = constants.CONFIG.WEBSERVER.HOST
106 97
107 tasks.push( 98 tasks.push(
108 // TODO: refractoring 99 // TODO: refractoring
@@ -123,7 +114,7 @@ VideoSchema.pre('save', function (next) {
123 if (err) return callback(err) 114 if (err) return callback(err)
124 115
125 const parsedTorrent = parseTorrent(torrent) 116 const parsedTorrent = parseTorrent(torrent)
126 video.magnet.infoHash = parsedTorrent.infoHash 117 video.infoHash = parsedTorrent.infoHash
127 118
128 callback(null) 119 callback(null)
129 }) 120 })
@@ -141,12 +132,46 @@ VideoSchema.pre('save', function (next) {
141 } 132 }
142 133
143 return next() 134 return next()
144}) 135}
136
137function afterDestroy (video, options, next) {
138 const tasks = []
145 139
146mongoose.model('Video', VideoSchema) 140 tasks.push(
141 function (callback) {
142 removeThumbnail(video, callback)
143 }
144 )
145
146 if (video.isOwned()) {
147 tasks.push(
148 function (callback) {
149 removeFile(video, callback)
150 },
151 function (callback) {
152 removeTorrent(video, callback)
153 },
154 function (callback) {
155 removePreview(video, callback)
156 }
157 )
158 }
159
160 parallel(tasks, next)
161}
147 162
148// ------------------------------ METHODS ------------------------------ 163// ------------------------------ METHODS ------------------------------
149 164
165function associate (models) {
166 this.belongsTo(models.Author, {
167 foreignKey: {
168 name: 'authorId',
169 allowNull: false
170 },
171 onDelete: 'cascade'
172 })
173}
174
150function generateMagnetUri () { 175function generateMagnetUri () {
151 let baseUrlHttp, baseUrlWs 176 let baseUrlHttp, baseUrlWs
152 177
@@ -154,8 +179,8 @@ function generateMagnetUri () {
154 baseUrlHttp = constants.CONFIG.WEBSERVER.URL 179 baseUrlHttp = constants.CONFIG.WEBSERVER.URL
155 baseUrlWs = constants.CONFIG.WEBSERVER.WS + '://' + constants.CONFIG.WEBSERVER.HOSTNAME + ':' + constants.CONFIG.WEBSERVER.PORT 180 baseUrlWs = constants.CONFIG.WEBSERVER.WS + '://' + constants.CONFIG.WEBSERVER.HOSTNAME + ':' + constants.CONFIG.WEBSERVER.PORT
156 } else { 181 } else {
157 baseUrlHttp = constants.REMOTE_SCHEME.HTTP + '://' + this.podHost 182 baseUrlHttp = constants.REMOTE_SCHEME.HTTP + '://' + this.Author.Pod.host
158 baseUrlWs = constants.REMOTE_SCHEME.WS + '://' + this.podHost 183 baseUrlWs = constants.REMOTE_SCHEME.WS + '://' + this.Author.Pod.host
159 } 184 }
160 185
161 const xs = baseUrlHttp + constants.STATIC_PATHS.TORRENTS + this.getTorrentName() 186 const xs = baseUrlHttp + constants.STATIC_PATHS.TORRENTS + this.getTorrentName()
@@ -166,7 +191,7 @@ function generateMagnetUri () {
166 xs, 191 xs,
167 announce, 192 announce,
168 urlList, 193 urlList,
169 infoHash: this.magnet.infoHash, 194 infoHash: this.infoHash,
170 name: this.name 195 name: this.name
171 } 196 }
172 197
@@ -174,20 +199,20 @@ function generateMagnetUri () {
174} 199}
175 200
176function getVideoFilename () { 201function getVideoFilename () {
177 if (this.isOwned()) return this._id + this.extname 202 if (this.isOwned()) return this.id + this.extname
178 203
179 return this.remoteId + this.extname 204 return this.remoteId + this.extname
180} 205}
181 206
182function getThumbnailName () { 207function getThumbnailName () {
183 // We always have a copy of the thumbnail 208 // We always have a copy of the thumbnail
184 return this._id + '.jpg' 209 return this.id + '.jpg'
185} 210}
186 211
187function getPreviewName () { 212function getPreviewName () {
188 const extension = '.jpg' 213 const extension = '.jpg'
189 214
190 if (this.isOwned()) return this._id + extension 215 if (this.isOwned()) return this.id + extension
191 216
192 return this.remoteId + extension 217 return this.remoteId + extension
193} 218}
@@ -195,7 +220,7 @@ function getPreviewName () {
195function getTorrentName () { 220function getTorrentName () {
196 const extension = '.torrent' 221 const extension = '.torrent'
197 222
198 if (this.isOwned()) return this._id + extension 223 if (this.isOwned()) return this.id + extension
199 224
200 return this.remoteId + extension 225 return this.remoteId + extension
201} 226}
@@ -205,18 +230,27 @@ function isOwned () {
205} 230}
206 231
207function toFormatedJSON () { 232function toFormatedJSON () {
233 let podHost
234
235 if (this.Author.Pod) {
236 podHost = this.Author.Pod.host
237 } else {
238 // It means it's our video
239 podHost = constants.CONFIG.WEBSERVER.HOST
240 }
241
208 const json = { 242 const json = {
209 id: this._id, 243 id: this.id,
210 name: this.name, 244 name: this.name,
211 description: this.description, 245 description: this.description,
212 podHost: this.podHost, 246 podHost,
213 isLocal: this.isOwned(), 247 isLocal: this.isOwned(),
214 magnetUri: this.generateMagnetUri(), 248 magnetUri: this.generateMagnetUri(),
215 author: this.author, 249 author: this.Author.name,
216 duration: this.duration, 250 duration: this.duration,
217 tags: this.tags, 251 tags: this.tags,
218 thumbnailPath: constants.STATIC_PATHS.THUMBNAILS + '/' + this.getThumbnailName(), 252 thumbnailPath: constants.STATIC_PATHS.THUMBNAILS + '/' + this.getThumbnailName(),
219 createdDate: this.createdDate 253 createdAt: this.createdAt
220 } 254 }
221 255
222 return json 256 return json
@@ -236,13 +270,13 @@ function toRemoteJSON (callback) {
236 const remoteVideo = { 270 const remoteVideo = {
237 name: self.name, 271 name: self.name,
238 description: self.description, 272 description: self.description,
239 magnet: self.magnet, 273 infoHash: self.infoHash,
240 remoteId: self._id, 274 remoteId: self.id,
241 author: self.author, 275 author: self.Author.name,
242 duration: self.duration, 276 duration: self.duration,
243 thumbnailBase64: new Buffer(thumbnailData).toString('base64'), 277 thumbnailBase64: new Buffer(thumbnailData).toString('base64'),
244 tags: self.tags, 278 tags: self.tags,
245 createdDate: self.createdDate, 279 createdAt: self.createdAt,
246 extname: self.extname 280 extname: self.extname
247 } 281 }
248 282
@@ -273,50 +307,168 @@ function getDurationFromFile (videoPath, callback) {
273} 307}
274 308
275function listForApi (start, count, sort, callback) { 309function listForApi (start, count, sort, callback) {
276 const query = {} 310 const query = {
277 return modelUtils.listForApiWithCount.call(this, query, start, count, sort, callback) 311 offset: start,
312 limit: count,
313 order: [ modelUtils.getSort(sort) ],
314 include: [
315 {
316 model: this.sequelize.models.Author,
317 include: [ this.sequelize.models.Pod ]
318 }
319 ]
320 }
321
322 return this.findAndCountAll(query).asCallback(function (err, result) {
323 if (err) return callback(err)
324
325 return callback(null, result.rows, result.count)
326 })
278} 327}
279 328
280function listByHostAndRemoteId (fromHost, remoteId, callback) { 329function listByHostAndRemoteId (fromHost, remoteId, callback) {
281 this.find({ podHost: fromHost, remoteId: remoteId }, callback) 330 const query = {
282} 331 where: {
332 remoteId: remoteId
333 },
334 include: [
335 {
336 model: this.sequelize.models.Author,
337 include: [
338 {
339 model: this.sequelize.models.Pod,
340 where: {
341 host: fromHost
342 }
343 }
344 ]
345 }
346 ]
347 }
283 348
284function listByHost (fromHost, callback) { 349 return this.findAll(query).asCallback(callback)
285 this.find({ podHost: fromHost }, callback)
286} 350}
287 351
288function listOwned (callback) { 352function listOwnedAndPopulateAuthor (callback) {
289 // If remoteId is null this is *our* video 353 // If remoteId is null this is *our* video
290 this.find({ remoteId: null }, callback) 354 const query = {
355 where: {
356 remoteId: null
357 },
358 include: [ this.sequelize.models.Author ]
359 }
360
361 return this.findAll(query).asCallback(callback)
291} 362}
292 363
293function listOwnedByAuthor (author, callback) { 364function listOwnedByAuthor (author, callback) {
294 this.find({ remoteId: null, author: author }, callback) 365 const query = {
295} 366 where: {
367 remoteId: null
368 },
369 include: [
370 {
371 model: this.sequelize.models.Author,
372 where: {
373 name: author
374 }
375 }
376 ]
377 }
296 378
297function listRemotes (callback) { 379 return this.findAll(query).asCallback(callback)
298 this.find({ remoteId: { $ne: null } }, callback)
299} 380}
300 381
301function load (id, callback) { 382function load (id, callback) {
302 this.findById(id, callback) 383 return this.findById(id).asCallback(callback)
303} 384}
304 385
305function search (value, field, start, count, sort, callback) { 386function loadAndPopulateAuthor (id, callback) {
306 const query = {} 387 const options = {
388 include: [ this.sequelize.models.Author ]
389 }
390
391 return this.findById(id, options).asCallback(callback)
392}
393
394function loadAndPopulateAuthorAndPod (id, callback) {
395 const options = {
396 include: [
397 {
398 model: this.sequelize.models.Author,
399 include: [ this.sequelize.models.Pod ]
400 }
401 ]
402 }
403
404 return this.findById(id, options).asCallback(callback)
405}
406
407function searchAndPopulateAuthorAndPod (value, field, start, count, sort, callback) {
408 const podInclude = {
409 model: this.sequelize.models.Pod
410 }
411 const authorInclude = {
412 model: this.sequelize.models.Author,
413 include: [
414 podInclude
415 ]
416 }
417
418 const query = {
419 where: {},
420 include: [
421 authorInclude
422 ],
423 offset: start,
424 limit: count,
425 order: [ modelUtils.getSort(sort) ]
426 }
427
428 // TODO: include our pod for podHost searches (we are not stored in the database)
307 // Make an exact search with the magnet 429 // Make an exact search with the magnet
308 if (field === 'magnetUri') { 430 if (field === 'magnetUri') {
309 const infoHash = magnetUtil.decode(value).infoHash 431 const infoHash = magnetUtil.decode(value).infoHash
310 query.magnet = { 432 query.where.infoHash = infoHash
311 infoHash
312 }
313 } else if (field === 'tags') { 433 } else if (field === 'tags') {
314 query[field] = value 434 query.where[field] = value
435 } else if (field === 'host') {
436 const whereQuery = {
437 '$Author.Pod.host$': {
438 $like: '%' + value + '%'
439 }
440 }
441
442 // Include our pod? (not stored in the database)
443 if (constants.CONFIG.WEBSERVER.HOST.indexOf(value) !== -1) {
444 query.where = {
445 $or: [
446 whereQuery,
447 {
448 remoteId: null
449 }
450 ]
451 }
452 } else {
453 query.where = whereQuery
454 }
455 } else if (field === 'author') {
456 query.where = {
457 '$Author.name$': {
458 $like: '%' + value + '%'
459 }
460 }
315 } else { 461 } else {
316 query[field] = new RegExp(value, 'i') 462 query.where[field] = {
463 $like: '%' + value + '%'
464 }
317 } 465 }
318 466
319 modelUtils.listForApiWithCount.call(this, query, start, count, sort, callback) 467 return this.findAndCountAll(query).asCallback(function (err, result) {
468 if (err) return callback(err)
469
470 return callback(null, result.rows, result.count)
471 })
320} 472}
321 473
322// --------------------------------------------------------------------------- 474// ---------------------------------------------------------------------------