aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/models
diff options
context:
space:
mode:
authorChocobozzz <florian.bigard@gmail.com>2016-10-02 15:39:09 +0200
committerChocobozzz <florian.bigard@gmail.com>2016-10-02 15:39:09 +0200
commita6375e69668ea42e19531c6bc68dcd37f3f7cbd7 (patch)
tree03204a408d56311692c3528bedcf95d2455e94f2 /server/models
parent052937db8a8d282eccdbdf38d487ed8d85d3c0a7 (diff)
parentc4403b29ad4db097af528a7f04eea07e0ed320d0 (diff)
downloadPeerTube-a6375e69668ea42e19531c6bc68dcd37f3f7cbd7.tar.gz
PeerTube-a6375e69668ea42e19531c6bc68dcd37f3f7cbd7.tar.zst
PeerTube-a6375e69668ea42e19531c6bc68dcd37f3f7cbd7.zip
Merge branch 'master' into webseed-merged
Diffstat (limited to 'server/models')
-rw-r--r--server/models/application.js31
-rw-r--r--server/models/oauth-client.js6
-rw-r--r--server/models/oauth-token.js11
-rw-r--r--server/models/pods.js44
-rw-r--r--server/models/request.js43
-rw-r--r--server/models/user.js86
-rw-r--r--server/models/utils.js30
-rw-r--r--server/models/video.js85
8 files changed, 248 insertions, 88 deletions
diff --git a/server/models/application.js b/server/models/application.js
new file mode 100644
index 000000000..452ac4283
--- /dev/null
+++ b/server/models/application.js
@@ -0,0 +1,31 @@
1const mongoose = require('mongoose')
2
3// ---------------------------------------------------------------------------
4
5const ApplicationSchema = mongoose.Schema({
6 mongoSchemaVersion: {
7 type: Number,
8 default: 0
9 }
10})
11
12ApplicationSchema.statics = {
13 loadMongoSchemaVersion,
14 updateMongoSchemaVersion
15}
16
17mongoose.model('Application', ApplicationSchema)
18
19// ---------------------------------------------------------------------------
20
21function loadMongoSchemaVersion (callback) {
22 return this.findOne({}, { mongoSchemaVersion: 1 }, function (err, data) {
23 const version = data ? data.mongoSchemaVersion : 0
24
25 return callback(err, version)
26 })
27}
28
29function updateMongoSchemaVersion (newVersion, callback) {
30 return this.update({}, { mongoSchemaVersion: newVersion }, callback)
31}
diff --git a/server/models/oauth-client.js b/server/models/oauth-client.js
index 45834c5a5..a1aefa985 100644
--- a/server/models/oauth-client.js
+++ b/server/models/oauth-client.js
@@ -11,9 +11,9 @@ const OAuthClientSchema = mongoose.Schema({
11OAuthClientSchema.path('clientSecret').required(true) 11OAuthClientSchema.path('clientSecret').required(true)
12 12
13OAuthClientSchema.statics = { 13OAuthClientSchema.statics = {
14 getByIdAndSecret: getByIdAndSecret, 14 getByIdAndSecret,
15 list: list, 15 list,
16 loadFirstClient: loadFirstClient 16 loadFirstClient
17} 17}
18 18
19mongoose.model('OAuthClient', OAuthClientSchema) 19mongoose.model('OAuthClient', OAuthClientSchema)
diff --git a/server/models/oauth-token.js b/server/models/oauth-token.js
index f6a814c36..5beb47bed 100644
--- a/server/models/oauth-token.js
+++ b/server/models/oauth-token.js
@@ -18,9 +18,10 @@ OAuthTokenSchema.path('client').required(true)
18OAuthTokenSchema.path('user').required(true) 18OAuthTokenSchema.path('user').required(true)
19 19
20OAuthTokenSchema.statics = { 20OAuthTokenSchema.statics = {
21 getByRefreshTokenAndPopulateClient: getByRefreshTokenAndPopulateClient, 21 getByRefreshTokenAndPopulateClient,
22 getByTokenAndPopulateUser: getByTokenAndPopulateUser, 22 getByTokenAndPopulateUser,
23 getByRefreshToken: getByRefreshToken 23 getByRefreshToken,
24 removeByUserId
24} 25}
25 26
26mongoose.model('OAuthToken', OAuthTokenSchema) 27mongoose.model('OAuthToken', OAuthTokenSchema)
@@ -53,3 +54,7 @@ function getByTokenAndPopulateUser (bearerToken) {
53function getByRefreshToken (refreshToken) { 54function getByRefreshToken (refreshToken) {
54 return this.findOne({ refreshToken: refreshToken }).exec() 55 return this.findOne({ refreshToken: refreshToken }).exec()
55} 56}
57
58function removeByUserId (userId, callback) {
59 return this.remove({ user: userId }, callback)
60}
diff --git a/server/models/pods.js b/server/models/pods.js
index bf43d7b25..4020a9603 100644
--- a/server/models/pods.js
+++ b/server/models/pods.js
@@ -11,7 +11,11 @@ const constants = require('../initializers/constants')
11const PodSchema = mongoose.Schema({ 11const PodSchema = mongoose.Schema({
12 url: String, 12 url: String,
13 publicKey: String, 13 publicKey: String,
14 score: { type: Number, max: constants.FRIEND_SCORE.MAX } 14 score: { type: Number, max: constants.FRIEND_SCORE.MAX },
15 createdDate: {
16 type: Date,
17 default: Date.now
18 }
15}) 19})
16 20
17// TODO: set options (TLD...) 21// TODO: set options (TLD...)
@@ -19,16 +23,19 @@ PodSchema.path('url').validate(validator.isURL)
19PodSchema.path('publicKey').required(true) 23PodSchema.path('publicKey').required(true)
20PodSchema.path('score').validate(function (value) { return !isNaN(value) }) 24PodSchema.path('score').validate(function (value) { return !isNaN(value) })
21 25
26PodSchema.methods = {
27 toFormatedJSON
28}
29
22PodSchema.statics = { 30PodSchema.statics = {
23 countAll: countAll, 31 countAll,
24 incrementScores: incrementScores, 32 incrementScores,
25 list: list, 33 list,
26 listAllIds: listAllIds, 34 listAllIds,
27 listOnlyUrls: listOnlyUrls, 35 listBadPods,
28 listBadPods: listBadPods, 36 load,
29 load: load, 37 loadByUrl,
30 loadByUrl: loadByUrl, 38 removeAll
31 removeAll: removeAll
32} 39}
33 40
34PodSchema.pre('save', function (next) { 41PodSchema.pre('save', function (next) {
@@ -46,6 +53,19 @@ PodSchema.pre('save', function (next) {
46 53
47const Pod = mongoose.model('Pod', PodSchema) 54const Pod = mongoose.model('Pod', PodSchema)
48 55
56// ------------------------------ METHODS ------------------------------
57
58function toFormatedJSON () {
59 const json = {
60 id: this._id,
61 url: this.url,
62 score: this.score,
63 createdDate: this.createdDate
64 }
65
66 return json
67}
68
49// ------------------------------ Statics ------------------------------ 69// ------------------------------ Statics ------------------------------
50 70
51function countAll (callback) { 71function countAll (callback) {
@@ -69,10 +89,6 @@ function listAllIds (callback) {
69 }) 89 })
70} 90}
71 91
72function listOnlyUrls (callback) {
73 return this.find({}, { _id: 0, url: 1 }, callback)
74}
75
76function listBadPods (callback) { 92function listBadPods (callback) {
77 return this.find({ score: 0 }, callback) 93 return this.find({ score: 0 }, callback)
78} 94}
diff --git a/server/models/request.js b/server/models/request.js
index 4d521919a..2d1c5af15 100644
--- a/server/models/request.js
+++ b/server/models/request.js
@@ -14,19 +14,22 @@ const Pod = mongoose.model('Pod')
14const Video = mongoose.model('Video') 14const Video = mongoose.model('Video')
15 15
16let timer = null 16let timer = null
17let lastRequestTimestamp = 0
17 18
18// --------------------------------------------------------------------------- 19// ---------------------------------------------------------------------------
19 20
20const RequestSchema = mongoose.Schema({ 21const RequestSchema = mongoose.Schema({
21 request: mongoose.Schema.Types.Mixed, 22 request: mongoose.Schema.Types.Mixed,
22 to: [ { type: mongoose.Schema.Types.ObjectId, ref: 'users' } ] 23 to: [ { type: mongoose.Schema.Types.ObjectId, ref: 'Pod' } ]
23}) 24})
24 25
25RequestSchema.statics = { 26RequestSchema.statics = {
26 activate, 27 activate,
27 deactivate, 28 deactivate,
28 flush, 29 flush,
29 forceSend 30 forceSend,
31 list,
32 remainingMilliSeconds
30} 33}
31 34
32RequestSchema.pre('save', function (next) { 35RequestSchema.pre('save', function (next) {
@@ -53,12 +56,19 @@ mongoose.model('Request', RequestSchema)
53 56
54function activate () { 57function activate () {
55 logger.info('Requests scheduler activated.') 58 logger.info('Requests scheduler activated.')
56 timer = setInterval(makeRequests.bind(this), constants.INTERVAL) 59 lastRequestTimestamp = Date.now()
60
61 const self = this
62 timer = setInterval(function () {
63 lastRequestTimestamp = Date.now()
64 makeRequests.call(self)
65 }, constants.REQUESTS_INTERVAL)
57} 66}
58 67
59function deactivate () { 68function deactivate () {
60 logger.info('Requests scheduler deactivated.') 69 logger.info('Requests scheduler deactivated.')
61 clearInterval(timer) 70 clearInterval(timer)
71 timer = null
62} 72}
63 73
64function flush () { 74function flush () {
@@ -72,6 +82,16 @@ function forceSend () {
72 makeRequests.call(this) 82 makeRequests.call(this)
73} 83}
74 84
85function list (callback) {
86 this.find({ }, callback)
87}
88
89function remainingMilliSeconds () {
90 if (timer === null) return -1
91
92 return constants.REQUESTS_INTERVAL - (Date.now() - lastRequestTimestamp)
93}
94
75// --------------------------------------------------------------------------- 95// ---------------------------------------------------------------------------
76 96
77// Make a requests to friends of a certain type 97// Make a requests to friends of a certain type
@@ -91,7 +111,13 @@ function makeRequest (toPod, requestsToMake, callback) {
91 // The function fire some useful callbacks 111 // The function fire some useful callbacks
92 requests.makeSecureRequest(params, function (err, res) { 112 requests.makeSecureRequest(params, function (err, res) {
93 if (err || (res.statusCode !== 200 && res.statusCode !== 201 && res.statusCode !== 204)) { 113 if (err || (res.statusCode !== 200 && res.statusCode !== 201 && res.statusCode !== 204)) {
94 logger.error('Error sending secure request to %s pod.', toPod.url, { error: err || new Error('Status code not 20x') }) 114 logger.error(
115 'Error sending secure request to %s pod.',
116 toPod.url,
117 {
118 error: err || new Error('Status code not 20x : ' + res.statusCode)
119 }
120 )
95 121
96 return callback(false) 122 return callback(false)
97 } 123 }
@@ -148,19 +174,14 @@ function makeRequests () {
148 return callbackEach() 174 return callbackEach()
149 } 175 }
150 176
151 // Maybe the pod is not our friend anymore so simply remove them 177 // Maybe the pod is not our friend anymore so simply remove it
152 if (!toPod) { 178 if (!toPod) {
179 logger.info('Removing %d requests of unexisting pod %s.', requestToMake.ids.length, toPodId)
153 removePodOf.call(self, requestToMake.ids, toPodId) 180 removePodOf.call(self, requestToMake.ids, toPodId)
154 return callbackEach() 181 return callbackEach()
155 } 182 }
156 183
157 makeRequest(toPod, requestToMake.datas, function (success) { 184 makeRequest(toPod, requestToMake.datas, function (success) {
158 if (err) {
159 logger.error('Errors when sent request to %s.', toPod.url, { error: err })
160 // Do not stop the process just for one error
161 return callbackEach()
162 }
163
164 if (success === true) { 185 if (success === true) {
165 logger.debug('Removing requests for %s pod.', toPodId, { requestsIds: requestToMake.ids }) 186 logger.debug('Removing requests for %s pod.', toPodId, { requestsIds: requestToMake.ids })
166 187
diff --git a/server/models/user.js b/server/models/user.js
index 14ffecbff..a19de7072 100644
--- a/server/models/user.js
+++ b/server/models/user.js
@@ -1,28 +1,98 @@
1const mongoose = require('mongoose') 1const mongoose = require('mongoose')
2 2
3const customUsersValidators = require('../helpers/custom-validators').users
4const modelUtils = require('./utils')
5const peertubeCrypto = require('../helpers/peertube-crypto')
6
7const OAuthToken = mongoose.model('OAuthToken')
8
3// --------------------------------------------------------------------------- 9// ---------------------------------------------------------------------------
4 10
5const UserSchema = mongoose.Schema({ 11const UserSchema = mongoose.Schema({
12 createdDate: {
13 type: Date,
14 default: Date.now
15 },
6 password: String, 16 password: String,
7 username: String 17 username: String,
18 role: String
8}) 19})
9 20
10UserSchema.path('password').required(true) 21UserSchema.path('password').required(customUsersValidators.isUserPasswordValid)
11UserSchema.path('username').required(true) 22UserSchema.path('username').required(customUsersValidators.isUserUsernameValid)
23UserSchema.path('role').validate(customUsersValidators.isUserRoleValid)
24
25UserSchema.methods = {
26 isPasswordMatch,
27 toFormatedJSON
28}
12 29
13UserSchema.statics = { 30UserSchema.statics = {
14 getByUsernameAndPassword: getByUsernameAndPassword, 31 countTotal,
15 list: list 32 getByUsername,
33 list,
34 listForApi,
35 loadById,
36 loadByUsername
16} 37}
17 38
39UserSchema.pre('save', function (next) {
40 const user = this
41
42 peertubeCrypto.cryptPassword(this.password, function (err, hash) {
43 if (err) return next(err)
44
45 user.password = hash
46
47 return next()
48 })
49})
50
51UserSchema.pre('remove', function (next) {
52 const user = this
53
54 OAuthToken.removeByUserId(user._id, next)
55})
56
18mongoose.model('User', UserSchema) 57mongoose.model('User', UserSchema)
19 58
20// --------------------------------------------------------------------------- 59// ------------------------------ METHODS ------------------------------
60
61function isPasswordMatch (password, callback) {
62 return peertubeCrypto.comparePassword(password, this.password, callback)
63}
64
65function toFormatedJSON () {
66 return {
67 id: this._id,
68 username: this.username,
69 role: this.role,
70 createdDate: this.createdDate
71 }
72}
73// ------------------------------ STATICS ------------------------------
74
75function countTotal (callback) {
76 return this.count(callback)
77}
78
79function getByUsername (username) {
80 return this.findOne({ username: username })
81}
21 82
22function list (callback) { 83function list (callback) {
23 return this.find(callback) 84 return this.find(callback)
24} 85}
25 86
26function getByUsernameAndPassword (username, password) { 87function listForApi (start, count, sort, callback) {
27 return this.findOne({ username: username, password: password }) 88 const query = {}
89 return modelUtils.listForApiWithCount.call(this, query, start, count, sort, callback)
90}
91
92function loadById (id, callback) {
93 return this.findById(id, callback)
94}
95
96function loadByUsername (username, callback) {
97 return this.findOne({ username: username }, callback)
28} 98}
diff --git a/server/models/utils.js b/server/models/utils.js
new file mode 100644
index 000000000..e798aabe6
--- /dev/null
+++ b/server/models/utils.js
@@ -0,0 +1,30 @@
1'use strict'
2
3const parallel = require('async/parallel')
4
5const utils = {
6 listForApiWithCount
7}
8
9function listForApiWithCount (query, start, count, sort, callback) {
10 const self = this
11
12 parallel([
13 function (asyncCallback) {
14 self.find(query).skip(start).limit(count).sort(sort).exec(asyncCallback)
15 },
16 function (asyncCallback) {
17 self.count(query, asyncCallback)
18 }
19 ], function (err, results) {
20 if (err) return callback(err)
21
22 const data = results[0]
23 const total = results[1]
24 return callback(null, data, total)
25 })
26}
27
28// ---------------------------------------------------------------------------
29
30module.exports = utils
diff --git a/server/models/video.js b/server/models/video.js
index 14e0df6f2..7d073cffa 100644
--- a/server/models/video.js
+++ b/server/models/video.js
@@ -11,8 +11,9 @@ const magnet = require('magnet-uri')
11const mongoose = require('mongoose') 11const mongoose = require('mongoose')
12 12
13const constants = require('../initializers/constants') 13const constants = require('../initializers/constants')
14const customValidators = require('../helpers/custom-validators') 14const customVideosValidators = require('../helpers/custom-validators').videos
15const logger = require('../helpers/logger') 15const logger = require('../helpers/logger')
16const modelUtils = require('./utils')
16const utils = require('../helpers/utils') 17const utils = require('../helpers/utils')
17 18
18const http = config.get('webserver.https') === true ? 'https' : 'http' 19const http = config.get('webserver.https') === true ? 'https' : 'http'
@@ -42,34 +43,35 @@ const VideoSchema = mongoose.Schema({
42 } 43 }
43}) 44})
44 45
45VideoSchema.path('name').validate(customValidators.isVideoNameValid) 46VideoSchema.path('name').validate(customVideosValidators.isVideoNameValid)
46VideoSchema.path('description').validate(customValidators.isVideoDescriptionValid) 47VideoSchema.path('description').validate(customVideosValidators.isVideoDescriptionValid)
47VideoSchema.path('magnetUri').validate(customValidators.isVideoMagnetUriValid) 48VideoSchema.path('magnetUri').validate(customVideosValidators.isVideoMagnetUriValid)
48VideoSchema.path('podUrl').validate(customValidators.isVideoPodUrlValid) 49VideoSchema.path('podUrl').validate(customVideosValidators.isVideoPodUrlValid)
49VideoSchema.path('author').validate(customValidators.isVideoAuthorValid) 50VideoSchema.path('author').validate(customVideosValidators.isVideoAuthorValid)
50VideoSchema.path('duration').validate(customValidators.isVideoDurationValid) 51VideoSchema.path('duration').validate(customVideosValidators.isVideoDurationValid)
51// The tumbnail can be the path or the data in base 64 52// The tumbnail can be the path or the data in base 64
52// The pre save hook will convert the base 64 data in a file on disk and replace the thumbnail key by the filename 53// The pre save hook will convert the base 64 data in a file on disk and replace the thumbnail key by the filename
53VideoSchema.path('thumbnail').validate(function (value) { 54VideoSchema.path('thumbnail').validate(function (value) {
54 return customValidators.isVideoThumbnailValid(value) || customValidators.isVideoThumbnail64Valid(value) 55 return customVideosValidators.isVideoThumbnailValid(value) || customVideosValidators.isVideoThumbnail64Valid(value)
55}) 56})
56VideoSchema.path('tags').validate(customValidators.isVideoTagsValid) 57VideoSchema.path('tags').validate(customVideosValidators.isVideoTagsValid)
57 58
58VideoSchema.methods = { 59VideoSchema.methods = {
59 isOwned: isOwned, 60 isOwned,
60 toFormatedJSON: toFormatedJSON, 61 toFormatedJSON,
61 toRemoteJSON: toRemoteJSON 62 toRemoteJSON
62} 63}
63 64
64VideoSchema.statics = { 65VideoSchema.statics = {
65 getDurationFromFile: getDurationFromFile, 66 getDurationFromFile,
66 list: list, 67 listForApi,
67 listByUrlAndMagnet: listByUrlAndMagnet, 68 listByUrlAndMagnet,
68 listByUrls: listByUrls, 69 listByUrls,
69 listOwned: listOwned, 70 listOwned,
70 listRemotes: listRemotes, 71 listOwnedByAuthor,
71 load: load, 72 listRemotes,
72 search: search 73 load,
74 search
73} 75}
74 76
75VideoSchema.pre('remove', function (next) { 77VideoSchema.pre('remove', function (next) {
@@ -101,8 +103,8 @@ VideoSchema.pre('save', function (next) {
101 const tasks = [] 103 const tasks = []
102 104
103 if (video.isOwned()) { 105 if (video.isOwned()) {
104 const videoPath = pathUtils.join(uploadsDir, video.filename) 106 const videoPath = pathUtils.join(constants.CONFIG.STORAGE.UPLOAD_DIR, video.filename)
105 this.podUrl = http + '://' + host + ':' + port 107 this.podUrl = constants.CONFIG.WEBSERVER.URL
106 108
107 tasks.push( 109 tasks.push(
108 // TODO: refractoring 110 // TODO: refractoring
@@ -174,7 +176,7 @@ function toRemoteJSON (callback) {
174 const self = this 176 const self = this
175 177
176 // Convert thumbnail to base64 178 // Convert thumbnail to base64
177 fs.readFile(pathUtils.join(thumbnailsDir, this.thumbnail), function (err, thumbnailData) { 179 fs.readFile(pathUtils.join(constants.CONFIG.STORAGE.THUMBNAILS_DIR, this.thumbnail), function (err, thumbnailData) {
178 if (err) { 180 if (err) {
179 logger.error('Cannot read the thumbnail of the video') 181 logger.error('Cannot read the thumbnail of the video')
180 return callback(err) 182 return callback(err)
@@ -207,9 +209,9 @@ function getDurationFromFile (videoPath, callback) {
207 }) 209 })
208} 210}
209 211
210function list (start, count, sort, callback) { 212function listForApi (start, count, sort, callback) {
211 const query = {} 213 const query = {}
212 return findWithCount.call(this, query, start, count, sort, callback) 214 return modelUtils.listForApiWithCount.call(this, query, start, count, sort, callback)
213} 215}
214 216
215function listByUrlAndMagnet (fromUrl, magnetUri, callback) { 217function listByUrlAndMagnet (fromUrl, magnetUri, callback) {
@@ -225,6 +227,10 @@ function listOwned (callback) {
225 this.find({ filename: { $ne: null } }, callback) 227 this.find({ filename: { $ne: null } }, callback)
226} 228}
227 229
230function listOwnedByAuthor (author, callback) {
231 this.find({ filename: { $ne: null }, author: author }, callback)
232}
233
228function listRemotes (callback) { 234function listRemotes (callback) {
229 this.find({ filename: null }, callback) 235 this.find({ filename: null }, callback)
230} 236}
@@ -242,36 +248,17 @@ function search (value, field, start, count, sort, callback) {
242 query[field] = new RegExp(value) 248 query[field] = new RegExp(value)
243 } 249 }
244 250
245 findWithCount.call(this, query, start, count, sort, callback) 251 modelUtils.listForApiWithCount.call(this, query, start, count, sort, callback)
246} 252}
247 253
248// --------------------------------------------------------------------------- 254// ---------------------------------------------------------------------------
249 255
250function findWithCount (query, start, count, sort, callback) {
251 const self = this
252
253 parallel([
254 function (asyncCallback) {
255 self.find(query).skip(start).limit(count).sort(sort).exec(asyncCallback)
256 },
257 function (asyncCallback) {
258 self.count(query, asyncCallback)
259 }
260 ], function (err, results) {
261 if (err) return callback(err)
262
263 const videos = results[0]
264 const totalVideos = results[1]
265 return callback(null, videos, totalVideos)
266 })
267}
268
269function removeThumbnail (video, callback) { 256function removeThumbnail (video, callback) {
270 fs.unlink(thumbnailsDir + video.thumbnail, callback) 257 fs.unlink(constants.CONFIG.STORAGE.THUMBNAILS_DIR + video.thumbnail, callback)
271} 258}
272 259
273function removeFile (video, callback) { 260function removeFile (video, callback) {
274 fs.unlink(uploadsDir + video.filename, callback) 261 fs.unlink(constants.CONFIG.STORAGE.UPLOAD_DIR + video.filename, callback)
275} 262}
276 263
277// Maybe the torrent is not seeded, but we catch the error to don't stop the removing process 264// Maybe the torrent is not seeded, but we catch the error to don't stop the removing process
@@ -288,7 +275,7 @@ function createThumbnail (videoPath, callback) {
288 }) 275 })
289 .thumbnail({ 276 .thumbnail({
290 count: 1, 277 count: 1,
291 folder: thumbnailsDir, 278 folder: constants.CONFIG.STORAGE.THUMBNAILS_DIR,
292 size: constants.THUMBNAILS_SIZE, 279 size: constants.THUMBNAILS_SIZE,
293 filename: filename 280 filename: filename
294 }) 281 })
@@ -300,7 +287,7 @@ function generateThumbnailFromBase64 (data, callback) {
300 if (err) return callback(err) 287 if (err) return callback(err)
301 288
302 const thumbnailName = randomString + '.jpg' 289 const thumbnailName = randomString + '.jpg'
303 fs.writeFile(thumbnailsDir + thumbnailName, data, { encoding: 'base64' }, function (err) { 290 fs.writeFile(constants.CONFIG.STORAGE.THUMBNAILS_DIR + thumbnailName, data, { encoding: 'base64' }, function (err) {
304 if (err) return callback(err) 291 if (err) return callback(err)
305 292
306 return callback(null, thumbnailName) 293 return callback(null, thumbnailName)