<td>{{ friend.id }}</td>
<td>{{ friend.host }}</td>
<td>{{ friend.score }}</td>
- <td>{{ friend.createdDate | date: 'medium' }}</td>
+ <td>{{ friend.createdAt | date: 'medium' }}</td>
</tr>
</tbody>
</table>
id: string;
host: string;
score: number;
- createdDate: Date;
+ createdAt: Date;
}
<div>
<span class="label-description">Remaining requests:</span>
- {{ stats.requests.length }}
+ {{ stats.totalRequests }}
</div>
</div>
}
ngOnDestroy() {
- if (this.stats.secondsInterval !== null) {
+ if (this.stats !== null && this.stats.secondsInterval !== null) {
clearInterval(this.interval);
}
}
maxRequestsInParallel: number;
milliSecondsInterval: number;
remainingMilliSeconds: number;
- requests: Request[];
+ totalRequests: number;
constructor(hash: {
maxRequestsInParallel: number,
milliSecondsInterval: number,
remainingMilliSeconds: number,
- requests: Request[];
+ totalRequests: number;
}) {
this.maxRequestsInParallel = hash.maxRequestsInParallel;
this.milliSecondsInterval = hash.milliSecondsInterval;
this.remainingMilliSeconds = hash.remainingMilliSeconds;
- this.requests = hash.requests;
+ this.totalRequests = hash.totalRequests;
}
get remainingSeconds() {
<tr *ngFor="let user of users">
<td>{{ user.id }}</td>
<td>{{ user.username }}</td>
- <td>{{ user.createdDate | date: 'medium' }}</td>
+ <td>{{ user.createdAt | date: 'medium' }}</td>
<td class="text-right">
<span class="glyphicon glyphicon-remove" *ngIf="!user.isAdmin()" (click)="removeUser(user)"></span>
</td>
USERNAME: 'username'
};
- id: string;
- role: string;
- username: string;
tokens: Tokens;
static load() {
if (usernameLocalStorage) {
return new AuthUser(
{
- id: localStorage.getItem(this.KEYS.ID),
+ id: parseInt(localStorage.getItem(this.KEYS.ID)),
username: localStorage.getItem(this.KEYS.USERNAME),
role: localStorage.getItem(this.KEYS.ROLE)
},
Tokens.flush();
}
- constructor(userHash: { id: string, username: string, role: string }, hashTokens: any) {
+ constructor(userHash: { id: number, username: string, role: string }, hashTokens: any) {
super(userHash);
this.tokens = new Tokens(hashTokens);
}
}
save() {
- localStorage.setItem(AuthUser.KEYS.ID, this.id);
+ localStorage.setItem(AuthUser.KEYS.ID, this.id.toString());
localStorage.setItem(AuthUser.KEYS.USERNAME, this.username);
localStorage.setItem(AuthUser.KEYS.ROLE, this.role);
this.tokens.save();
-export type SearchField = "name" | "author" | "podUrl" | "magnetUri" | "tags";
+export type SearchField = "name" | "author" | "host" | "magnetUri" | "tags";
fieldChoices = {
name: 'Name',
author: 'Author',
- podUrl: 'Pod Url',
- magnetUri: 'Magnet Uri',
+ host: 'Pod Host',
+ magnetUri: 'Magnet URI',
tags: 'Tags'
};
searchCriterias: Search = {
export class User {
- id: string;
+ id: number;
username: string;
role: string;
- createdDate: Date;
+ createdAt: Date;
- constructor(hash: { id: string, username: string, role: string, createdDate?: Date }) {
+ constructor(hash: { id: number, username: string, role: string, createdAt?: Date }) {
this.id = hash.id;
this.username = hash.username;
this.role = hash.role;
- if (hash.createdDate) {
- this.createdDate = hash.createdDate;
+ if (hash.createdAt) {
+ this.createdAt = hash.createdAt;
}
}
export type SortField = "name" | "-name"
| "duration" | "-duration"
- | "createdDate" | "-createdDate";
+ | "createdAt" | "-createdAt";
export class Video {
author: string;
by: string;
- createdDate: Date;
+ createdAt: Date;
description: string;
duration: string;
id: string;
constructor(hash: {
author: string,
- createdDate: string,
+ createdAt: string,
description: string,
duration: number;
id: string,
thumbnailPath: string
}) {
this.author = hash.author;
- this.createdDate = new Date(hash.createdDate);
+ this.createdAt = new Date(hash.createdAt);
this.description = hash.description;
this.duration = Video.createDurationString(hash.duration);
this.id = hash.id;
};
}
- this.sort = <SortField>routeParams['sort'] || '-createdDate';
+ this.sort = <SortField>routeParams['sort'] || '-createdAt';
if (routeParams['page'] !== undefined) {
this.pagination.currentPage = parseInt(routeParams['page']);
</span>
<a [routerLink]="['/videos/list', { field: 'author', search: video.author, sort: currentSort }]" class="video-miniature-author">{{ video.by }}</a>
- <span class="video-miniature-created-date">{{ video.createdDate | date:'short' }}</span>
+ <span class="video-miniature-created-at">{{ video.createdAt | date:'short' }}</span>
</div>
</div>
}
}
- .video-miniature-author, .video-miniature-created-date {
+ .video-miniature-author, .video-miniature-created-at {
display: block;
margin-left: 1px;
font-size: 12px;
'-name': 'Name - Desc',
'duration': 'Duration - Asc',
'-duration': 'Duration - Desc',
- 'createdDate': 'Created Date - Asc',
- '-createdDate': 'Created Date - Desc'
+ 'createdAt': 'Created Date - Asc',
+ '-createdAt': 'Created Date - Desc'
};
get choiceKeys() {
{{ video.by }}
</a>
</span>
- <span id="video-date">on {{ video.createdDate | date:'short' }}</span>
+ <span id="video-date">on {{ video.createdAt | date:'short' }}</span>
</div>
</div>
</div>
database:
hostname: 'localhost'
- port: 27017
- suffix: '-dev'
+ port: 5432
+ suffix: '_dev'
# From the project root directory
storage:
port: 80
database:
- suffix: '-prod'
+ suffix: '_prod'
port: 9001
database:
- suffix: '-test1'
+ suffix: '_test1'
# From the project root directory
storage:
port: 9002
database:
- suffix: '-test2'
+ suffix: '_test2'
# From the project root directory
storage:
port: 9003
database:
- suffix: '-test3'
+ suffix: '_test3'
# From the project root directory
storage:
port: 9004
database:
- suffix: '-test4'
+ suffix: '_test4'
# From the project root directory
storage:
port: 9005
database:
- suffix: '-test5'
+ suffix: '_test5'
# From the project root directory
storage:
port: 9006
database:
- suffix: '-test6'
+ suffix: '_test6'
# From the project root directory
storage:
database:
hostname: 'localhost'
- port: 27017
+ port: 5432
"lodash": "^4.11.1",
"magnet-uri": "^5.1.4",
"mkdirp": "^0.5.1",
- "mongoose": "^4.0.5",
"morgan": "^1.5.3",
"multer": "^1.1.0",
"openssl-wrapper": "^0.3.4",
"parse-torrent": "^5.8.0",
"password-generator": "^2.0.2",
+ "pg": "^6.1.0",
+ "pg-hstore": "^2.3.2",
"request": "^2.57.0",
"request-replay": "^1.0.2",
"rimraf": "^2.5.4",
"scripty": "^1.5.0",
+ "sequelize": "^3.27.0",
"ursa": "^0.9.1",
"winston": "^2.1.1",
"ws": "^1.1.1"
#!/usr/bin/env sh
for i in $(seq 1 6); do
- printf "use peertube-test%s;\ndb.dropDatabase();" "$i" | mongo
+ dropdb "peertube_test$i"
rm -rf "./test$i"
+ createdb "peertube_test$i"
done
// ----------- Database -----------
const constants = require('./server/initializers/constants')
-const database = require('./server/initializers/database')
const logger = require('./server/helpers/logger')
-
-database.connect()
+// Initialize database and models
+const db = require('./server/initializers/database')
// ----------- Checker -----------
const checker = require('./server/initializers/checker')
const customValidators = require('./server/helpers/custom-validators')
const installer = require('./server/initializers/installer')
const migrator = require('./server/initializers/migrator')
-const mongoose = require('mongoose')
const routes = require('./server/controllers')
-const Request = mongoose.model('Request')
// ----------- Command line -----------
// ----------- Make the server listening -----------
server.listen(port, function () {
// Activate the pool requests
- Request.activate()
+ db.Request.activate()
logger.info('Server listening on port %d', port)
logger.info('Webserver: %s', constants.CONFIG.WEBSERVER.URL)
'use strict'
const express = require('express')
-const mongoose = require('mongoose')
const constants = require('../../initializers/constants')
+const db = require('../../initializers/database')
const logger = require('../../helpers/logger')
-const Client = mongoose.model('OAuthClient')
-
const router = express.Router()
router.get('/local', getLocalClient)
return res.type('json').status(403).end()
}
- Client.loadFirstClient(function (err, client) {
+ db.OAuthClient.loadFirstClient(function (err, client) {
if (err) return next(err)
if (!client) return next(new Error('No client available.'))
res.json({
- client_id: client._id,
+ client_id: client.clientId,
client_secret: client.clientSecret
})
})
'use strict'
const express = require('express')
-const mongoose = require('mongoose')
const waterfall = require('async/waterfall')
+const db = require('../../initializers/database')
const logger = require('../../helpers/logger')
const friends = require('../../lib/friends')
const middlewares = require('../../middlewares')
const signatureValidator = middlewares.validators.remote.signature
const router = express.Router()
-const Pod = mongoose.model('Pod')
router.get('/', listPods)
router.post('/',
waterfall([
function addPod (callback) {
- const pod = new Pod(informations)
- pod.save(function (err, podCreated) {
+ const pod = db.Pod.build(informations)
+ pod.save().asCallback(function (err, podCreated) {
// Be sure about the number of parameters for the callback
return callback(err, podCreated)
})
},
function sendMyVideos (podCreated, callback) {
- friends.sendOwnedVideosToPod(podCreated._id)
+ friends.sendOwnedVideosToPod(podCreated.id)
callback(null)
},
}
function listPods (req, res, next) {
- Pod.list(function (err, podsList) {
+ db.Pod.list(function (err, podsList) {
if (err) return next(err)
res.json(getFormatedPods(podsList))
waterfall([
function loadPod (callback) {
- Pod.loadByHost(host, callback)
+ db.Pod.loadByHost(host, callback)
},
function removePod (pod, callback) {
- pod.remove(callback)
+ pod.destroy().asCallback(callback)
}
], function (err) {
if (err) return next(err)
const each = require('async/each')
const eachSeries = require('async/eachSeries')
const express = require('express')
-const mongoose = require('mongoose')
+const waterfall = require('async/waterfall')
+const db = require('../../initializers/database')
const middlewares = require('../../middlewares')
const secureMiddleware = middlewares.secure
const validators = middlewares.validators.remote
const logger = require('../../helpers/logger')
const router = express.Router()
-const Video = mongoose.model('Video')
router.post('/videos',
validators.signature,
function addRemoteVideo (videoToCreateData, fromHost, callback) {
logger.debug('Adding remote video "%s".', videoToCreateData.name)
- const video = new Video(videoToCreateData)
- video.podHost = fromHost
- Video.generateThumbnailFromBase64(video, videoToCreateData.thumbnailBase64, function (err) {
- if (err) {
- logger.error('Cannot generate thumbnail from base 64 data.', { error: err })
- return callback(err)
+ waterfall([
+
+ function findOrCreatePod (callback) {
+ fromHost
+
+ const query = {
+ where: {
+ host: fromHost
+ },
+ defaults: {
+ host: fromHost
+ }
+ }
+
+ db.Pod.findOrCreate(query).asCallback(function (err, result) {
+ // [ instance, wasCreated ]
+ return callback(err, result[0])
+ })
+ },
+
+ function findOrCreateAuthor (pod, callback) {
+ const username = videoToCreateData.author
+
+ const query = {
+ where: {
+ name: username,
+ podId: pod.id
+ },
+ defaults: {
+ name: username,
+ podId: pod.id
+ }
+ }
+
+ db.Author.findOrCreate(query).asCallback(function (err, result) {
+ // [ instance, wasCreated ]
+ return callback(err, result[0])
+ })
+ },
+
+ function createVideoObject (author, callback) {
+ const videoData = {
+ name: videoToCreateData.name,
+ remoteId: videoToCreateData.remoteId,
+ extname: videoToCreateData.extname,
+ infoHash: videoToCreateData.infoHash,
+ description: videoToCreateData.description,
+ authorId: author.id,
+ duration: videoToCreateData.duration,
+ tags: videoToCreateData.tags
+ }
+
+ const video = db.Video.build(videoData)
+
+ return callback(null, video)
+ },
+
+ function generateThumbnail (video, callback) {
+ db.Video.generateThumbnailFromBase64(video, videoToCreateData.thumbnailBase64, function (err) {
+ if (err) {
+ logger.error('Cannot generate thumbnail from base 64 data.', { error: err })
+ return callback(err)
+ }
+
+ video.save().asCallback(callback)
+ })
+ },
+
+ function insertIntoDB (video, callback) {
+ video.save().asCallback(callback)
}
- video.save(callback)
- })
+ ], callback)
}
function removeRemoteVideo (videoToRemoveData, fromHost, callback) {
+ // TODO: use bulkDestroy?
+
// We need the list because we have to remove some other stuffs (thumbnail etc)
- Video.listByHostAndRemoteId(fromHost, videoToRemoveData.remoteId, function (err, videosList) {
+ db.Video.listByHostAndRemoteId(fromHost, videoToRemoveData.remoteId, function (err, videosList) {
if (err) {
- logger.error('Cannot list videos from host and magnets.', { error: err })
+ logger.error('Cannot list videos from host and remote id.', { error: err.message })
return callback(err)
}
if (videosList.length === 0) {
- logger.error('No remote video was found for this pod.', { magnetUri: videoToRemoveData.magnetUri, podHost: fromHost })
+ logger.error('No remote video was found for this pod.', { remoteId: videoToRemoveData.remoteId, podHost: fromHost })
}
each(videosList, function (video, callbackEach) {
- logger.debug('Removing remote video %s.', video.magnetUri)
+ logger.debug('Removing remote video %s.', video.remoteId)
- video.remove(callbackEach)
+ video.destroy().asCallback(callbackEach)
}, callback)
})
}
'use strict'
const express = require('express')
-const mongoose = require('mongoose')
const constants = require('../../initializers/constants')
+const db = require('../../initializers/database')
const middlewares = require('../../middlewares')
const admin = middlewares.admin
const oAuth = middlewares.oauth
-const Request = mongoose.model('Request')
-
const router = express.Router()
router.get('/stats',
// ---------------------------------------------------------------------------
function getStatsRequests (req, res, next) {
- Request.list(function (err, requests) {
+ db.Request.countTotalRequests(function (err, totalRequests) {
if (err) return next(err)
return res.json({
- requests: requests,
+ totalRequests: totalRequests,
maxRequestsInParallel: constants.REQUESTS_IN_PARALLEL,
- remainingMilliSeconds: Request.remainingMilliSeconds(),
+ remainingMilliSeconds: db.Request.remainingMilliSeconds(),
milliSecondsInterval: constants.REQUESTS_INTERVAL
})
})
const each = require('async/each')
const express = require('express')
-const mongoose = require('mongoose')
const waterfall = require('async/waterfall')
const constants = require('../../initializers/constants')
+const db = require('../../initializers/database')
const friends = require('../../lib/friends')
const logger = require('../../helpers/logger')
const middlewares = require('../../middlewares')
const validatorsSort = middlewares.validators.sort
const validatorsUsers = middlewares.validators.users
-const User = mongoose.model('User')
-const Video = mongoose.model('Video')
-
const router = express.Router()
router.get('/me', oAuth.authenticate, getUserInformation)
// ---------------------------------------------------------------------------
function createUser (req, res, next) {
- const user = new User({
+ const user = db.User.build({
username: req.body.username,
password: req.body.password,
role: constants.USER_ROLES.USER
})
- user.save(function (err, createdUser) {
+ user.save().asCallback(function (err, createdUser) {
if (err) return next(err)
return res.type('json').status(204).end()
}
function getUserInformation (req, res, next) {
- User.loadByUsername(res.locals.oauth.token.user.username, function (err, user) {
+ db.User.loadByUsername(res.locals.oauth.token.user.username, function (err, user) {
if (err) return next(err)
return res.json(user.toFormatedJSON())
}
function listUsers (req, res, next) {
- User.listForApi(req.query.start, req.query.count, req.query.sort, function (err, usersList, usersTotal) {
+ db.User.listForApi(req.query.start, req.query.count, req.query.sort, function (err, usersList, usersTotal) {
if (err) return next(err)
res.json(getFormatedUsers(usersList, usersTotal))
function removeUser (req, res, next) {
waterfall([
function getUser (callback) {
- User.loadById(req.params.id, callback)
+ db.User.loadById(req.params.id, callback)
},
+ // TODO: use foreignkey?
function getVideos (user, callback) {
- Video.listOwnedByAuthor(user.username, function (err, videos) {
+ db.Video.listOwnedByAuthor(user.username, function (err, videos) {
return callback(err, user, videos)
})
},
function removeVideosFromDB (user, videos, callback) {
each(videos, function (video, callbackEach) {
- video.remove(callbackEach)
+ video.destroy().asCallback(callbackEach)
}, function (err) {
return callback(err, user, videos)
})
videos.forEach(function (video) {
const params = {
name: video.name,
- magnetUri: video.magnetUri
+ remoteId: video.id
}
friends.removeVideoToFriends(params)
},
function removeUserFromDB (user, callback) {
- user.remove(callback)
+ user.destroy().asCallback(callback)
}
], function andFinally (err) {
if (err) {
}
function updateUser (req, res, next) {
- User.loadByUsername(res.locals.oauth.token.user.username, function (err, user) {
+ db.User.loadByUsername(res.locals.oauth.token.user.username, function (err, user) {
if (err) return next(err)
user.password = req.body.password
- user.save(function (err) {
+ user.save().asCallback(function (err) {
if (err) return next(err)
return res.sendStatus(204)
const express = require('express')
const fs = require('fs')
-const mongoose = require('mongoose')
const multer = require('multer')
const path = require('path')
const waterfall = require('async/waterfall')
const constants = require('../../initializers/constants')
+const db = require('../../initializers/database')
const logger = require('../../helpers/logger')
const friends = require('../../lib/friends')
const middlewares = require('../../middlewares')
const utils = require('../../helpers/utils')
const router = express.Router()
-const Video = mongoose.model('Video')
// multer configuration
const storage = multer.diskStorage({
const videoInfos = req.body
waterfall([
- function createVideoObject (callback) {
- const id = mongoose.Types.ObjectId()
+ function findOrCreateAuthor (callback) {
+ const username = res.locals.oauth.token.user.username
+
+ const query = {
+ where: {
+ name: username,
+ podId: null
+ },
+ defaults: {
+ name: username,
+ podId: null // null because it is OUR pod
+ }
+ }
+
+ db.Author.findOrCreate(query).asCallback(function (err, result) {
+ // [ instance, wasCreated ]
+ return callback(err, result[0])
+ })
+ },
+
+ function createVideoObject (author, callback) {
const videoData = {
- _id: id,
name: videoInfos.name,
remoteId: null,
extname: path.extname(videoFile.filename),
description: videoInfos.description,
- author: res.locals.oauth.token.user.username,
duration: videoFile.duration,
- tags: videoInfos.tags
+ tags: videoInfos.tags,
+ authorId: author.id
}
- const video = new Video(videoData)
+ const video = db.Video.build(videoData)
- return callback(null, video)
+ return callback(null, author, video)
},
- // Set the videoname the same as the MongoDB id
- function renameVideoFile (video, callback) {
+ // Set the videoname the same as the id
+ function renameVideoFile (author, video, callback) {
const videoDir = constants.CONFIG.STORAGE.VIDEOS_DIR
const source = path.join(videoDir, videoFile.filename)
const destination = path.join(videoDir, video.getVideoFilename())
fs.rename(source, destination, function (err) {
- return callback(err, video)
+ return callback(err, author, video)
})
},
- function insertIntoDB (video, callback) {
- video.save(function (err, video) {
- // Assert there are only one argument sent to the next function (video)
- return callback(err, video)
+ function insertIntoDB (author, video, callback) {
+ video.save().asCallback(function (err, videoCreated) {
+ // Do not forget to add Author informations to the created video
+ videoCreated.Author = author
+
+ return callback(err, videoCreated)
})
},
}
function getVideo (req, res, next) {
- Video.load(req.params.id, function (err, video) {
+ db.Video.loadAndPopulateAuthorAndPod(req.params.id, function (err, video) {
if (err) return next(err)
if (!video) {
}
function listVideos (req, res, next) {
- Video.listForApi(req.query.start, req.query.count, req.query.sort, function (err, videosList, videosTotal) {
+ db.Video.listForApi(req.query.start, req.query.count, req.query.sort, function (err, videosList, videosTotal) {
if (err) return next(err)
res.json(getFormatedVideos(videosList, videosTotal))
waterfall([
function getVideo (callback) {
- Video.load(videoId, callback)
+ db.Video.load(videoId, callback)
},
function removeFromDB (video, callback) {
- video.remove(function (err) {
+ video.destroy().asCallback(function (err) {
if (err) return callback(err)
return callback(null, video)
function sendInformationToFriends (video, callback) {
const params = {
name: video.name,
- remoteId: video._id
+ remoteId: video.id
}
friends.removeVideoToFriends(params)
}
function searchVideos (req, res, next) {
- Video.search(req.params.value, req.query.field, req.query.start, req.query.count, req.query.sort,
+ db.Video.searchAndPopulateAuthorAndPod(req.params.value, req.query.field, req.query.start, req.query.count, req.query.sort,
function (err, videosList, videosTotal) {
if (err) return next(err)
const parallel = require('async/parallel')
const express = require('express')
const fs = require('fs')
-const mongoose = require('mongoose')
const path = require('path')
const validator = require('express-validator').validator
const constants = require('../initializers/constants')
+const db = require('../initializers/database')
-const Video = mongoose.model('Video')
const router = express.Router()
const opengraphComment = '<!-- opengraph tags -->'
if (video.isOwned()) {
basePreviewUrlHttp = constants.CONFIG.WEBSERVER.URL
} else {
- basePreviewUrlHttp = constants.REMOTE_SCHEME.HTTP + '://' + video.podHost
+ basePreviewUrlHttp = constants.REMOTE_SCHEME.HTTP + '://' + video.Author.Pod.host
}
// We fetch the remote preview (bigger than the thumbnail)
// This should not overhead the remote server since social websites put in a cache the OpenGraph tags
// We can't use the thumbnail because these social websites want bigger images (> 200x200 for Facebook for example)
const previewUrl = basePreviewUrlHttp + constants.STATIC_PATHS.PREVIEWS + video.getPreviewName()
- const videoUrl = constants.CONFIG.WEBSERVER.URL + '/videos/watch/' + video._id
+ const videoUrl = constants.CONFIG.WEBSERVER.URL + '/videos/watch/' + video.id
const metaTags = {
'og:type': 'video',
const videoId = req.params.id
// Let Angular application handle errors
- if (!validator.isMongoId(videoId)) return res.sendFile(indexPath)
+ if (!validator.isUUID(videoId, 4)) return res.sendFile(indexPath)
parallel({
file: function (callback) {
},
video: function (callback) {
- Video.load(videoId, callback)
+ db.Video.loadAndPopulateAuthorAndPod(videoId, callback)
}
}, function (err, results) {
if (err) return next(err)
isVideoDateValid,
isVideoDescriptionValid,
isVideoDurationValid,
- isVideoMagnetValid,
+ isVideoInfoHashValid,
isVideoNameValid,
isVideoPodHostValid,
isVideoTagsValid,
return (
isRequestTypeAddValid(request.type) &&
isVideoAuthorValid(video.author) &&
- isVideoDateValid(video.createdDate) &&
+ isVideoDateValid(video.createdAt) &&
isVideoDescriptionValid(video.description) &&
isVideoDurationValid(video.duration) &&
- isVideoMagnetValid(video.magnet) &&
+ isVideoInfoHashValid(video.infoHash) &&
isVideoNameValid(video.name) &&
isVideoTagsValid(video.tags) &&
isVideoThumbnail64Valid(video.thumbnailBase64) &&
- isVideoRemoteIdValid(video.remoteId)
+ isVideoRemoteIdValid(video.remoteId) &&
+ isVideoExtnameValid(video.extname)
) ||
(
isRequestTypeRemoveValid(request.type) &&
return validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.DURATION)
}
-function isVideoMagnetValid (value) {
- return validator.isLength(value.infoHash, VIDEOS_CONSTRAINTS_FIELDS.MAGNET.INFO_HASH)
+function isVideoExtnameValid (value) {
+ return VIDEOS_CONSTRAINTS_FIELDS.EXTNAME.indexOf(value) !== -1
+}
+
+function isVideoInfoHashValid (value) {
+ return validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.INFO_HASH)
}
function isVideoNameValid (value) {
}
function isVideoRemoteIdValid (value) {
- return validator.isMongoId(value)
+ return validator.isUUID(value, 4)
}
// ---------------------------------------------------------------------------
json: true,
maxsize: 5242880,
maxFiles: 5,
- colorize: false
+ colorize: false,
+ prettyPrint: true
}),
new winston.transports.Console({
level: 'debug',
handleExceptions: true,
humanReadableUnhandledException: true,
json: false,
- colorize: true
+ colorize: true,
+ prettyPrint: true
})
],
exitOnError: true
'use strict'
const config = require('config')
-const mongoose = require('mongoose')
-const Client = mongoose.model('OAuthClient')
-const User = mongoose.model('User')
+const db = require('./database')
const checker = {
checkConfig,
}
function clientsExist (callback) {
- Client.list(function (err, clients) {
+ db.OAuthClient.list(function (err, clients) {
if (err) return callback(err)
return callback(null, clients.length !== 0)
}
function usersExist (callback) {
- User.countTotal(function (err, totalUsers) {
+ db.User.countTotal(function (err, totalUsers) {
if (err) return callback(err)
return callback(null, totalUsers !== 0)
// Sortable columns per schema
const SEARCHABLE_COLUMNS = {
- VIDEOS: [ 'name', 'magnetUri', 'podHost', 'author', 'tags' ]
+ VIDEOS: [ 'name', 'magnetUri', 'host', 'author', 'tags' ]
}
// Sortable columns per schema
const SORTABLE_COLUMNS = {
- USERS: [ 'username', '-username', 'createdDate', '-createdDate' ],
- VIDEOS: [ 'name', '-name', 'duration', '-duration', 'createdDate', '-createdDate' ]
+ USERS: [ 'username', '-username', 'createdAt', '-createdAt' ],
+ VIDEOS: [ 'name', '-name', 'duration', '-duration', 'createdAt', '-createdAt' ]
}
const OAUTH_LIFETIME = {
VIDEOS: {
NAME: { min: 3, max: 50 }, // Length
DESCRIPTION: { min: 3, max: 250 }, // Length
- MAGNET: {
- INFO_HASH: { min: 10, max: 50 } // Length
- },
+ EXTNAME: [ '.mp4', '.ogv', '.webm' ],
+ INFO_HASH: { min: 10, max: 50 }, // Length
DURATION: { min: 1, max: 7200 }, // Number
TAGS: { min: 1, max: 3 }, // Number of total tags
TAG: { min: 2, max: 10 }, // Length
// ---------------------------------------------------------------------------
-const MONGO_MIGRATION_SCRIPTS = [
+const MIGRATION_SCRIPTS = [
{
script: '0005-create-application',
version: 5
version: 40
}
]
-const LAST_MONGO_SCHEMA_VERSION = (maxBy(MONGO_MIGRATION_SCRIPTS, 'version'))['version']
+const LAST_SQL_SCHEMA_VERSION = (maxBy(MIGRATION_SCRIPTS, 'version'))['version']
// ---------------------------------------------------------------------------
CONFIG,
CONSTRAINTS_FIELDS,
FRIEND_SCORE,
- LAST_MONGO_SCHEMA_VERSION,
- MONGO_MIGRATION_SCRIPTS,
+ LAST_SQL_SCHEMA_VERSION,
+ MIGRATION_SCRIPTS,
OAUTH_LIFETIME,
PAGINATION_COUNT_DEFAULT,
PODS_SCORE,
'use strict'
-const mongoose = require('mongoose')
+const fs = require('fs')
+const path = require('path')
+const Sequelize = require('sequelize')
const constants = require('../initializers/constants')
const logger = require('../helpers/logger')
-// Bootstrap models
-require('../models/application')
-require('../models/oauth-token')
-require('../models/user')
-require('../models/oauth-client')
-require('../models/video')
-// Request model needs Video model
-require('../models/pods')
-// Request model needs Pod model
-require('../models/request')
-
-const database = {
- connect: connect
-}
-
-function connect () {
- mongoose.Promise = global.Promise
- mongoose.connect('mongodb://' + constants.CONFIG.DATABASE.HOSTNAME + ':' + constants.CONFIG.DATABASE.PORT + '/' + constants.CONFIG.DATABASE.DBNAME)
- mongoose.connection.on('error', function () {
- throw new Error('Mongodb connection error.')
+const database = {}
+
+const sequelize = new Sequelize(constants.CONFIG.DATABASE.DBNAME, 'peertube', 'peertube', {
+ dialect: 'postgres',
+ host: constants.CONFIG.DATABASE.HOSTNAME,
+ port: constants.CONFIG.DATABASE.PORT
+})
+
+const modelDirectory = path.join(__dirname, '..', 'models')
+fs.readdir(modelDirectory, function (err, files) {
+ if (err) throw err
+
+ files.filter(function (file) {
+ if (file === 'utils.js') return false
+
+ return true
})
+ .forEach(function (file) {
+ const model = sequelize.import(path.join(modelDirectory, file))
- mongoose.connection.on('open', function () {
- logger.info('Connected to mongodb.')
+ database[model.name] = model
})
-}
+
+ Object.keys(database).forEach(function (modelName) {
+ if ('associate' in database[modelName]) {
+ database[modelName].associate(database)
+ }
+ })
+
+ logger.info('Database is ready.')
+})
+
+database.sequelize = sequelize
+database.Sequelize = Sequelize
// ---------------------------------------------------------------------------
const config = require('config')
const each = require('async/each')
const mkdirp = require('mkdirp')
-const mongoose = require('mongoose')
const passwordGenerator = require('password-generator')
const path = require('path')
const series = require('async/series')
const checker = require('./checker')
const constants = require('./constants')
+const db = require('./database')
const logger = require('../helpers/logger')
const peertubeCrypto = require('../helpers/peertube-crypto')
-const Application = mongoose.model('Application')
-const Client = mongoose.model('OAuthClient')
-const User = mongoose.model('User')
-
const installer = {
installApplication
}
function installApplication (callback) {
series([
+ function createDatabase (callbackAsync) {
+ db.sequelize.sync().asCallback(callbackAsync)
+ // db.sequelize.sync({ force: true }).asCallback(callbackAsync)
+ },
+
function createDirectories (callbackAsync) {
createDirectoriesIfNotExist(callbackAsync)
},
logger.info('Creating a default OAuth Client.')
- const secret = passwordGenerator(32, false)
- const client = new Client({
+ const id = passwordGenerator(32, false, /[a-z0-9]/)
+ const secret = passwordGenerator(32, false, /[a-zA-Z0-9]/)
+ const client = db.OAuthClient.build({
+ clientId: id,
clientSecret: secret,
grants: [ 'password', 'refresh_token' ]
})
- client.save(function (err, createdClient) {
+ client.save().asCallback(function (err, createdClient) {
if (err) return callback(err)
- logger.info('Client id: ' + createdClient._id)
+ logger.info('Client id: ' + createdClient.clientId)
logger.info('Client secret: ' + createdClient.clientSecret)
return callback(null)
password = passwordGenerator(8, true)
}
- const user = new User({
+ const user = db.User.build({
username,
password,
role
})
- user.save(function (err, createdUser) {
+ user.save().asCallback(function (err, createdUser) {
if (err) return callback(err)
logger.info('Username: ' + username)
logger.info('User password: ' + password)
logger.info('Creating Application collection.')
- const application = new Application({ mongoSchemaVersion: constants.LAST_MONGO_SCHEMA_VERSION })
- application.save(callback)
+ const application = db.Application.build({ sqlSchemaVersion: constants.LAST_SQL_SCHEMA_VERSION })
+ application.save().asCallback(callback)
})
})
}
'use strict'
const eachSeries = require('async/eachSeries')
-const mongoose = require('mongoose')
const path = require('path')
const constants = require('./constants')
+const db = require('./database')
const logger = require('../helpers/logger')
-const Application = mongoose.model('Application')
-
const migrator = {
migrate: migrate
}
function migrate (callback) {
- Application.loadMongoSchemaVersion(function (err, actualVersion) {
+ db.Application.loadSqlSchemaVersion(function (err, actualVersion) {
if (err) return callback(err)
// If there are a new mongo schemas
- if (!actualVersion || actualVersion < constants.LAST_MONGO_SCHEMA_VERSION) {
+ if (!actualVersion || actualVersion < constants.LAST_SQL_SCHEMA_VERSION) {
logger.info('Begin migrations.')
eachSeries(constants.MONGO_MIGRATION_SCRIPTS, function (entity, callbackEach) {
if (err) return callbackEach(err)
// Update the new mongo version schema
- Application.updateMongoSchemaVersion(versionScript, callbackEach)
+ db.Application.updateSqlSchemaVersion(versionScript, callbackEach)
})
}, function (err) {
if (err) return callback(err)
- logger.info('Migrations finished. New mongo version schema: %s', constants.LAST_MONGO_SCHEMA_VERSION)
+ logger.info('Migrations finished. New SQL version schema: %s', constants.LAST_SQL_SCHEMA_VERSION)
return callback(null)
})
} else {
const eachLimit = require('async/eachLimit')
const eachSeries = require('async/eachSeries')
const fs = require('fs')
-const mongoose = require('mongoose')
const request = require('request')
const waterfall = require('async/waterfall')
const constants = require('../initializers/constants')
+const db = require('../initializers/database')
const logger = require('../helpers/logger')
const requests = require('../helpers/requests')
-const Pod = mongoose.model('Pod')
-const Request = mongoose.model('Request')
-const Video = mongoose.model('Video')
-
const friends = {
addVideoToFriends,
hasFriends,
}
function hasFriends (callback) {
- Pod.countAll(function (err, count) {
+ db.Pod.countAll(function (err, count) {
if (err) return callback(err)
const hasFriends = (count !== 0)
function quitFriends (callback) {
// Stop pool requests
- Request.deactivate()
+ db.Request.deactivate()
// Flush pool requests
- Request.flush()
+ db.Request.flush()
waterfall([
function getPodsList (callbackAsync) {
- return Pod.list(callbackAsync)
+ return db.Pod.list(callbackAsync)
},
function announceIQuitMyFriends (pods, callbackAsync) {
function removePodsFromDB (pods, callbackAsync) {
each(pods, function (pod, callbackEach) {
- pod.remove(callbackEach)
+ pod.destroy().asCallback(callbackEach)
}, callbackAsync)
}
], function (err) {
// Don't forget to re activate the scheduler, even if there was an error
- Request.activate()
+ db.Request.activate()
if (err) return callback(err)
}
function sendOwnedVideosToPod (podId) {
- Video.listOwned(function (err, videosList) {
+ db.Video.listOwnedAndPopulateAuthor(function (err, videosList) {
if (err) {
logger.error('Cannot get the list of videos we own.')
return
function makeRequestsToWinningPods (cert, podsList, callback) {
// Stop pool requests
- Request.deactivate()
+ db.Request.deactivate()
// Flush pool requests
- Request.forceSend()
+ db.Request.forceSend()
eachLimit(podsList, constants.REQUESTS_IN_PARALLEL, function (pod, callbackEach) {
const params = {
}
if (res.statusCode === 200) {
- const podObj = new Pod({ host: pod.host, publicKey: body.cert })
- podObj.save(function (err, podCreated) {
+ const podObj = db.Pod.build({ host: pod.host, publicKey: body.cert })
+ podObj.save().asCallback(function (err, podCreated) {
if (err) {
logger.error('Cannot add friend %s pod.', pod.host, { error: err })
return callbackEach()
}, function endRequests () {
// Final callback, we've ended all the requests
// Now we made new friends, we can re activate the pool of requests
- Request.activate()
+ db.Request.activate()
logger.debug('makeRequestsToWinningPods finished.')
return callback()
})
}
+// Wrapper that populate "to" argument with all our friends if it is not specified
function createRequest (type, endpoint, data, to) {
- const req = new Request({
+ if (to) return _createRequest(type, endpoint, data, to)
+
+ // If the "to" pods is not specified, we send the request to all our friends
+ db.Pod.listAllIds(function (err, podIds) {
+ if (err) {
+ logger.error('Cannot get pod ids', { error: err })
+ return
+ }
+
+ return _createRequest(type, endpoint, data, podIds)
+ })
+}
+
+function _createRequest (type, endpoint, data, to) {
+ const pods = []
+
+ // If there are no destination pods abort
+ if (to.length === 0) return
+
+ to.forEach(function (toPod) {
+ pods.push(db.Pod.build({ id: toPod }))
+ })
+
+ const createQuery = {
endpoint,
request: {
type: type,
data: data
}
- })
-
- if (to) {
- req.to = to
}
- req.save(function (err) {
- if (err) logger.error('Cannot save the request.', { error: err })
+ // We run in transaction to keep coherency between Request and RequestToPod tables
+ db.sequelize.transaction(function (t) {
+ const dbRequestOptions = {
+ transaction: t
+ }
+
+ return db.Request.create(createQuery, dbRequestOptions).then(function (request) {
+ return request.setPods(pods, dbRequestOptions)
+ })
+ }).asCallback(function (err) {
+ if (err) logger.error('Error in createRequest transaction.', { error: err })
})
}
-const mongoose = require('mongoose')
-
+const db = require('../initializers/database')
const logger = require('../helpers/logger')
-const OAuthClient = mongoose.model('OAuthClient')
-const OAuthToken = mongoose.model('OAuthToken')
-const User = mongoose.model('User')
-
// See https://github.com/oauthjs/node-oauth2-server/wiki/Model-specification for the model specifications
const OAuthModel = {
getAccessToken,
function getAccessToken (bearerToken) {
logger.debug('Getting access token (bearerToken: ' + bearerToken + ').')
- return OAuthToken.getByTokenAndPopulateUser(bearerToken)
+ return db.OAuthToken.getByTokenAndPopulateUser(bearerToken)
}
function getClient (clientId, clientSecret) {
logger.debug('Getting Client (clientId: ' + clientId + ', clientSecret: ' + clientSecret + ').')
- // TODO req validator
- const mongoId = new mongoose.mongo.ObjectID(clientId)
- return OAuthClient.getByIdAndSecret(mongoId, clientSecret)
+ return db.OAuthClient.getByIdAndSecret(clientId, clientSecret)
}
function getRefreshToken (refreshToken) {
logger.debug('Getting RefreshToken (refreshToken: ' + refreshToken + ').')
- return OAuthToken.getByRefreshTokenAndPopulateClient(refreshToken)
+ return db.OAuthToken.getByRefreshTokenAndPopulateClient(refreshToken)
}
function getUser (username, password) {
logger.debug('Getting User (username: ' + username + ', password: ' + password + ').')
- return User.getByUsername(username).then(function (user) {
+ return db.User.getByUsername(username).then(function (user) {
if (!user) return null
// We need to return a promise
}
function revokeToken (token) {
- return OAuthToken.getByRefreshTokenAndPopulateUser(token.refreshToken).then(function (tokenDB) {
- if (tokenDB) tokenDB.remove()
+ return db.OAuthToken.getByRefreshTokenAndPopulateUser(token.refreshToken).then(function (tokenDB) {
+ if (tokenDB) tokenDB.destroy()
/*
* Thanks to https://github.com/manjeshpv/node-oauth2-server-implementation/blob/master/components/oauth/mongo-models.js
function saveToken (token, client, user) {
logger.debug('Saving token ' + token.accessToken + ' for client ' + client.id + ' and user ' + user.id + '.')
- const tokenObj = new OAuthToken({
+ const tokenToCreate = {
accessToken: token.accessToken,
accessTokenExpiresAt: token.accessTokenExpiresAt,
- client: client.id,
refreshToken: token.refreshToken,
refreshTokenExpiresAt: token.refreshTokenExpiresAt,
- user: user.id
- })
+ oAuthClientId: client.id,
+ userId: user.id
+ }
- return tokenObj.save().then(function (tokenCreated) {
+ return db.OAuthToken.create(tokenToCreate).then(function (tokenCreated) {
tokenCreated.client = client
tokenCreated.user = user
+
return tokenCreated
}).catch(function (err) {
throw err
function getHostWithPort (host) {
const splitted = host.split(':')
- console.log(splitted)
// The port was not specified
if (splitted.length === 1) {
if (constants.REMOTE_SCHEME.HTTP === 'https') return host + ':443'
'use strict'
+const db = require('../initializers/database')
const logger = require('../helpers/logger')
-const mongoose = require('mongoose')
const peertubeCrypto = require('../helpers/peertube-crypto')
-const Pod = mongoose.model('Pod')
-
const secureMiddleware = {
checkSignature
}
function checkSignature (req, res, next) {
const host = req.body.signature.host
- Pod.loadByHost(host, function (err, pod) {
+ db.Pod.loadByHost(host, function (err, pod) {
if (err) {
logger.error('Cannot get signed host in body.', { error: err })
return res.sendStatus(500)
}
function setUsersSort (req, res, next) {
- if (!req.query.sort) req.query.sort = '-createdDate'
+ if (!req.query.sort) req.query.sort = '-createdAt'
return next()
}
function setVideosSort (req, res, next) {
- if (!req.query.sort) req.query.sort = '-createdDate'
+ if (!req.query.sort) req.query.sort = '-createdAt'
return next()
}
'use strict'
-const mongoose = require('mongoose')
-
const checkErrors = require('./utils').checkErrors
+const db = require('../../initializers/database')
const logger = require('../../helpers/logger')
-const User = mongoose.model('User')
-
const validatorsUsers = {
usersAdd,
usersRemove,
logger.debug('Checking usersAdd parameters', { parameters: req.body })
checkErrors(req, res, function () {
- User.loadByUsername(req.body.username, function (err, user) {
+ db.User.loadByUsername(req.body.username, function (err, user) {
if (err) {
logger.error('Error in usersAdd request validator.', { error: err })
return res.sendStatus(500)
}
function usersRemove (req, res, next) {
- req.checkParams('id', 'Should have a valid id').notEmpty().isMongoId()
+ req.checkParams('id', 'Should have a valid id').notEmpty().isInt()
logger.debug('Checking usersRemove parameters', { parameters: req.params })
checkErrors(req, res, function () {
- User.loadById(req.params.id, function (err, user) {
+ db.User.loadById(req.params.id, function (err, user) {
if (err) {
logger.error('Error in usersRemove request validator.', { error: err })
return res.sendStatus(500)
}
function usersUpdate (req, res, next) {
- req.checkParams('id', 'Should have a valid id').notEmpty().isMongoId()
+ req.checkParams('id', 'Should have a valid id').notEmpty().isInt()
// Add old password verification
req.checkBody('password', 'Should have a valid password').isUserPasswordValid()
'use strict'
-const mongoose = require('mongoose')
-
const checkErrors = require('./utils').checkErrors
const constants = require('../../initializers/constants')
const customVideosValidators = require('../../helpers/custom-validators').videos
+const db = require('../../initializers/database')
const logger = require('../../helpers/logger')
-const Video = mongoose.model('Video')
-
const validatorsVideos = {
videosAdd,
videosGet,
checkErrors(req, res, function () {
const videoFile = req.files.videofile[0]
- Video.getDurationFromFile(videoFile.path, function (err, duration) {
+ db.Video.getDurationFromFile(videoFile.path, function (err, duration) {
if (err) {
return res.status(400).send('Cannot retrieve metadata of the file.')
}
}
function videosGet (req, res, next) {
- req.checkParams('id', 'Should have a valid id').notEmpty().isMongoId()
+ req.checkParams('id', 'Should have a valid id').notEmpty().isUUID(4)
logger.debug('Checking videosGet parameters', { parameters: req.params })
checkErrors(req, res, function () {
- Video.load(req.params.id, function (err, video) {
+ db.Video.load(req.params.id, function (err, video) {
if (err) {
logger.error('Error in videosGet request validator.', { error: err })
return res.sendStatus(500)
}
function videosRemove (req, res, next) {
- req.checkParams('id', 'Should have a valid id').notEmpty().isMongoId()
+ req.checkParams('id', 'Should have a valid id').notEmpty().isUUID(4)
logger.debug('Checking videosRemove parameters', { parameters: req.params })
checkErrors(req, res, function () {
- Video.load(req.params.id, function (err, video) {
+ db.Video.loadAndPopulateAuthor(req.params.id, function (err, video) {
if (err) {
logger.error('Error in videosRemove request validator.', { error: err })
return res.sendStatus(500)
if (!video) return res.status(404).send('Video not found')
else if (video.isOwned() === false) return res.status(403).send('Cannot remove video of another pod')
- else if (video.author !== res.locals.oauth.token.user.username) return res.status(403).send('Cannot remove video of another user')
+ else if (video.Author.name !== res.locals.oauth.token.user.username) return res.status(403).send('Cannot remove video of another user')
next()
})
-const mongoose = require('mongoose')
+module.exports = function (sequelize, DataTypes) {
+ const Application = sequelize.define('Application',
+ {
+ sqlSchemaVersion: {
+ type: DataTypes.INTEGER,
+ defaultValue: 0
+ }
+ },
+ {
+ classMethods: {
+ loadSqlSchemaVersion,
+ updateSqlSchemaVersion
+ }
+ }
+ )
+
+ return Application
+}
// ---------------------------------------------------------------------------
-const ApplicationSchema = mongoose.Schema({
- mongoSchemaVersion: {
- type: Number,
- default: 0
+function loadSqlSchemaVersion (callback) {
+ const query = {
+ attributes: [ 'sqlSchemaVersion' ]
}
-})
-
-ApplicationSchema.statics = {
- loadMongoSchemaVersion,
- updateMongoSchemaVersion
-}
-
-mongoose.model('Application', ApplicationSchema)
-
-// ---------------------------------------------------------------------------
-function loadMongoSchemaVersion (callback) {
- return this.findOne({}, { mongoSchemaVersion: 1 }, function (err, data) {
- const version = data ? data.mongoSchemaVersion : 0
+ return this.findOne(query).asCallback(function (err, data) {
+ const version = data ? data.sqlSchemaVersion : 0
return callback(err, version)
})
}
-function updateMongoSchemaVersion (newVersion, callback) {
- return this.update({}, { mongoSchemaVersion: newVersion }, callback)
+function updateSqlSchemaVersion (newVersion, callback) {
+ return this.update({ sqlSchemaVersion: newVersion }).asCallback(callback)
}
--- /dev/null
+module.exports = function (sequelize, DataTypes) {
+ const Author = sequelize.define('Author',
+ {
+ name: {
+ type: DataTypes.STRING
+ }
+ },
+ {
+ classMethods: {
+ associate
+ }
+ }
+ )
+
+ return Author
+}
+
+// ---------------------------------------------------------------------------
+
+function associate (models) {
+ this.belongsTo(models.Pod, {
+ foreignKey: {
+ name: 'podId',
+ allowNull: true
+ },
+ onDelete: 'cascade'
+ })
+}
-const mongoose = require('mongoose')
-
-// ---------------------------------------------------------------------------
-
-const OAuthClientSchema = mongoose.Schema({
- clientSecret: String,
- grants: Array,
- redirectUris: Array
-})
-
-OAuthClientSchema.path('clientSecret').required(true)
-
-OAuthClientSchema.statics = {
- getByIdAndSecret,
- list,
- loadFirstClient
+module.exports = function (sequelize, DataTypes) {
+ const OAuthClient = sequelize.define('OAuthClient',
+ {
+ clientId: {
+ type: DataTypes.STRING
+ },
+ clientSecret: {
+ type: DataTypes.STRING
+ },
+ grants: {
+ type: DataTypes.ARRAY(DataTypes.STRING)
+ },
+ redirectUris: {
+ type: DataTypes.ARRAY(DataTypes.STRING)
+ }
+ },
+ {
+ classMethods: {
+ associate,
+
+ getByIdAndSecret,
+ list,
+ loadFirstClient
+ }
+ }
+ )
+
+ return OAuthClient
}
-mongoose.model('OAuthClient', OAuthClientSchema)
+// TODO: validation
+// OAuthClientSchema.path('clientSecret').required(true)
// ---------------------------------------------------------------------------
+function associate (models) {
+ this.hasMany(models.OAuthToken, {
+ foreignKey: {
+ name: 'oAuthClientId',
+ allowNull: false
+ },
+ onDelete: 'cascade'
+ })
+}
+
function list (callback) {
- return this.find(callback)
+ return this.findAll().asCallback(callback)
}
function loadFirstClient (callback) {
- return this.findOne({}, callback)
+ return this.findOne().asCallback(callback)
}
-function getByIdAndSecret (id, clientSecret) {
- return this.findOne({ _id: id, clientSecret: clientSecret }).exec()
+function getByIdAndSecret (clientId, clientSecret) {
+ const query = {
+ where: {
+ clientId: clientId,
+ clientSecret: clientSecret
+ }
+ }
+
+ return this.findOne(query)
}
-const mongoose = require('mongoose')
-
const logger = require('../helpers/logger')
// ---------------------------------------------------------------------------
-const OAuthTokenSchema = mongoose.Schema({
- accessToken: String,
- accessTokenExpiresAt: Date,
- client: { type: mongoose.Schema.Types.ObjectId, ref: 'OAuthClient' },
- refreshToken: String,
- refreshTokenExpiresAt: Date,
- user: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }
-})
-
-OAuthTokenSchema.path('accessToken').required(true)
-OAuthTokenSchema.path('client').required(true)
-OAuthTokenSchema.path('user').required(true)
-
-OAuthTokenSchema.statics = {
- getByRefreshTokenAndPopulateClient,
- getByTokenAndPopulateUser,
- getByRefreshTokenAndPopulateUser,
- removeByUserId
+module.exports = function (sequelize, DataTypes) {
+ const OAuthToken = sequelize.define('OAuthToken',
+ {
+ accessToken: {
+ type: DataTypes.STRING
+ },
+ accessTokenExpiresAt: {
+ type: DataTypes.DATE
+ },
+ refreshToken: {
+ type: DataTypes.STRING
+ },
+ refreshTokenExpiresAt: {
+ type: DataTypes.DATE
+ }
+ },
+ {
+ classMethods: {
+ associate,
+
+ getByRefreshTokenAndPopulateClient,
+ getByTokenAndPopulateUser,
+ getByRefreshTokenAndPopulateUser,
+ removeByUserId
+ }
+ }
+ )
+
+ return OAuthToken
}
-mongoose.model('OAuthToken', OAuthTokenSchema)
+// TODO: validation
+// OAuthTokenSchema.path('accessToken').required(true)
+// OAuthTokenSchema.path('client').required(true)
+// OAuthTokenSchema.path('user').required(true)
// ---------------------------------------------------------------------------
+function associate (models) {
+ this.belongsTo(models.User, {
+ foreignKey: {
+ name: 'userId',
+ allowNull: false
+ },
+ onDelete: 'cascade'
+ })
+}
+
function getByRefreshTokenAndPopulateClient (refreshToken) {
- return this.findOne({ refreshToken: refreshToken }).populate('client').exec().then(function (token) {
+ const query = {
+ where: {
+ refreshToken: refreshToken
+ },
+ include: [ this.associations.OAuthClient ]
+ }
+
+ return this.findOne(query).then(function (token) {
if (!token) return token
const tokenInfos = {
refreshToken: token.refreshToken,
refreshTokenExpiresAt: token.refreshTokenExpiresAt,
client: {
- id: token.client._id.toString()
+ id: token.client.id
},
user: {
id: token.user
}
function getByTokenAndPopulateUser (bearerToken) {
- return this.findOne({ accessToken: bearerToken }).populate('user').exec()
+ const query = {
+ where: {
+ accessToken: bearerToken
+ },
+ include: [ this.sequelize.models.User ]
+ }
+
+ return this.findOne(query).then(function (token) {
+ if (token) token.user = token.User
+
+ return token
+ })
}
function getByRefreshTokenAndPopulateUser (refreshToken) {
- return this.findOne({ refreshToken: refreshToken }).populate('user').exec()
+ const query = {
+ where: {
+ refreshToken: refreshToken
+ },
+ include: [ this.sequelize.models.User ]
+ }
+
+ return this.findOne(query).then(function (token) {
+ token.user = token.User
+
+ return token
+ })
}
function removeByUserId (userId, callback) {
- return this.remove({ user: userId }, callback)
+ const query = {
+ where: {
+ userId: userId
+ }
+ }
+
+ return this.destroy(query).asCallback(callback)
}
'use strict'
-const each = require('async/each')
-const mongoose = require('mongoose')
const map = require('lodash/map')
-const validator = require('express-validator').validator
const constants = require('../initializers/constants')
-const Video = mongoose.model('Video')
-
// ---------------------------------------------------------------------------
-const PodSchema = mongoose.Schema({
- host: String,
- publicKey: String,
- score: { type: Number, max: constants.FRIEND_SCORE.MAX },
- createdDate: {
- type: Date,
- default: Date.now
- }
-})
-
-PodSchema.path('host').validate(validator.isURL)
-PodSchema.path('publicKey').required(true)
-PodSchema.path('score').validate(function (value) { return !isNaN(value) })
-
-PodSchema.methods = {
- toFormatedJSON
+module.exports = function (sequelize, DataTypes) {
+ const Pod = sequelize.define('Pod',
+ {
+ host: {
+ type: DataTypes.STRING
+ },
+ publicKey: {
+ type: DataTypes.STRING(5000)
+ },
+ score: {
+ type: DataTypes.INTEGER,
+ defaultValue: constants.FRIEND_SCORE.BASE
+ }
+ // Check createdAt
+ },
+ {
+ classMethods: {
+ associate,
+
+ countAll,
+ incrementScores,
+ list,
+ listAllIds,
+ listBadPods,
+ load,
+ loadByHost,
+ removeAll
+ },
+ instanceMethods: {
+ toFormatedJSON
+ }
+ }
+ )
+
+ return Pod
}
-PodSchema.statics = {
- countAll,
- incrementScores,
- list,
- listAllIds,
- listBadPods,
- load,
- loadByHost,
- removeAll
-}
-
-PodSchema.pre('save', function (next) {
- const self = this
-
- Pod.loadByHost(this.host, function (err, pod) {
- if (err) return next(err)
-
- if (pod) return next(new Error('Pod already exists.'))
-
- self.score = constants.FRIEND_SCORE.BASE
- return next()
- })
-})
-
-PodSchema.pre('remove', function (next) {
- // Remove the videos owned by this pod too
- Video.listByHost(this.host, function (err, videos) {
- if (err) return next(err)
-
- each(videos, function (video, callbackEach) {
- video.remove(callbackEach)
- }, next)
- })
-})
-
-const Pod = mongoose.model('Pod', PodSchema)
+// TODO: max score -> constants.FRIENDS_SCORE.MAX
+// TODO: validation
+// PodSchema.path('host').validate(validator.isURL)
+// PodSchema.path('publicKey').required(true)
+// PodSchema.path('score').validate(function (value) { return !isNaN(value) })
// ------------------------------ METHODS ------------------------------
function toFormatedJSON () {
const json = {
- id: this._id,
+ id: this.id,
host: this.host,
score: this.score,
- createdDate: this.createdDate
+ createdAt: this.createdAt
}
return json
// ------------------------------ Statics ------------------------------
+function associate (models) {
+ this.belongsToMany(models.Request, {
+ foreignKey: 'podId',
+ through: models.RequestToPod,
+ onDelete: 'CASCADE'
+ })
+}
+
function countAll (callback) {
- return this.count(callback)
+ return this.count().asCallback(callback)
}
function incrementScores (ids, value, callback) {
if (!callback) callback = function () {}
- return this.update({ _id: { $in: ids } }, { $inc: { score: value } }, { multi: true }, callback)
+
+ const update = {
+ score: this.sequelize.literal('score +' + value)
+ }
+
+ const query = {
+ where: {
+ id: {
+ $in: ids
+ }
+ }
+ }
+
+ return this.update(update, query).asCallback(callback)
}
function list (callback) {
- return this.find(callback)
+ return this.findAll().asCallback(callback)
}
function listAllIds (callback) {
- return this.find({}, { _id: 1 }, function (err, pods) {
+ const query = {
+ attributes: [ 'id' ]
+ }
+
+ return this.findAll(query).asCallback(function (err, pods) {
if (err) return callback(err)
- return callback(null, map(pods, '_id'))
+ return callback(null, map(pods, 'id'))
})
}
function listBadPods (callback) {
- return this.find({ score: 0 }, callback)
+ const query = {
+ where: {
+ score: { $lte: 0 }
+ }
+ }
+
+ return this.findAll(query).asCallback(callback)
}
function load (id, callback) {
- return this.findById(id, callback)
+ return this.findById(id).asCallback(callback)
}
function loadByHost (host, callback) {
- return this.findOne({ host }, callback)
+ const query = {
+ where: {
+ host: host
+ }
+ }
+
+ return this.findOne(query).asCallback(callback)
}
function removeAll (callback) {
- return this.remove({}, callback)
+ return this.destroy().asCallback(callback)
}
const each = require('async/each')
const eachLimit = require('async/eachLimit')
-const values = require('lodash/values')
-const mongoose = require('mongoose')
const waterfall = require('async/waterfall')
const constants = require('../initializers/constants')
const logger = require('../helpers/logger')
const requests = require('../helpers/requests')
-const Pod = mongoose.model('Pod')
-
let timer = null
let lastRequestTimestamp = 0
// ---------------------------------------------------------------------------
-const RequestSchema = mongoose.Schema({
- request: mongoose.Schema.Types.Mixed,
- endpoint: {
- type: String,
- enum: [ values(constants.REQUEST_ENDPOINTS) ]
- },
- to: [
+module.exports = function (sequelize, DataTypes) {
+ const Request = sequelize.define('Request',
+ {
+ request: {
+ type: DataTypes.JSON
+ },
+ endpoint: {
+ // TODO: enum?
+ type: DataTypes.STRING
+ }
+ },
{
- type: mongoose.Schema.Types.ObjectId,
- ref: 'Pod'
+ classMethods: {
+ associate,
+
+ activate,
+ countTotalRequests,
+ deactivate,
+ flush,
+ forceSend,
+ remainingMilliSeconds
+ }
}
- ]
-})
-
-RequestSchema.statics = {
- activate,
- deactivate,
- flush,
- forceSend,
- list,
- remainingMilliSeconds
-}
-
-RequestSchema.pre('save', function (next) {
- const self = this
-
- if (self.to.length === 0) {
- Pod.listAllIds(function (err, podIds) {
- if (err) return next(err)
-
- // No friends
- if (podIds.length === 0) return
-
- self.to = podIds
- return next()
- })
- } else {
- return next()
- }
-})
+ )
-mongoose.model('Request', RequestSchema)
+ return Request
+}
// ------------------------------ STATICS ------------------------------
+function associate (models) {
+ this.belongsToMany(models.Pod, {
+ foreignKey: {
+ name: 'requestId',
+ allowNull: false
+ },
+ through: models.RequestToPod,
+ onDelete: 'CASCADE'
+ })
+}
+
function activate () {
logger.info('Requests scheduler activated.')
lastRequestTimestamp = Date.now()
}, constants.REQUESTS_INTERVAL)
}
+function countTotalRequests (callback) {
+ const query = {
+ include: [ this.sequelize.models.Pod ]
+ }
+
+ return this.count(query).asCallback(callback)
+}
+
function deactivate () {
logger.info('Requests scheduler deactivated.')
clearInterval(timer)
makeRequests.call(this)
}
-function list (callback) {
- this.find({ }, callback)
-}
-
function remainingMilliSeconds () {
if (timer === null) return -1
// Make all the requests of the scheduler
function makeRequests () {
const self = this
+ const RequestToPod = this.sequelize.models.RequestToPod
// We limit the size of the requests (REQUESTS_LIMIT)
// We don't want to stuck with the same failing requests so we get a random list
// We want to group requests by destinations pod and endpoint
const requestsToMakeGrouped = {}
- requests.forEach(function (poolRequest) {
- poolRequest.to.forEach(function (toPodId) {
- const hashKey = toPodId + poolRequest.endpoint
+ requests.forEach(function (request) {
+ request.Pods.forEach(function (toPod) {
+ const hashKey = toPod.id + request.endpoint
if (!requestsToMakeGrouped[hashKey]) {
requestsToMakeGrouped[hashKey] = {
- toPodId,
- endpoint: poolRequest.endpoint,
- ids: [], // pool request ids, to delete them from the DB in the future
+ toPodId: toPod.id,
+ endpoint: request.endpoint,
+ ids: [], // request ids, to delete them from the DB in the future
datas: [] // requests data,
}
}
- requestsToMakeGrouped[hashKey].ids.push(poolRequest._id)
- requestsToMakeGrouped[hashKey].datas.push(poolRequest.request)
+ requestsToMakeGrouped[hashKey].ids.push(request.id)
+ requestsToMakeGrouped[hashKey].datas.push(request.request)
})
})
eachLimit(Object.keys(requestsToMakeGrouped), constants.REQUESTS_IN_PARALLEL, function (hashKey, callbackEach) {
const requestToMake = requestsToMakeGrouped[hashKey]
- // FIXME: mongodb request inside a loop :/
- Pod.load(requestToMake.toPodId, function (err, toPod) {
+ // FIXME: SQL request inside a loop :/
+ self.sequelize.models.Pod.load(requestToMake.toPodId, function (err, toPod) {
if (err) {
logger.error('Error finding pod by id.', { err: err })
return callbackEach()
const requestIdsToDelete = requestToMake.ids
logger.info('Removing %d requests of unexisting pod %s.', requestIdsToDelete.length, requestToMake.toPodId)
- removePodOf.call(self, requestIdsToDelete, requestToMake.toPodId)
+ RequestToPod.removePodOf.call(self, requestIdsToDelete, requestToMake.toPodId)
return callbackEach()
}
goodPods.push(requestToMake.toPodId)
// Remove the pod id of these request ids
- removePodOf.call(self, requestToMake.ids, requestToMake.toPodId, callbackEach)
+ RequestToPod.removePodOf(requestToMake.ids, requestToMake.toPodId, callbackEach)
} else {
badPods.push(requestToMake.toPodId)
callbackEach()
})
}, function () {
// All the requests were made, we update the pods score
- updatePodsScore(goodPods, badPods)
+ updatePodsScore.call(self, goodPods, badPods)
// Flush requests with no pod
- removeWithEmptyTo.call(self)
+ removeWithEmptyTo.call(self, function (err) {
+ if (err) logger.error('Error when removing requests with no pods.', { error: err })
+ })
})
})
}
// Remove pods with a score of 0 (too many requests where they were unreachable)
function removeBadPods () {
+ const self = this
+
waterfall([
function findBadPods (callback) {
- Pod.listBadPods(function (err, pods) {
+ self.sequelize.models.Pod.listBadPods(function (err, pods) {
if (err) {
logger.error('Cannot find bad pods.', { error: err })
return callback(err)
},
function removeTheseBadPods (pods, callback) {
- if (pods.length === 0) return callback(null, 0)
-
each(pods, function (pod, callbackEach) {
- pod.remove(callbackEach)
+ pod.destroy().asCallback(callbackEach)
}, function (err) {
return callback(err, pods.length)
})
}
function updatePodsScore (goodPods, badPods) {
+ const self = this
+ const Pod = this.sequelize.models.Pod
+
logger.info('Updating %d good pods and %d bad pods scores.', goodPods.length, badPods.length)
- Pod.incrementScores(goodPods, constants.PODS_SCORE.BONUS, function (err) {
- if (err) logger.error('Cannot increment scores of good pods.')
- })
+ if (goodPods.length !== 0) {
+ Pod.incrementScores(goodPods, constants.PODS_SCORE.BONUS, function (err) {
+ if (err) logger.error('Cannot increment scores of good pods.')
+ })
+ }
- Pod.incrementScores(badPods, constants.PODS_SCORE.MALUS, function (err) {
- if (err) logger.error('Cannot decrement scores of bad pods.')
- removeBadPods()
- })
+ if (badPods.length !== 0) {
+ Pod.incrementScores(badPods, constants.PODS_SCORE.MALUS, function (err) {
+ if (err) logger.error('Cannot decrement scores of bad pods.')
+ removeBadPods.call(self)
+ })
+ }
}
function listWithLimitAndRandom (limit, callback) {
const self = this
- self.count(function (err, count) {
+ self.count().asCallback(function (err, count) {
if (err) return callback(err)
+ // Optimization...
+ if (count === 0) return callback(null, [])
+
let start = Math.floor(Math.random() * count) - limit
if (start < 0) start = 0
- self.find().sort({ _id: 1 }).skip(start).limit(limit).exec(callback)
+ const query = {
+ order: [
+ [ 'id', 'ASC' ]
+ ],
+ offset: start,
+ limit: limit,
+ include: [ this.sequelize.models.Pod ]
+ }
+
+ self.findAll(query).asCallback(callback)
})
}
function removeAll (callback) {
- this.remove({ }, callback)
-}
-
-function removePodOf (requestsIds, podId, callback) {
- if (!callback) callback = function () {}
-
- this.update({ _id: { $in: requestsIds } }, { $pull: { to: podId } }, { multi: true }, callback)
+ // Delete all requests
+ this.destroy({ truncate: true }).asCallback(callback)
}
function removeWithEmptyTo (callback) {
if (!callback) callback = function () {}
- this.remove({ to: { $size: 0 } }, callback)
+ const query = {
+ where: {
+ id: {
+ $notIn: [
+ this.sequelize.literal('SELECT "requestId" FROM "RequestToPods"')
+ ]
+ }
+ }
+ }
+
+ this.destroy(query).asCallback(callback)
}
--- /dev/null
+'use strict'
+
+// ---------------------------------------------------------------------------
+
+module.exports = function (sequelize, DataTypes) {
+ const RequestToPod = sequelize.define('RequestToPod', {}, {
+ classMethods: {
+ removePodOf
+ }
+ })
+
+ return RequestToPod
+}
+
+// ---------------------------------------------------------------------------
+
+function removePodOf (requestsIds, podId, callback) {
+ if (!callback) callback = function () {}
+
+ const query = {
+ where: {
+ requestId: {
+ $in: requestsIds
+ },
+ podId: podId
+ }
+ }
+
+ this.destroy(query).asCallback(callback)
+}
-const mongoose = require('mongoose')
-
-const customUsersValidators = require('../helpers/custom-validators').users
const modelUtils = require('./utils')
const peertubeCrypto = require('../helpers/peertube-crypto')
-const OAuthToken = mongoose.model('OAuthToken')
-
// ---------------------------------------------------------------------------
-const UserSchema = mongoose.Schema({
- createdDate: {
- type: Date,
- default: Date.now
- },
- password: String,
- username: String,
- role: String
-})
-
-UserSchema.path('password').required(customUsersValidators.isUserPasswordValid)
-UserSchema.path('username').required(customUsersValidators.isUserUsernameValid)
-UserSchema.path('role').validate(customUsersValidators.isUserRoleValid)
-
-UserSchema.methods = {
- isPasswordMatch,
- toFormatedJSON
+module.exports = function (sequelize, DataTypes) {
+ const User = sequelize.define('User',
+ {
+ password: {
+ type: DataTypes.STRING
+ },
+ username: {
+ type: DataTypes.STRING
+ },
+ role: {
+ type: DataTypes.STRING
+ }
+ },
+ {
+ classMethods: {
+ associate,
+
+ countTotal,
+ getByUsername,
+ list,
+ listForApi,
+ loadById,
+ loadByUsername
+ },
+ instanceMethods: {
+ isPasswordMatch,
+ toFormatedJSON
+ },
+ hooks: {
+ beforeCreate: beforeCreateOrUpdate,
+ beforeUpdate: beforeCreateOrUpdate
+ }
+ }
+ )
+
+ return User
}
-UserSchema.statics = {
- countTotal,
- getByUsername,
- list,
- listForApi,
- loadById,
- loadByUsername
-}
+// TODO: Validation
+// UserSchema.path('password').required(customUsersValidators.isUserPasswordValid)
+// UserSchema.path('username').required(customUsersValidators.isUserUsernameValid)
+// UserSchema.path('role').validate(customUsersValidators.isUserRoleValid)
-UserSchema.pre('save', function (next) {
- const user = this
-
- peertubeCrypto.cryptPassword(this.password, function (err, hash) {
+function beforeCreateOrUpdate (user, options, next) {
+ peertubeCrypto.cryptPassword(user.password, function (err, hash) {
if (err) return next(err)
user.password = hash
return next()
})
-})
-
-UserSchema.pre('remove', function (next) {
- const user = this
-
- OAuthToken.removeByUserId(user._id, next)
-})
-
-mongoose.model('User', UserSchema)
+}
// ------------------------------ METHODS ------------------------------
function toFormatedJSON () {
return {
- id: this._id,
+ id: this.id,
username: this.username,
role: this.role,
- createdDate: this.createdDate
+ createdAt: this.createdAt
}
}
// ------------------------------ STATICS ------------------------------
+function associate (models) {
+ this.hasMany(models.OAuthToken, {
+ foreignKey: 'userId',
+ onDelete: 'cascade'
+ })
+}
+
function countTotal (callback) {
- return this.count(callback)
+ return this.count().asCallback(callback)
}
function getByUsername (username) {
- return this.findOne({ username: username })
+ const query = {
+ where: {
+ username: username
+ }
+ }
+
+ return this.findOne(query)
}
function list (callback) {
- return this.find(callback)
+ return this.find().asCallback(callback)
}
function listForApi (start, count, sort, callback) {
- const query = {}
- return modelUtils.listForApiWithCount.call(this, query, start, count, sort, callback)
+ const query = {
+ offset: start,
+ limit: count,
+ order: [ modelUtils.getSort(sort) ]
+ }
+
+ return this.findAndCountAll(query).asCallback(function (err, result) {
+ if (err) return callback(err)
+
+ return callback(null, result.rows, result.count)
+ })
}
function loadById (id, callback) {
- return this.findById(id, callback)
+ return this.findById(id).asCallback(callback)
}
function loadByUsername (username, callback) {
- return this.findOne({ username: username }, callback)
+ const query = {
+ where: {
+ username: username
+ }
+ }
+
+ return this.findOne(query).asCallback(callback)
}
'use strict'
-const parallel = require('async/parallel')
-
const utils = {
- listForApiWithCount
+ getSort
}
-function listForApiWithCount (query, start, count, sort, callback) {
- const self = this
+// Translate for example "-name" to [ 'name', 'DESC' ]
+function getSort (value) {
+ let field
+ let direction
- parallel([
- function (asyncCallback) {
- self.find(query).skip(start).limit(count).sort(sort).exec(asyncCallback)
- },
- function (asyncCallback) {
- self.count(query, asyncCallback)
- }
- ], function (err, results) {
- if (err) return callback(err)
+ if (value.substring(0, 1) === '-') {
+ direction = 'DESC'
+ field = value.substring(1)
+ } else {
+ direction = 'ASC'
+ field = value
+ }
- const data = results[0]
- const total = results[1]
- return callback(null, data, total)
- })
+ return [ field, direction ]
}
// ---------------------------------------------------------------------------
const parallel = require('async/parallel')
const parseTorrent = require('parse-torrent')
const pathUtils = require('path')
-const mongoose = require('mongoose')
const constants = require('../initializers/constants')
-const customVideosValidators = require('../helpers/custom-validators').videos
const logger = require('../helpers/logger')
const modelUtils = require('./utils')
// ---------------------------------------------------------------------------
+module.exports = function (sequelize, DataTypes) {
// TODO: add indexes on searchable columns
-const VideoSchema = mongoose.Schema({
- name: String,
- extname: {
- type: String,
- enum: [ '.mp4', '.webm', '.ogv' ]
- },
- remoteId: mongoose.Schema.Types.ObjectId,
- description: String,
- magnet: {
- infoHash: String
- },
- podHost: String,
- author: String,
- duration: Number,
- tags: [ String ],
- createdDate: {
- type: Date,
- default: Date.now
- }
-})
-
-VideoSchema.path('name').validate(customVideosValidators.isVideoNameValid)
-VideoSchema.path('description').validate(customVideosValidators.isVideoDescriptionValid)
-VideoSchema.path('podHost').validate(customVideosValidators.isVideoPodHostValid)
-VideoSchema.path('author').validate(customVideosValidators.isVideoAuthorValid)
-VideoSchema.path('duration').validate(customVideosValidators.isVideoDurationValid)
-VideoSchema.path('tags').validate(customVideosValidators.isVideoTagsValid)
-
-VideoSchema.methods = {
- generateMagnetUri,
- getVideoFilename,
- getThumbnailName,
- getPreviewName,
- getTorrentName,
- isOwned,
- toFormatedJSON,
- toRemoteJSON
-}
-
-VideoSchema.statics = {
- generateThumbnailFromBase64,
- getDurationFromFile,
- listForApi,
- listByHostAndRemoteId,
- listByHost,
- listOwned,
- listOwnedByAuthor,
- listRemotes,
- load,
- search
-}
-
-VideoSchema.pre('remove', function (next) {
- const video = this
- const tasks = []
-
- tasks.push(
- function (callback) {
- removeThumbnail(video, callback)
- }
- )
-
- if (video.isOwned()) {
- tasks.push(
- function (callback) {
- removeFile(video, callback)
+ const Video = sequelize.define('Video',
+ {
+ id: {
+ type: DataTypes.UUID,
+ defaultValue: DataTypes.UUIDV4,
+ primaryKey: true
},
- function (callback) {
- removeTorrent(video, callback)
+ name: {
+ type: DataTypes.STRING
},
- function (callback) {
- removePreview(video, callback)
+ extname: {
+ // TODO: enum?
+ type: DataTypes.STRING
+ },
+ remoteId: {
+ type: DataTypes.UUID
+ },
+ description: {
+ type: DataTypes.STRING
+ },
+ infoHash: {
+ type: DataTypes.STRING
+ },
+ duration: {
+ type: DataTypes.INTEGER
+ },
+ tags: {
+ type: DataTypes.ARRAY(DataTypes.STRING)
}
- )
- }
+ },
+ {
+ classMethods: {
+ associate,
+
+ generateThumbnailFromBase64,
+ getDurationFromFile,
+ listForApi,
+ listByHostAndRemoteId,
+ listOwnedAndPopulateAuthor,
+ listOwnedByAuthor,
+ load,
+ loadAndPopulateAuthor,
+ loadAndPopulateAuthorAndPod,
+ searchAndPopulateAuthorAndPod
+ },
+ instanceMethods: {
+ generateMagnetUri,
+ getVideoFilename,
+ getThumbnailName,
+ getPreviewName,
+ getTorrentName,
+ isOwned,
+ toFormatedJSON,
+ toRemoteJSON
+ },
+ hooks: {
+ beforeCreate,
+ afterDestroy
+ }
+ }
+ )
- parallel(tasks, next)
-})
+ return Video
+}
-VideoSchema.pre('save', function (next) {
- const video = this
+// TODO: Validation
+// VideoSchema.path('name').validate(customVideosValidators.isVideoNameValid)
+// VideoSchema.path('description').validate(customVideosValidators.isVideoDescriptionValid)
+// VideoSchema.path('podHost').validate(customVideosValidators.isVideoPodHostValid)
+// VideoSchema.path('author').validate(customVideosValidators.isVideoAuthorValid)
+// VideoSchema.path('duration').validate(customVideosValidators.isVideoDurationValid)
+// VideoSchema.path('tags').validate(customVideosValidators.isVideoTagsValid)
+
+function beforeCreate (video, options, next) {
const tasks = []
if (video.isOwned()) {
const videoPath = pathUtils.join(constants.CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename())
- this.podHost = constants.CONFIG.WEBSERVER.HOST
tasks.push(
// TODO: refractoring
if (err) return callback(err)
const parsedTorrent = parseTorrent(torrent)
- video.magnet.infoHash = parsedTorrent.infoHash
+ video.infoHash = parsedTorrent.infoHash
callback(null)
})
}
return next()
-})
+}
+
+function afterDestroy (video, options, next) {
+ const tasks = []
-mongoose.model('Video', VideoSchema)
+ tasks.push(
+ function (callback) {
+ removeThumbnail(video, callback)
+ }
+ )
+
+ if (video.isOwned()) {
+ tasks.push(
+ function (callback) {
+ removeFile(video, callback)
+ },
+ function (callback) {
+ removeTorrent(video, callback)
+ },
+ function (callback) {
+ removePreview(video, callback)
+ }
+ )
+ }
+
+ parallel(tasks, next)
+}
// ------------------------------ METHODS ------------------------------
+function associate (models) {
+ this.belongsTo(models.Author, {
+ foreignKey: {
+ name: 'authorId',
+ allowNull: false
+ },
+ onDelete: 'cascade'
+ })
+}
+
function generateMagnetUri () {
let baseUrlHttp, baseUrlWs
baseUrlHttp = constants.CONFIG.WEBSERVER.URL
baseUrlWs = constants.CONFIG.WEBSERVER.WS + '://' + constants.CONFIG.WEBSERVER.HOSTNAME + ':' + constants.CONFIG.WEBSERVER.PORT
} else {
- baseUrlHttp = constants.REMOTE_SCHEME.HTTP + '://' + this.podHost
- baseUrlWs = constants.REMOTE_SCHEME.WS + '://' + this.podHost
+ baseUrlHttp = constants.REMOTE_SCHEME.HTTP + '://' + this.Author.Pod.host
+ baseUrlWs = constants.REMOTE_SCHEME.WS + '://' + this.Author.Pod.host
}
const xs = baseUrlHttp + constants.STATIC_PATHS.TORRENTS + this.getTorrentName()
xs,
announce,
urlList,
- infoHash: this.magnet.infoHash,
+ infoHash: this.infoHash,
name: this.name
}
}
function getVideoFilename () {
- if (this.isOwned()) return this._id + this.extname
+ if (this.isOwned()) return this.id + this.extname
return this.remoteId + this.extname
}
function getThumbnailName () {
// We always have a copy of the thumbnail
- return this._id + '.jpg'
+ return this.id + '.jpg'
}
function getPreviewName () {
const extension = '.jpg'
- if (this.isOwned()) return this._id + extension
+ if (this.isOwned()) return this.id + extension
return this.remoteId + extension
}
function getTorrentName () {
const extension = '.torrent'
- if (this.isOwned()) return this._id + extension
+ if (this.isOwned()) return this.id + extension
return this.remoteId + extension
}
}
function toFormatedJSON () {
+ let podHost
+
+ if (this.Author.Pod) {
+ podHost = this.Author.Pod.host
+ } else {
+ // It means it's our video
+ podHost = constants.CONFIG.WEBSERVER.HOST
+ }
+
const json = {
- id: this._id,
+ id: this.id,
name: this.name,
description: this.description,
- podHost: this.podHost,
+ podHost,
isLocal: this.isOwned(),
magnetUri: this.generateMagnetUri(),
- author: this.author,
+ author: this.Author.name,
duration: this.duration,
tags: this.tags,
thumbnailPath: constants.STATIC_PATHS.THUMBNAILS + '/' + this.getThumbnailName(),
- createdDate: this.createdDate
+ createdAt: this.createdAt
}
return json
const remoteVideo = {
name: self.name,
description: self.description,
- magnet: self.magnet,
- remoteId: self._id,
- author: self.author,
+ infoHash: self.infoHash,
+ remoteId: self.id,
+ author: self.Author.name,
duration: self.duration,
thumbnailBase64: new Buffer(thumbnailData).toString('base64'),
tags: self.tags,
- createdDate: self.createdDate,
+ createdAt: self.createdAt,
extname: self.extname
}
}
function listForApi (start, count, sort, callback) {
- const query = {}
- return modelUtils.listForApiWithCount.call(this, query, start, count, sort, callback)
+ const query = {
+ offset: start,
+ limit: count,
+ order: [ modelUtils.getSort(sort) ],
+ include: [
+ {
+ model: this.sequelize.models.Author,
+ include: [ this.sequelize.models.Pod ]
+ }
+ ]
+ }
+
+ return this.findAndCountAll(query).asCallback(function (err, result) {
+ if (err) return callback(err)
+
+ return callback(null, result.rows, result.count)
+ })
}
function listByHostAndRemoteId (fromHost, remoteId, callback) {
- this.find({ podHost: fromHost, remoteId: remoteId }, callback)
-}
+ const query = {
+ where: {
+ remoteId: remoteId
+ },
+ include: [
+ {
+ model: this.sequelize.models.Author,
+ include: [
+ {
+ model: this.sequelize.models.Pod,
+ where: {
+ host: fromHost
+ }
+ }
+ ]
+ }
+ ]
+ }
-function listByHost (fromHost, callback) {
- this.find({ podHost: fromHost }, callback)
+ return this.findAll(query).asCallback(callback)
}
-function listOwned (callback) {
+function listOwnedAndPopulateAuthor (callback) {
// If remoteId is null this is *our* video
- this.find({ remoteId: null }, callback)
+ const query = {
+ where: {
+ remoteId: null
+ },
+ include: [ this.sequelize.models.Author ]
+ }
+
+ return this.findAll(query).asCallback(callback)
}
function listOwnedByAuthor (author, callback) {
- this.find({ remoteId: null, author: author }, callback)
-}
+ const query = {
+ where: {
+ remoteId: null
+ },
+ include: [
+ {
+ model: this.sequelize.models.Author,
+ where: {
+ name: author
+ }
+ }
+ ]
+ }
-function listRemotes (callback) {
- this.find({ remoteId: { $ne: null } }, callback)
+ return this.findAll(query).asCallback(callback)
}
function load (id, callback) {
- this.findById(id, callback)
+ return this.findById(id).asCallback(callback)
}
-function search (value, field, start, count, sort, callback) {
- const query = {}
+function loadAndPopulateAuthor (id, callback) {
+ const options = {
+ include: [ this.sequelize.models.Author ]
+ }
+
+ return this.findById(id, options).asCallback(callback)
+}
+
+function loadAndPopulateAuthorAndPod (id, callback) {
+ const options = {
+ include: [
+ {
+ model: this.sequelize.models.Author,
+ include: [ this.sequelize.models.Pod ]
+ }
+ ]
+ }
+
+ return this.findById(id, options).asCallback(callback)
+}
+
+function searchAndPopulateAuthorAndPod (value, field, start, count, sort, callback) {
+ const podInclude = {
+ model: this.sequelize.models.Pod
+ }
+ const authorInclude = {
+ model: this.sequelize.models.Author,
+ include: [
+ podInclude
+ ]
+ }
+
+ const query = {
+ where: {},
+ include: [
+ authorInclude
+ ],
+ offset: start,
+ limit: count,
+ order: [ modelUtils.getSort(sort) ]
+ }
+
+ // TODO: include our pod for podHost searches (we are not stored in the database)
// Make an exact search with the magnet
if (field === 'magnetUri') {
const infoHash = magnetUtil.decode(value).infoHash
- query.magnet = {
- infoHash
- }
+ query.where.infoHash = infoHash
} else if (field === 'tags') {
- query[field] = value
+ query.where[field] = value
+ } else if (field === 'host') {
+ const whereQuery = {
+ '$Author.Pod.host$': {
+ $like: '%' + value + '%'
+ }
+ }
+
+ // Include our pod? (not stored in the database)
+ if (constants.CONFIG.WEBSERVER.HOST.indexOf(value) !== -1) {
+ query.where = {
+ $or: [
+ whereQuery,
+ {
+ remoteId: null
+ }
+ ]
+ }
+ } else {
+ query.where = whereQuery
+ }
+ } else if (field === 'author') {
+ query.where = {
+ '$Author.name$': {
+ $like: '%' + value + '%'
+ }
+ }
} else {
- query[field] = new RegExp(value, 'i')
+ query.where[field] = {
+ $like: '%' + value + '%'
+ }
}
- modelUtils.listForApiWithCount.call(this, query, start, count, sort, callback)
+ return this.findAndCountAll(query).asCallback(function (err, result) {
+ if (err) return callback(err)
+
+ return callback(null, result.rows, result.count)
+ })
}
// ---------------------------------------------------------------------------
it('Should return 404 with an incorrect video', function (done) {
request(server.url)
- .get(path + '123456789012345678901234')
+ .get(path + '4da6fde3-88f7-4d16-b119-108df5630b06')
.set('Accept', 'application/json')
.expect(404, done)
})
it('Should fail with a video which does not exist', function (done) {
request(server.url)
- .delete(path + '123456789012345678901234')
+ .delete(path + '4da6fde3-88f7-4d16-b119-108df5630b06')
.set('Authorization', 'Bearer ' + server.accessToken)
.expect(404, done)
})
it('Should return 404 with a non existing id', function (done) {
request(server.url)
- .delete(path + '579f982228c99c221d8092b8')
+ .delete(path + '45')
.set('Authorization', 'Bearer ' + server.accessToken)
.expect(404, done)
})
const pod = result[0]
expect(pod.host).to.equal(servers[2].host)
expect(pod.score).to.equal(20)
- expect(miscsUtils.dateIsValid(pod.createdDate)).to.be.true
+ expect(miscsUtils.dateIsValid(pod.createdAt)).to.be.true
next()
})
const pod = result[0]
expect(pod.host).to.equal(servers[1].host)
expect(pod.score).to.equal(20)
- expect(miscsUtils.dateIsValid(pod.createdDate)).to.be.true
+ expect(miscsUtils.dateIsValid(pod.createdAt)).to.be.true
next()
})
expect(video.magnetUri).to.exist
expect(video.duration).to.equal(10)
expect(video.tags).to.deep.equal([ 'tag1p1', 'tag2p1' ])
- expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true
+ expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true
expect(video.author).to.equal('root')
if (server.url !== 'http://localhost:9001') {
expect(video.magnetUri).to.exist
expect(video.duration).to.equal(5)
expect(video.tags).to.deep.equal([ 'tag1p2', 'tag2p2', 'tag3p2' ])
- expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true
+ expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true
expect(video.author).to.equal('root')
if (server.url !== 'http://localhost:9002') {
expect(video1.duration).to.equal(5)
expect(video1.tags).to.deep.equal([ 'tag1p3' ])
expect(video1.author).to.equal('root')
- expect(miscsUtils.dateIsValid(video1.createdDate)).to.be.true
+ expect(miscsUtils.dateIsValid(video1.createdAt)).to.be.true
expect(video2.name).to.equal('my super name for pod 3-2')
expect(video2.description).to.equal('my super description for pod 3-2')
expect(video2.duration).to.equal(5)
expect(video2.tags).to.deep.equal([ 'tag2p3', 'tag3p3', 'tag4p3' ])
expect(video2.author).to.equal('root')
- expect(miscsUtils.dateIsValid(video2.createdDate)).to.be.true
+ expect(miscsUtils.dateIsValid(video2.createdAt)).to.be.true
if (server.url !== 'http://localhost:9003') {
expect(video1.isLocal).to.be.false
})
})
- it('Should have the correct request', function (done) {
+ it('Should have the correct total request', function (done) {
this.timeout(15000)
const server = servers[0]
if (err) throw err
const body = res.body
- expect(body.requests).to.have.lengthOf(1)
-
- const request = body.requests[0]
- expect(request.to).to.have.lengthOf(1)
- expect(request.request.type).to.equal('add')
+ expect(body.totalRequests).to.equal(1)
// Wait one cycle
setTimeout(done, 10000)
})
})
- it('Should have the correct requests', function (done) {
- const server = servers[0]
-
- uploadVideo(server, function (err) {
- if (err) throw err
-
- getRequestsStats(server, function (err, res) {
- if (err) throw err
-
- const body = res.body
- expect(body.requests).to.have.lengthOf(2)
-
- const request = body.requests[1]
- expect(request.to).to.have.lengthOf(1)
- expect(request.request.type).to.equal('add')
-
- done()
- })
- })
- })
-
after(function (done) {
process.kill(-servers[0].app.pid)
expect(video.author).to.equal('root')
expect(video.isLocal).to.be.true
expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ])
- expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true
+ expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true
videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) {
if (err) throw err
expect(video.author).to.equal('root')
expect(video.isLocal).to.be.true
expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ])
- expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true
+ expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true
videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) {
if (err) throw err
expect(video.author).to.equal('root')
expect(video.isLocal).to.be.true
expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ])
- expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true
+ expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true
videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) {
if (err) throw err
})
it('Should search the video by podHost', function (done) {
- videosUtils.searchVideo(server.url, '9001', 'podHost', function (err, res) {
+ videosUtils.searchVideo(server.url, '9001', 'host', function (err, res) {
if (err) throw err
expect(res.body.total).to.equal(1)
expect(video.author).to.equal('root')
expect(video.isLocal).to.be.true
expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ])
- expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true
+ expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true
videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) {
if (err) throw err
expect(video.author).to.equal('root')
expect(video.isLocal).to.be.true
expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ])
- expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true
+ expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true
videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) {
if (err) throw err
})
it('Should search all the 9001 port videos', function (done) {
- videosUtils.searchVideoWithPagination(server.url, '9001', 'podHost', 0, 15, function (err, res) {
+ videosUtils.searchVideoWithPagination(server.url, '9001', 'host', 0, 15, function (err, res) {
if (err) throw err
const videos = res.body.data
})
it('Should search all the localhost videos', function (done) {
- videosUtils.searchVideoWithPagination(server.url, 'localhost', 'podHost', 0, 15, function (err, res) {
+ videosUtils.searchVideoWithPagination(server.url, 'localhost', 'host', 0, 15, function (err, res) {
if (err) throw err
const videos = res.body.data
})
})
- it('Should list only the second user by createdDate desc', function (done) {
- usersUtils.getUsersListPaginationAndSort(server.url, 0, 1, '-createdDate', function (err, res) {
+ it('Should list only the second user by createdAt desc', function (done) {
+ usersUtils.getUsersListPaginationAndSort(server.url, 0, 1, '-createdAt', function (err, res) {
if (err) throw err
const result = res.body
})
})
- it('Should list all the users by createdDate asc', function (done) {
- usersUtils.getUsersListPaginationAndSort(server.url, 0, 2, 'createdDate', function (err, res) {
+ it('Should list all the users by createdAt asc', function (done) {
+ usersUtils.getUsersListPaginationAndSort(server.url, 0, 2, 'createdAt', function (err, res) {
if (err) throw err
const result = res.body
// These actions are async so we need to be sure that they have both been done
const serverRunString = {
- 'Connected to mongodb': false,
+ 'Database is ready': false,
'Server listening on port': false
}
const regexps = {
- client_id: 'Client id: ([a-f0-9]+)',
+ client_id: 'Client id: (.+)',
client_secret: 'Client secret: (.+)',
user_username: 'Username: (.+)',
user_password: 'User password: (.+)'
request(url)
.get(path)
- .query({ sort: 'createdDate' })
+ .query({ sort: 'createdAt' })
.query({ start: 0 })
.query({ count: 10000 })
.set('Accept', 'application/json')