From 65fcc3119c334b75dd13bcfdebf186afdc580a8f Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Mon, 15 May 2017 22:22:03 +0200 Subject: First typescript iteration --- server/initializers/checker.js | 88 ------ server/initializers/checker.ts | 84 +++++ server/initializers/constants.js | 343 --------------------- server/initializers/constants.ts | 343 +++++++++++++++++++++ server/initializers/database.js | 77 ----- server/initializers/database.ts | 72 +++++ server/initializers/index.ts | 6 + server/initializers/installer.js | 134 -------- server/initializers/installer.ts | 128 ++++++++ server/initializers/migrations/0005-email-pod.js | 41 --- server/initializers/migrations/0005-email-pod.ts | 44 +++ server/initializers/migrations/0010-email-user.js | 41 --- server/initializers/migrations/0010-email-user.ts | 44 +++ server/initializers/migrations/0015-video-views.js | 19 -- server/initializers/migrations/0015-video-views.ts | 22 ++ server/initializers/migrations/0020-video-likes.js | 19 -- server/initializers/migrations/0020-video-likes.ts | 22 ++ .../initializers/migrations/0025-video-dislikes.js | 19 -- .../initializers/migrations/0025-video-dislikes.ts | 22 ++ .../initializers/migrations/0030-video-category.js | 34 -- .../initializers/migrations/0030-video-category.ts | 37 +++ .../initializers/migrations/0035-video-licence.js | 34 -- .../initializers/migrations/0035-video-licence.ts | 37 +++ server/initializers/migrations/0040-video-nsfw.js | 34 -- server/initializers/migrations/0040-video-nsfw.ts | 37 +++ .../migrations/0045-user-display-nsfw.js | 19 -- .../migrations/0045-user-display-nsfw.ts | 22 ++ .../initializers/migrations/0050-video-language.js | 19 -- .../initializers/migrations/0050-video-language.ts | 22 ++ server/initializers/migrator.js | 139 --------- server/initializers/migrator.ts | 134 ++++++++ 31 files changed, 1076 insertions(+), 1060 deletions(-) delete mode 100644 server/initializers/checker.js create mode 100644 server/initializers/checker.ts delete mode 100644 server/initializers/constants.js create mode 100644 server/initializers/constants.ts delete mode 100644 server/initializers/database.js create mode 100644 server/initializers/database.ts create mode 100644 server/initializers/index.ts delete mode 100644 server/initializers/installer.js create mode 100644 server/initializers/installer.ts delete mode 100644 server/initializers/migrations/0005-email-pod.js create mode 100644 server/initializers/migrations/0005-email-pod.ts delete mode 100644 server/initializers/migrations/0010-email-user.js create mode 100644 server/initializers/migrations/0010-email-user.ts delete mode 100644 server/initializers/migrations/0015-video-views.js create mode 100644 server/initializers/migrations/0015-video-views.ts delete mode 100644 server/initializers/migrations/0020-video-likes.js create mode 100644 server/initializers/migrations/0020-video-likes.ts delete mode 100644 server/initializers/migrations/0025-video-dislikes.js create mode 100644 server/initializers/migrations/0025-video-dislikes.ts delete mode 100644 server/initializers/migrations/0030-video-category.js create mode 100644 server/initializers/migrations/0030-video-category.ts delete mode 100644 server/initializers/migrations/0035-video-licence.js create mode 100644 server/initializers/migrations/0035-video-licence.ts delete mode 100644 server/initializers/migrations/0040-video-nsfw.js create mode 100644 server/initializers/migrations/0040-video-nsfw.ts delete mode 100644 server/initializers/migrations/0045-user-display-nsfw.js create mode 100644 server/initializers/migrations/0045-user-display-nsfw.ts delete mode 100644 server/initializers/migrations/0050-video-language.js create mode 100644 server/initializers/migrations/0050-video-language.ts delete mode 100644 server/initializers/migrator.js create mode 100644 server/initializers/migrator.ts (limited to 'server/initializers') diff --git a/server/initializers/checker.js b/server/initializers/checker.js deleted file mode 100644 index aa8dea4bf..000000000 --- a/server/initializers/checker.js +++ /dev/null @@ -1,88 +0,0 @@ -'use strict' - -const config = require('config') - -const constants = require('./constants') -const db = require('./database') - -const checker = { - checkConfig, - checkFFmpeg, - checkMissedConfig, - clientsExist, - usersExist -} - -// Some checks on configuration files -function checkConfig () { - if (config.has('webserver.host')) { - let errorMessage = '`host` config key was renamed to `hostname` but it seems you still have a `host` key in your configuration files!' - errorMessage += ' Please ensure to rename your `host` configuration to `hostname`.' - - return errorMessage - } - - return null -} - -// Check the config files -function checkMissedConfig () { - const required = [ 'listen.port', - 'webserver.https', 'webserver.hostname', 'webserver.port', - 'database.hostname', 'database.port', 'database.suffix', 'database.username', 'database.password', - 'storage.certs', 'storage.videos', 'storage.logs', 'storage.thumbnails', 'storage.previews', - 'admin.email', 'signup.enabled', 'transcoding.enabled', 'transcoding.threads' - ] - const miss = [] - - for (const key of required) { - if (!config.has(key)) { - miss.push(key) - } - } - - return miss -} - -// Check the available codecs -function checkFFmpeg (callback) { - const Ffmpeg = require('fluent-ffmpeg') - - Ffmpeg.getAvailableCodecs(function (err, codecs) { - if (err) return callback(err) - if (constants.CONFIG.TRANSCODING.ENABLED === false) return callback(null) - - const canEncode = [ 'libx264' ] - canEncode.forEach(function (codec) { - if (codecs[codec] === undefined) { - return callback(new Error('Unknown codec ' + codec + ' in FFmpeg.')) - } - - if (codecs[codec].canEncode !== true) { - return callback(new Error('Unavailable encode codec ' + codec + ' in FFmpeg')) - } - }) - - return callback(null) - }) -} - -function clientsExist (callback) { - db.OAuthClient.countTotal(function (err, totalClients) { - if (err) return callback(err) - - return callback(null, totalClients !== 0) - }) -} - -function usersExist (callback) { - db.User.countTotal(function (err, totalUsers) { - if (err) return callback(err) - - return callback(null, totalUsers !== 0) - }) -} - -// --------------------------------------------------------------------------- - -module.exports = checker diff --git a/server/initializers/checker.ts b/server/initializers/checker.ts new file mode 100644 index 000000000..370dff2d4 --- /dev/null +++ b/server/initializers/checker.ts @@ -0,0 +1,84 @@ +import config = require('config') + +const db = require('./database') +import { CONFIG } from './constants' + +// Some checks on configuration files +function checkConfig () { + if (config.has('webserver.host')) { + let errorMessage = '`host` config key was renamed to `hostname` but it seems you still have a `host` key in your configuration files!' + errorMessage += ' Please ensure to rename your `host` configuration to `hostname`.' + + return errorMessage + } + + return null +} + +// Check the config files +function checkMissedConfig () { + const required = [ 'listen.port', + 'webserver.https', 'webserver.hostname', 'webserver.port', + 'database.hostname', 'database.port', 'database.suffix', 'database.username', 'database.password', + 'storage.certs', 'storage.videos', 'storage.logs', 'storage.thumbnails', 'storage.previews', + 'admin.email', 'signup.enabled', 'transcoding.enabled', 'transcoding.threads' + ] + const miss = [] + + for (const key of required) { + if (!config.has(key)) { + miss.push(key) + } + } + + return miss +} + +// Check the available codecs +function checkFFmpeg (callback) { + const Ffmpeg = require('fluent-ffmpeg') + + Ffmpeg.getAvailableCodecs(function (err, codecs) { + if (err) return callback(err) + if (CONFIG.TRANSCODING.ENABLED === false) return callback(null) + + const canEncode = [ 'libx264' ] + canEncode.forEach(function (codec) { + if (codecs[codec] === undefined) { + return callback(new Error('Unknown codec ' + codec + ' in FFmpeg.')) + } + + if (codecs[codec].canEncode !== true) { + return callback(new Error('Unavailable encode codec ' + codec + ' in FFmpeg')) + } + }) + + return callback(null) + }) +} + +function clientsExist (callback) { + db.OAuthClient.countTotal(function (err, totalClients) { + if (err) return callback(err) + + return callback(null, totalClients !== 0) + }) +} + +function usersExist (callback) { + db.User.countTotal(function (err, totalUsers) { + if (err) return callback(err) + + return callback(null, totalUsers !== 0) + }) +} + +// --------------------------------------------------------------------------- + +export { + checkConfig, + checkFFmpeg, + checkMissedConfig, + clientsExist, + usersExist +} diff --git a/server/initializers/constants.js b/server/initializers/constants.js deleted file mode 100644 index 87e9c8002..000000000 --- a/server/initializers/constants.js +++ /dev/null @@ -1,343 +0,0 @@ -'use strict' - -const config = require('config') -const path = require('path') - -// --------------------------------------------------------------------------- - -const LAST_MIGRATION_VERSION = 50 - -// --------------------------------------------------------------------------- - -// API version -const API_VERSION = 'v1' - -// Number of results by default for the pagination -const PAGINATION_COUNT_DEFAULT = 15 - -// Sortable columns per schema -const SEARCHABLE_COLUMNS = { - VIDEOS: [ 'name', 'magnetUri', 'host', 'author', 'tags' ] -} - -// Sortable columns per schema -const SORTABLE_COLUMNS = { - USERS: [ 'id', 'username', 'createdAt' ], - VIDEO_ABUSES: [ 'id', 'createdAt' ], - VIDEOS: [ 'name', 'duration', 'createdAt', 'views', 'likes' ] -} - -const OAUTH_LIFETIME = { - ACCESS_TOKEN: 3600 * 4, // 4 hours - REFRESH_TOKEN: 1209600 // 2 weeks -} - -// --------------------------------------------------------------------------- - -const CONFIG = { - LISTEN: { - PORT: config.get('listen.port') - }, - DATABASE: { - DBNAME: 'peertube' + config.get('database.suffix'), - HOSTNAME: config.get('database.hostname'), - PORT: config.get('database.port'), - USERNAME: config.get('database.username'), - PASSWORD: config.get('database.password') - }, - STORAGE: { - CERT_DIR: path.join(__dirname, '..', '..', config.get('storage.certs')), - LOG_DIR: path.join(__dirname, '..', '..', config.get('storage.logs')), - VIDEOS_DIR: path.join(__dirname, '..', '..', config.get('storage.videos')), - THUMBNAILS_DIR: path.join(__dirname, '..', '..', config.get('storage.thumbnails')), - PREVIEWS_DIR: path.join(__dirname, '..', '..', config.get('storage.previews')), - TORRENTS_DIR: path.join(__dirname, '..', '..', config.get('storage.torrents')) - }, - WEBSERVER: { - SCHEME: config.get('webserver.https') === true ? 'https' : 'http', - WS: config.get('webserver.https') === true ? 'wss' : 'ws', - HOSTNAME: config.get('webserver.hostname'), - PORT: config.get('webserver.port') - }, - ADMIN: { - EMAIL: config.get('admin.email') - }, - SIGNUP: { - ENABLED: config.get('signup.enabled') - }, - TRANSCODING: { - ENABLED: config.get('transcoding.enabled'), - THREADS: config.get('transcoding.threads') - } -} -CONFIG.WEBSERVER.URL = CONFIG.WEBSERVER.SCHEME + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT -CONFIG.WEBSERVER.HOST = CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT - -// --------------------------------------------------------------------------- - -const CONSTRAINTS_FIELDS = { - USERS: { - USERNAME: { min: 3, max: 20 }, // Length - PASSWORD: { min: 6, max: 255 } // Length - }, - VIDEO_ABUSES: { - REASON: { min: 2, max: 300 } // Length - }, - VIDEOS: { - NAME: { min: 3, max: 50 }, // Length - DESCRIPTION: { min: 3, max: 250 }, // Length - EXTNAME: [ '.mp4', '.ogv', '.webm' ], - INFO_HASH: { min: 40, max: 40 }, // Length, infohash is 20 bytes length but we represent it in hexa so 20 * 2 - DURATION: { min: 1, max: 7200 }, // Number - TAGS: { min: 0, max: 3 }, // Number of total tags - TAG: { min: 2, max: 10 }, // Length - THUMBNAIL: { min: 2, max: 30 }, - THUMBNAIL_DATA: { min: 0, max: 20000 }, // Bytes - VIEWS: { min: 0 }, - LIKES: { min: 0 }, - DISLIKES: { min: 0 } - }, - VIDEO_EVENTS: { - COUNT: { min: 0 } - } -} - -const VIDEO_RATE_TYPES = { - LIKE: 'like', - DISLIKE: 'dislike' -} - -const VIDEO_CATEGORIES = { - 1: 'Music', - 2: 'Films', - 3: 'Vehicles', - 4: 'Art', - 5: 'Sports', - 6: 'Travels', - 7: 'Gaming', - 8: 'People', - 9: 'Comedy', - 10: 'Entertainment', - 11: 'News', - 12: 'Howto', - 13: 'Education', - 14: 'Activism', - 15: 'Science & Technology', - 16: 'Animals', - 17: 'Kids', - 18: 'Food' -} - -// See https://creativecommons.org/licenses/?lang=en -const VIDEO_LICENCES = { - 1: 'Attribution', - 2: 'Attribution - Share Alike', - 3: 'Attribution - No Derivatives', - 4: 'Attribution - Non Commercial', - 5: 'Attribution - Non Commercial - Share Alike', - 6: 'Attribution - Non Commercial - No Derivatives', - 7: 'Public Domain Dedication' -} - -// See https://en.wikipedia.org/wiki/List_of_languages_by_number_of_native_speakers#Nationalencyklopedin -const VIDEO_LANGUAGES = { - 1: 'English', - 2: 'Spanish', - 3: 'Mandarin', - 4: 'Hindi', - 5: 'Arabic', - 6: 'Portuguese', - 7: 'Bengali', - 8: 'Russian', - 9: 'Japanese', - 10: 'Punjabi', - 11: 'German', - 12: 'Korean', - 13: 'French', - 14: 'Italien' -} - -// --------------------------------------------------------------------------- - -// Score a pod has when we create it as a friend -const FRIEND_SCORE = { - BASE: 100, - MAX: 1000 -} - -// --------------------------------------------------------------------------- - -// Number of points we add/remove from a friend after a successful/bad request -const PODS_SCORE = { - MALUS: -10, - BONUS: 10 -} - -// Time to wait between requests to the friends (10 min) -let REQUESTS_INTERVAL = 600000 - -// Number of requests in parallel we can make -const REQUESTS_IN_PARALLEL = 10 - -// To how many pods we send requests -const REQUESTS_LIMIT_PODS = 10 -// How many requests we send to a pod per interval -const REQUESTS_LIMIT_PER_POD = 5 - -const REQUESTS_VIDEO_QADU_LIMIT_PODS = 10 -// The QADU requests are not big -const REQUESTS_VIDEO_QADU_LIMIT_PER_POD = 50 - -const REQUESTS_VIDEO_EVENT_LIMIT_PODS = 10 -// The EVENTS requests are not big -const REQUESTS_VIDEO_EVENT_LIMIT_PER_POD = 50 - -// Number of requests to retry for replay requests module -const RETRY_REQUESTS = 5 - -const REQUEST_ENDPOINTS = { - VIDEOS: 'videos' -} - -const REQUEST_ENDPOINT_ACTIONS = {} -REQUEST_ENDPOINT_ACTIONS[REQUEST_ENDPOINTS.VIDEOS] = { - ADD: 'add', - UPDATE: 'update', - REMOVE: 'remove', - REPORT_ABUSE: 'report-abuse' -} - -const REQUEST_VIDEO_QADU_ENDPOINT = 'videos/qadu' -const REQUEST_VIDEO_EVENT_ENDPOINT = 'videos/events' - -const REQUEST_VIDEO_QADU_TYPES = { - LIKES: 'likes', - DISLIKES: 'dislikes', - VIEWS: 'views' -} - -const REQUEST_VIDEO_EVENT_TYPES = { - LIKES: 'likes', - DISLIKES: 'dislikes', - VIEWS: 'views' -} - -const REMOTE_SCHEME = { - HTTP: 'https', - WS: 'wss' -} - -const JOB_STATES = { - PENDING: 'pending', - PROCESSING: 'processing', - ERROR: 'error', - SUCCESS: 'success' -} -// How many maximum jobs we fetch from the database per cycle -const JOBS_FETCH_LIMIT_PER_CYCLE = 10 -const JOBS_CONCURRENCY = 1 -// 1 minutes -let JOBS_FETCHING_INTERVAL = 60000 - -// --------------------------------------------------------------------------- - -const PRIVATE_CERT_NAME = 'peertube.key.pem' -const PUBLIC_CERT_NAME = 'peertube.pub' -const SIGNATURE_ALGORITHM = 'RSA-SHA256' -const SIGNATURE_ENCODING = 'hex' - -// Password encryption -const BCRYPT_SALT_SIZE = 10 - -// --------------------------------------------------------------------------- - -// Express static paths (router) -const STATIC_PATHS = { - PREVIEWS: '/static/previews/', - THUMBNAILS: '/static/thumbnails/', - TORRENTS: '/static/torrents/', - WEBSEED: '/static/webseed/' -} - -// Cache control -let STATIC_MAX_AGE = '30d' - -// Videos thumbnail size -const THUMBNAILS_SIZE = '200x110' -const PREVIEWS_SIZE = '640x480' - -// --------------------------------------------------------------------------- - -const USER_ROLES = { - ADMIN: 'admin', - USER: 'user' -} - -// --------------------------------------------------------------------------- - -// Special constants for a test instance -if (isTestInstance() === true) { - CONSTRAINTS_FIELDS.VIDEOS.DURATION.max = 14 - FRIEND_SCORE.BASE = 20 - REQUESTS_INTERVAL = 10000 - JOBS_FETCHING_INTERVAL = 10000 - REMOTE_SCHEME.HTTP = 'http' - REMOTE_SCHEME.WS = 'ws' - STATIC_MAX_AGE = 0 -} - -// --------------------------------------------------------------------------- - -module.exports = { - API_VERSION, - BCRYPT_SALT_SIZE, - CONFIG, - CONSTRAINTS_FIELDS, - FRIEND_SCORE, - JOBS_FETCHING_INTERVAL, - JOB_STATES, - JOBS_CONCURRENCY, - JOBS_FETCH_LIMIT_PER_CYCLE, - LAST_MIGRATION_VERSION, - OAUTH_LIFETIME, - PAGINATION_COUNT_DEFAULT, - PODS_SCORE, - PREVIEWS_SIZE, - PRIVATE_CERT_NAME, - PUBLIC_CERT_NAME, - REMOTE_SCHEME, - REQUEST_ENDPOINT_ACTIONS, - REQUEST_ENDPOINTS, - REQUEST_VIDEO_EVENT_ENDPOINT, - REQUEST_VIDEO_EVENT_TYPES, - REQUEST_VIDEO_QADU_ENDPOINT, - REQUEST_VIDEO_QADU_TYPES, - REQUESTS_IN_PARALLEL, - REQUESTS_INTERVAL, - REQUESTS_LIMIT_PER_POD, - REQUESTS_LIMIT_PODS, - REQUESTS_VIDEO_EVENT_LIMIT_PER_POD, - REQUESTS_VIDEO_EVENT_LIMIT_PODS, - REQUESTS_VIDEO_QADU_LIMIT_PER_POD, - REQUESTS_VIDEO_QADU_LIMIT_PODS, - RETRY_REQUESTS, - SEARCHABLE_COLUMNS, - SIGNATURE_ALGORITHM, - SIGNATURE_ENCODING, - SORTABLE_COLUMNS, - STATIC_MAX_AGE, - STATIC_PATHS, - THUMBNAILS_SIZE, - USER_ROLES, - VIDEO_CATEGORIES, - VIDEO_LANGUAGES, - VIDEO_LICENCES, - VIDEO_RATE_TYPES -} - -// --------------------------------------------------------------------------- - -// This method exists in utils module but we want to let the constants module independent -function isTestInstance () { - return (process.env.NODE_ENV === 'test') -} diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts new file mode 100644 index 000000000..6bdc261ad --- /dev/null +++ b/server/initializers/constants.ts @@ -0,0 +1,343 @@ +import config = require('config') +import { join } from 'path' + +// --------------------------------------------------------------------------- + +const LAST_MIGRATION_VERSION = 50 + +// --------------------------------------------------------------------------- + +// API version +const API_VERSION = 'v1' + +// Number of results by default for the pagination +const PAGINATION_COUNT_DEFAULT = 15 + +// Sortable columns per schema +const SEARCHABLE_COLUMNS = { + VIDEOS: [ 'name', 'magnetUri', 'host', 'author', 'tags' ] +} + +// Sortable columns per schema +const SORTABLE_COLUMNS = { + USERS: [ 'id', 'username', 'createdAt' ], + VIDEO_ABUSES: [ 'id', 'createdAt' ], + VIDEOS: [ 'name', 'duration', 'createdAt', 'views', 'likes' ] +} + +const OAUTH_LIFETIME = { + ACCESS_TOKEN: 3600 * 4, // 4 hours + REFRESH_TOKEN: 1209600 // 2 weeks +} + +// --------------------------------------------------------------------------- + +const CONFIG = { + LISTEN: { + PORT: config.get('listen.port') + }, + DATABASE: { + DBNAME: 'peertube' + config.get('database.suffix'), + HOSTNAME: config.get('database.hostname'), + PORT: config.get('database.port'), + USERNAME: config.get('database.username'), + PASSWORD: config.get('database.password') + }, + STORAGE: { + CERT_DIR: join(__dirname, '..', '..', config.get('storage.certs')), + LOG_DIR: join(__dirname, '..', '..', config.get('storage.logs')), + VIDEOS_DIR: join(__dirname, '..', '..', config.get('storage.videos')), + THUMBNAILS_DIR: join(__dirname, '..', '..', config.get('storage.thumbnails')), + PREVIEWS_DIR: join(__dirname, '..', '..', config.get('storage.previews')), + TORRENTS_DIR: join(__dirname, '..', '..', config.get('storage.torrents')) + }, + WEBSERVER: { + SCHEME: config.get('webserver.https') === true ? 'https' : 'http', + WS: config.get('webserver.https') === true ? 'wss' : 'ws', + HOSTNAME: config.get('webserver.hostname'), + PORT: config.get('webserver.port'), + URL: '', + HOST: '' + }, + ADMIN: { + EMAIL: config.get('admin.email') + }, + SIGNUP: { + ENABLED: config.get('signup.enabled') + }, + TRANSCODING: { + ENABLED: config.get('transcoding.enabled'), + THREADS: config.get('transcoding.threads') + } +} +CONFIG.WEBSERVER.URL = CONFIG.WEBSERVER.SCHEME + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT +CONFIG.WEBSERVER.HOST = CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT + +// --------------------------------------------------------------------------- + +const CONSTRAINTS_FIELDS = { + USERS: { + USERNAME: { min: 3, max: 20 }, // Length + PASSWORD: { min: 6, max: 255 } // Length + }, + VIDEO_ABUSES: { + REASON: { min: 2, max: 300 } // Length + }, + VIDEOS: { + NAME: { min: 3, max: 50 }, // Length + DESCRIPTION: { min: 3, max: 250 }, // Length + EXTNAME: [ '.mp4', '.ogv', '.webm' ], + INFO_HASH: { min: 40, max: 40 }, // Length, infohash is 20 bytes length but we represent it in hexa so 20 * 2 + DURATION: { min: 1, max: 7200 }, // Number + TAGS: { min: 0, max: 3 }, // Number of total tags + TAG: { min: 2, max: 10 }, // Length + THUMBNAIL: { min: 2, max: 30 }, + THUMBNAIL_DATA: { min: 0, max: 20000 }, // Bytes + VIEWS: { min: 0 }, + LIKES: { min: 0 }, + DISLIKES: { min: 0 } + }, + VIDEO_EVENTS: { + COUNT: { min: 0 } + } +} + +const VIDEO_RATE_TYPES = { + LIKE: 'like', + DISLIKE: 'dislike' +} + +const VIDEO_CATEGORIES = { + 1: 'Music', + 2: 'Films', + 3: 'Vehicles', + 4: 'Art', + 5: 'Sports', + 6: 'Travels', + 7: 'Gaming', + 8: 'People', + 9: 'Comedy', + 10: 'Entertainment', + 11: 'News', + 12: 'Howto', + 13: 'Education', + 14: 'Activism', + 15: 'Science & Technology', + 16: 'Animals', + 17: 'Kids', + 18: 'Food' +} + +// See https://creativecommons.org/licenses/?lang=en +const VIDEO_LICENCES = { + 1: 'Attribution', + 2: 'Attribution - Share Alike', + 3: 'Attribution - No Derivatives', + 4: 'Attribution - Non Commercial', + 5: 'Attribution - Non Commercial - Share Alike', + 6: 'Attribution - Non Commercial - No Derivatives', + 7: 'Public Domain Dedication' +} + +// See https://en.wikipedia.org/wiki/List_of_languages_by_number_of_native_speakers#Nationalencyklopedin +const VIDEO_LANGUAGES = { + 1: 'English', + 2: 'Spanish', + 3: 'Mandarin', + 4: 'Hindi', + 5: 'Arabic', + 6: 'Portuguese', + 7: 'Bengali', + 8: 'Russian', + 9: 'Japanese', + 10: 'Punjabi', + 11: 'German', + 12: 'Korean', + 13: 'French', + 14: 'Italien' +} + +// --------------------------------------------------------------------------- + +// Score a pod has when we create it as a friend +const FRIEND_SCORE = { + BASE: 100, + MAX: 1000 +} + +// --------------------------------------------------------------------------- + +// Number of points we add/remove from a friend after a successful/bad request +const PODS_SCORE = { + MALUS: -10, + BONUS: 10 +} + +// Time to wait between requests to the friends (10 min) +let REQUESTS_INTERVAL = 600000 + +// Number of requests in parallel we can make +const REQUESTS_IN_PARALLEL = 10 + +// To how many pods we send requests +const REQUESTS_LIMIT_PODS = 10 +// How many requests we send to a pod per interval +const REQUESTS_LIMIT_PER_POD = 5 + +const REQUESTS_VIDEO_QADU_LIMIT_PODS = 10 +// The QADU requests are not big +const REQUESTS_VIDEO_QADU_LIMIT_PER_POD = 50 + +const REQUESTS_VIDEO_EVENT_LIMIT_PODS = 10 +// The EVENTS requests are not big +const REQUESTS_VIDEO_EVENT_LIMIT_PER_POD = 50 + +// Number of requests to retry for replay requests module +const RETRY_REQUESTS = 5 + +const REQUEST_ENDPOINTS = { + VIDEOS: 'videos' +} + +const REQUEST_ENDPOINT_ACTIONS = {} +REQUEST_ENDPOINT_ACTIONS[REQUEST_ENDPOINTS.VIDEOS] = { + ADD: 'add', + UPDATE: 'update', + REMOVE: 'remove', + REPORT_ABUSE: 'report-abuse' +} + +const REQUEST_VIDEO_QADU_ENDPOINT = 'videos/qadu' +const REQUEST_VIDEO_EVENT_ENDPOINT = 'videos/events' + +const REQUEST_VIDEO_QADU_TYPES = { + LIKES: 'likes', + DISLIKES: 'dislikes', + VIEWS: 'views' +} + +const REQUEST_VIDEO_EVENT_TYPES = { + LIKES: 'likes', + DISLIKES: 'dislikes', + VIEWS: 'views' +} + +const REMOTE_SCHEME = { + HTTP: 'https', + WS: 'wss' +} + +const JOB_STATES = { + PENDING: 'pending', + PROCESSING: 'processing', + ERROR: 'error', + SUCCESS: 'success' +} +// How many maximum jobs we fetch from the database per cycle +const JOBS_FETCH_LIMIT_PER_CYCLE = 10 +const JOBS_CONCURRENCY = 1 +// 1 minutes +let JOBS_FETCHING_INTERVAL = 60000 + +// --------------------------------------------------------------------------- + +const PRIVATE_CERT_NAME = 'peertube.key.pem' +const PUBLIC_CERT_NAME = 'peertube.pub' +const SIGNATURE_ALGORITHM = 'RSA-SHA256' +const SIGNATURE_ENCODING = 'hex' + +// Password encryption +const BCRYPT_SALT_SIZE = 10 + +// --------------------------------------------------------------------------- + +// Express static paths (router) +const STATIC_PATHS = { + PREVIEWS: '/static/previews/', + THUMBNAILS: '/static/thumbnails/', + TORRENTS: '/static/torrents/', + WEBSEED: '/static/webseed/' +} + +// Cache control +let STATIC_MAX_AGE = '30d' + +// Videos thumbnail size +const THUMBNAILS_SIZE = '200x110' +const PREVIEWS_SIZE = '640x480' + +// --------------------------------------------------------------------------- + +const USER_ROLES = { + ADMIN: 'admin', + USER: 'user' +} + +// --------------------------------------------------------------------------- + +// Special constants for a test instance +if (isTestInstance() === true) { + CONSTRAINTS_FIELDS.VIDEOS.DURATION.max = 14 + FRIEND_SCORE.BASE = 20 + REQUESTS_INTERVAL = 10000 + JOBS_FETCHING_INTERVAL = 10000 + REMOTE_SCHEME.HTTP = 'http' + REMOTE_SCHEME.WS = 'ws' + STATIC_MAX_AGE = '0' +} + +// --------------------------------------------------------------------------- + +export { + API_VERSION, + BCRYPT_SALT_SIZE, + CONFIG, + CONSTRAINTS_FIELDS, + FRIEND_SCORE, + JOBS_FETCHING_INTERVAL, + JOB_STATES, + JOBS_CONCURRENCY, + JOBS_FETCH_LIMIT_PER_CYCLE, + LAST_MIGRATION_VERSION, + OAUTH_LIFETIME, + PAGINATION_COUNT_DEFAULT, + PODS_SCORE, + PREVIEWS_SIZE, + PRIVATE_CERT_NAME, + PUBLIC_CERT_NAME, + REMOTE_SCHEME, + REQUEST_ENDPOINT_ACTIONS, + REQUEST_ENDPOINTS, + REQUEST_VIDEO_EVENT_ENDPOINT, + REQUEST_VIDEO_EVENT_TYPES, + REQUEST_VIDEO_QADU_ENDPOINT, + REQUEST_VIDEO_QADU_TYPES, + REQUESTS_IN_PARALLEL, + REQUESTS_INTERVAL, + REQUESTS_LIMIT_PER_POD, + REQUESTS_LIMIT_PODS, + REQUESTS_VIDEO_EVENT_LIMIT_PER_POD, + REQUESTS_VIDEO_EVENT_LIMIT_PODS, + REQUESTS_VIDEO_QADU_LIMIT_PER_POD, + REQUESTS_VIDEO_QADU_LIMIT_PODS, + RETRY_REQUESTS, + SEARCHABLE_COLUMNS, + SIGNATURE_ALGORITHM, + SIGNATURE_ENCODING, + SORTABLE_COLUMNS, + STATIC_MAX_AGE, + STATIC_PATHS, + THUMBNAILS_SIZE, + USER_ROLES, + VIDEO_CATEGORIES, + VIDEO_LANGUAGES, + VIDEO_LICENCES, + VIDEO_RATE_TYPES +} + +// --------------------------------------------------------------------------- + +// This method exists in utils module but we want to let the constants module independent +function isTestInstance () { + return (process.env.NODE_ENV === 'test') +} diff --git a/server/initializers/database.js b/server/initializers/database.js deleted file mode 100644 index 043152a0e..000000000 --- a/server/initializers/database.js +++ /dev/null @@ -1,77 +0,0 @@ -'use strict' - -const fs = require('fs') -const path = require('path') -const Sequelize = require('sequelize') - -const constants = require('../initializers/constants') -const logger = require('../helpers/logger') -const utils = require('../helpers/utils') - -const database = {} - -const dbname = constants.CONFIG.DATABASE.DBNAME -const username = constants.CONFIG.DATABASE.USERNAME -const password = constants.CONFIG.DATABASE.PASSWORD - -const sequelize = new Sequelize(dbname, username, password, { - dialect: 'postgres', - host: constants.CONFIG.DATABASE.HOSTNAME, - port: constants.CONFIG.DATABASE.PORT, - benchmark: utils.isTestInstance(), - - logging: function (message, benchmark) { - let newMessage = message - if (benchmark !== undefined) { - newMessage += ' | ' + benchmark + 'ms' - } - - logger.debug(newMessage) - } -}) - -database.sequelize = sequelize -database.Sequelize = Sequelize -database.init = init - -// --------------------------------------------------------------------------- - -module.exports = database - -// --------------------------------------------------------------------------- - -function init (silent, callback) { - if (!callback) { - callback = silent - silent = false - } - - if (!callback) callback = function () {} - - const modelDirectory = path.join(__dirname, '..', 'models') - fs.readdir(modelDirectory, function (err, files) { - if (err) throw err - - files.filter(function (file) { - // For all models but not utils.js - if (file === 'utils.js') return false - - return true - }) - .forEach(function (file) { - const model = sequelize.import(path.join(modelDirectory, file)) - - database[model.name] = model - }) - - Object.keys(database).forEach(function (modelName) { - if ('associate' in database[modelName]) { - database[modelName].associate(database) - } - }) - - if (!silent) logger.info('Database %s is ready.', dbname) - - return callback(null) - }) -} diff --git a/server/initializers/database.ts b/server/initializers/database.ts new file mode 100644 index 000000000..753a06669 --- /dev/null +++ b/server/initializers/database.ts @@ -0,0 +1,72 @@ +import fs = require('fs') +import { join } from 'path' +import Sequelize = require('sequelize') + +import { CONFIG } from './constants' +// Do not use barrel, we need to load database first +import { logger } from '../helpers/logger' +import { isTestInstance } from '../helpers/utils' + +const dbname = CONFIG.DATABASE.DBNAME +const username = CONFIG.DATABASE.USERNAME +const password = CONFIG.DATABASE.PASSWORD + +const database: any = {} + +const sequelize = new Sequelize(dbname, username, password, { + dialect: 'postgres', + host: CONFIG.DATABASE.HOSTNAME, + port: CONFIG.DATABASE.PORT, + benchmark: isTestInstance(), + + logging: function (message, benchmark) { + let newMessage = message + if (benchmark !== undefined) { + newMessage += ' | ' + benchmark + 'ms' + } + + logger.debug(newMessage) + } +}) + +database.sequelize = sequelize + +database.init = function (silent, callback) { + if (!callback) { + callback = silent + silent = false + } + + if (!callback) callback = function () { /* empty */ } + + const modelDirectory = join(__dirname, '..', 'models') + fs.readdir(modelDirectory, function (err, files) { + if (err) throw err + + files.filter(function (file) { + // For all models but not utils.js + if (file === 'utils.js') return false + + return true + }) + .forEach(function (file) { + const model = sequelize.import(join(modelDirectory, file)) + + database[model['name']] = model + }) + + Object.keys(database).forEach(function (modelName) { + if ('associate' in database[modelName]) { + database[modelName].associate(database) + } + }) + + if (!silent) logger.info('Database %s is ready.', dbname) + + return callback(null) + }) +} + +// --------------------------------------------------------------------------- + +module.exports = database diff --git a/server/initializers/index.ts b/server/initializers/index.ts new file mode 100644 index 000000000..b8400ff84 --- /dev/null +++ b/server/initializers/index.ts @@ -0,0 +1,6 @@ +// Constants first, databse in second! +export * from './constants' +export * from './database' +export * from './checker' +export * from './installer' +export * from './migrator' diff --git a/server/initializers/installer.js b/server/initializers/installer.js deleted file mode 100644 index 837a987dd..000000000 --- a/server/initializers/installer.js +++ /dev/null @@ -1,134 +0,0 @@ -'use strict' - -const config = require('config') -const each = require('async/each') -const mkdirp = require('mkdirp') -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 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) - }, - - function createCertificates (callbackAsync) { - peertubeCrypto.createCertsIfNotExist(callbackAsync) - }, - - function createOAuthClient (callbackAsync) { - createOAuthClientIfNotExist(callbackAsync) - }, - - function createOAuthUser (callbackAsync) { - createOAuthAdminIfNotExist(callbackAsync) - } - ], callback) -} - -// --------------------------------------------------------------------------- - -module.exports = installer - -// --------------------------------------------------------------------------- - -function createDirectoriesIfNotExist (callback) { - const storages = config.get('storage') - - each(Object.keys(storages), function (key, callbackEach) { - const dir = storages[key] - mkdirp(path.join(__dirname, '..', '..', dir), callbackEach) - }, callback) -} - -function createOAuthClientIfNotExist (callback) { - checker.clientsExist(function (err, exist) { - if (err) return callback(err) - - // Nothing to do, clients already exist - if (exist === true) return callback(null) - - logger.info('Creating a default OAuth 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().asCallback(function (err, createdClient) { - if (err) return callback(err) - - logger.info('Client id: ' + createdClient.clientId) - logger.info('Client secret: ' + createdClient.clientSecret) - - return callback(null) - }) - }) -} - -function createOAuthAdminIfNotExist (callback) { - checker.usersExist(function (err, exist) { - if (err) return callback(err) - - // Nothing to do, users already exist - if (exist === true) return callback(null) - - logger.info('Creating the administrator.') - - const username = 'root' - const role = constants.USER_ROLES.ADMIN - const email = constants.CONFIG.ADMIN.EMAIL - const createOptions = {} - let password = '' - - // Do not generate a random password for tests - if (process.env.NODE_ENV === 'test') { - password = 'test' - - if (process.env.NODE_APP_INSTANCE) { - password += process.env.NODE_APP_INSTANCE - } - - // Our password is weak so do not validate it - createOptions.validate = false - } else { - password = passwordGenerator(8, true) - } - - const userData = { - username, - email, - password, - role - } - - db.User.create(userData, createOptions).asCallback(function (err, createdUser) { - if (err) return callback(err) - - logger.info('Username: ' + username) - logger.info('User password: ' + password) - - logger.info('Creating Application table.') - db.Application.create({ migrationVersion: constants.LAST_MIGRATION_VERSION }).asCallback(callback) - }) - }) -} diff --git a/server/initializers/installer.ts b/server/initializers/installer.ts new file mode 100644 index 000000000..cd1404d48 --- /dev/null +++ b/server/initializers/installer.ts @@ -0,0 +1,128 @@ +import { join } from 'path' +import config = require('config') +import { each, series } from 'async' +import mkdirp = require('mkdirp') +import passwordGenerator = require('password-generator') + +const db = require('./database') +import { USER_ROLES, CONFIG, LAST_MIGRATION_VERSION } from './constants' +import { clientsExist, usersExist } from './checker' +import { logger, createCertsIfNotExist } from '../helpers' + +function installApplication (callback) { + series([ + function createDatabase (callbackAsync) { + db.sequelize.sync().asCallback(callbackAsync) + // db.sequelize.sync({ force: true }).asCallback(callbackAsync) + }, + + function createDirectories (callbackAsync) { + createDirectoriesIfNotExist(callbackAsync) + }, + + function createCertificates (callbackAsync) { + createCertsIfNotExist(callbackAsync) + }, + + function createOAuthClient (callbackAsync) { + createOAuthClientIfNotExist(callbackAsync) + }, + + function createOAuthUser (callbackAsync) { + createOAuthAdminIfNotExist(callbackAsync) + } + ], callback) +} + +// --------------------------------------------------------------------------- + +export { + installApplication +} + +// --------------------------------------------------------------------------- + +function createDirectoriesIfNotExist (callback) { + const storages = config.get('storage') + + each(Object.keys(storages), function (key, callbackEach) { + const dir = storages[key] + mkdirp(join(__dirname, '..', '..', dir), callbackEach) + }, callback) +} + +function createOAuthClientIfNotExist (callback) { + clientsExist(function (err, exist) { + if (err) return callback(err) + + // Nothing to do, clients already exist + if (exist === true) return callback(null) + + logger.info('Creating a default OAuth 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().asCallback(function (err, createdClient) { + if (err) return callback(err) + + logger.info('Client id: ' + createdClient.clientId) + logger.info('Client secret: ' + createdClient.clientSecret) + + return callback(null) + }) + }) +} + +function createOAuthAdminIfNotExist (callback) { + usersExist(function (err, exist) { + if (err) return callback(err) + + // Nothing to do, users already exist + if (exist === true) return callback(null) + + logger.info('Creating the administrator.') + + const username = 'root' + const role = USER_ROLES.ADMIN + const email = CONFIG.ADMIN.EMAIL + const createOptions: { validate?: boolean } = {} + let password = '' + + // Do not generate a random password for tests + if (process.env.NODE_ENV === 'test') { + password = 'test' + + if (process.env.NODE_APP_INSTANCE) { + password += process.env.NODE_APP_INSTANCE + } + + // Our password is weak so do not validate it + createOptions.validate = false + } else { + password = passwordGenerator(8, true) + } + + const userData = { + username, + email, + password, + role + } + + db.User.create(userData, createOptions).asCallback(function (err, createdUser) { + if (err) return callback(err) + + logger.info('Username: ' + username) + logger.info('User password: ' + password) + + logger.info('Creating Application table.') + db.Application.create({ migrationVersion: LAST_MIGRATION_VERSION }).asCallback(callback) + }) + }) +} diff --git a/server/initializers/migrations/0005-email-pod.js b/server/initializers/migrations/0005-email-pod.js deleted file mode 100644 index 9bbb354bf..000000000 --- a/server/initializers/migrations/0005-email-pod.js +++ /dev/null @@ -1,41 +0,0 @@ -'use strict' - -const waterfall = require('async/waterfall') - -// utils = { transaction, queryInterface, sequelize, Sequelize } -exports.up = function (utils, finalCallback) { - const q = utils.queryInterface - const Sequelize = utils.Sequelize - - const data = { - type: Sequelize.STRING(400), - allowNull: false, - defaultValue: '' - } - - waterfall([ - - function addEmailColumn (callback) { - q.addColumn('Pods', 'email', data, { transaction: utils.transaction }).asCallback(function (err) { - return callback(err) - }) - }, - - function updateWithFakeEmails (callback) { - const query = 'UPDATE "Pods" SET "email" = \'dummy@example.com\'' - utils.sequelize.query(query, { transaction: utils.transaction }).asCallback(function (err) { - return callback(err) - }) - }, - - function nullOnDefault (callback) { - data.defaultValue = null - - q.changeColumn('Pods', 'email', data, { transaction: utils.transaction }).asCallback(callback) - } - ], finalCallback) -} - -exports.down = function (options, callback) { - throw new Error('Not implemented.') -} diff --git a/server/initializers/migrations/0005-email-pod.ts b/server/initializers/migrations/0005-email-pod.ts new file mode 100644 index 000000000..a9200c47f --- /dev/null +++ b/server/initializers/migrations/0005-email-pod.ts @@ -0,0 +1,44 @@ +import { waterfall } from 'async' + +// utils = { transaction, queryInterface, sequelize, Sequelize } +function up (utils, finalCallback) { + const q = utils.queryInterface + const Sequelize = utils.Sequelize + + const data = { + type: Sequelize.STRING(400), + allowNull: false, + defaultValue: '' + } + + waterfall([ + + function addEmailColumn (callback) { + q.addColumn('Pods', 'email', data, { transaction: utils.transaction }).asCallback(function (err) { + return callback(err) + }) + }, + + function updateWithFakeEmails (callback) { + const query = 'UPDATE "Pods" SET "email" = \'dummy@example.com\'' + utils.sequelize.query(query, { transaction: utils.transaction }).asCallback(function (err) { + return callback(err) + }) + }, + + function nullOnDefault (callback) { + data.defaultValue = null + + q.changeColumn('Pods', 'email', data, { transaction: utils.transaction }).asCallback(callback) + } + ], finalCallback) +} + +function down (options, callback) { + throw new Error('Not implemented.') +} + +export { + up, + down +} diff --git a/server/initializers/migrations/0010-email-user.js b/server/initializers/migrations/0010-email-user.js deleted file mode 100644 index 1ab27133a..000000000 --- a/server/initializers/migrations/0010-email-user.js +++ /dev/null @@ -1,41 +0,0 @@ -'use strict' - -const waterfall = require('async/waterfall') - -// utils = { transaction, queryInterface, sequelize, Sequelize } -exports.up = function (utils, finalCallback) { - const q = utils.queryInterface - const Sequelize = utils.Sequelize - - const data = { - type: Sequelize.STRING(400), - allowNull: false, - defaultValue: '' - } - - waterfall([ - - function addEmailColumn (callback) { - q.addColumn('Users', 'email', data, { transaction: utils.transaction }).asCallback(function (err) { - return callback(err) - }) - }, - - function updateWithFakeEmails (callback) { - const query = 'UPDATE "Users" SET "email" = CONCAT("username", \'@example.com\')' - utils.sequelize.query(query, { transaction: utils.transaction }).asCallback(function (err) { - return callback(err) - }) - }, - - function nullOnDefault (callback) { - data.defaultValue = null - - q.changeColumn('Users', 'email', data, { transaction: utils.transaction }).asCallback(callback) - } - ], finalCallback) -} - -exports.down = function (options, callback) { - throw new Error('Not implemented.') -} diff --git a/server/initializers/migrations/0010-email-user.ts b/server/initializers/migrations/0010-email-user.ts new file mode 100644 index 000000000..4b5d29394 --- /dev/null +++ b/server/initializers/migrations/0010-email-user.ts @@ -0,0 +1,44 @@ +import { waterfall } from 'async' + +// utils = { transaction, queryInterface, sequelize, Sequelize } +function up (utils, finalCallback) { + const q = utils.queryInterface + const Sequelize = utils.Sequelize + + const data = { + type: Sequelize.STRING(400), + allowNull: false, + defaultValue: '' + } + + waterfall([ + + function addEmailColumn (callback) { + q.addColumn('Users', 'email', data, { transaction: utils.transaction }).asCallback(function (err) { + return callback(err) + }) + }, + + function updateWithFakeEmails (callback) { + const query = 'UPDATE "Users" SET "email" = CONCAT("username", \'@example.com\')' + utils.sequelize.query(query, { transaction: utils.transaction }).asCallback(function (err) { + return callback(err) + }) + }, + + function nullOnDefault (callback) { + data.defaultValue = null + + q.changeColumn('Users', 'email', data, { transaction: utils.transaction }).asCallback(callback) + } + ], finalCallback) +} + +function down (options, callback) { + throw new Error('Not implemented.') +} + +export { + up, + down +} diff --git a/server/initializers/migrations/0015-video-views.js b/server/initializers/migrations/0015-video-views.js deleted file mode 100644 index ae49fe73c..000000000 --- a/server/initializers/migrations/0015-video-views.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict' - -// utils = { transaction, queryInterface, sequelize, Sequelize } -exports.up = function (utils, finalCallback) { - const q = utils.queryInterface - const Sequelize = utils.Sequelize - - const data = { - type: Sequelize.INTEGER, - allowNull: false, - defaultValue: 0 - } - - q.addColumn('Videos', 'views', data, { transaction: utils.transaction }).asCallback(finalCallback) -} - -exports.down = function (options, callback) { - throw new Error('Not implemented.') -} diff --git a/server/initializers/migrations/0015-video-views.ts b/server/initializers/migrations/0015-video-views.ts new file mode 100644 index 000000000..e70869404 --- /dev/null +++ b/server/initializers/migrations/0015-video-views.ts @@ -0,0 +1,22 @@ +// utils = { transaction, queryInterface, sequelize, Sequelize } +function up (utils, finalCallback) { + const q = utils.queryInterface + const Sequelize = utils.Sequelize + + const data = { + type: Sequelize.INTEGER, + allowNull: false, + defaultValue: 0 + } + + q.addColumn('Videos', 'views', data, { transaction: utils.transaction }).asCallback(finalCallback) +} + +function down (options, callback) { + throw new Error('Not implemented.') +} + +export { + up, + down +} diff --git a/server/initializers/migrations/0020-video-likes.js b/server/initializers/migrations/0020-video-likes.js deleted file mode 100644 index 6db62cb90..000000000 --- a/server/initializers/migrations/0020-video-likes.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict' - -// utils = { transaction, queryInterface, sequelize, Sequelize } -exports.up = function (utils, finalCallback) { - const q = utils.queryInterface - const Sequelize = utils.Sequelize - - const data = { - type: Sequelize.INTEGER, - allowNull: false, - defaultValue: 0 - } - - q.addColumn('Videos', 'likes', data, { transaction: utils.transaction }).asCallback(finalCallback) -} - -exports.down = function (options, callback) { - throw new Error('Not implemented.') -} diff --git a/server/initializers/migrations/0020-video-likes.ts b/server/initializers/migrations/0020-video-likes.ts new file mode 100644 index 000000000..e435d0657 --- /dev/null +++ b/server/initializers/migrations/0020-video-likes.ts @@ -0,0 +1,22 @@ +// utils = { transaction, queryInterface, sequelize, Sequelize } +function up (utils, finalCallback) { + const q = utils.queryInterface + const Sequelize = utils.Sequelize + + const data = { + type: Sequelize.INTEGER, + allowNull: false, + defaultValue: 0 + } + + q.addColumn('Videos', 'likes', data, { transaction: utils.transaction }).asCallback(finalCallback) +} + +function down (options, callback) { + throw new Error('Not implemented.') +} + +export { + up, + down +} diff --git a/server/initializers/migrations/0025-video-dislikes.js b/server/initializers/migrations/0025-video-dislikes.js deleted file mode 100644 index 40d2e7351..000000000 --- a/server/initializers/migrations/0025-video-dislikes.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict' - -// utils = { transaction, queryInterface, sequelize, Sequelize } -exports.up = function (utils, finalCallback) { - const q = utils.queryInterface - const Sequelize = utils.Sequelize - - const data = { - type: Sequelize.INTEGER, - allowNull: false, - defaultValue: 0 - } - - q.addColumn('Videos', 'dislikes', data, { transaction: utils.transaction }).asCallback(finalCallback) -} - -exports.down = function (options, callback) { - throw new Error('Not implemented.') -} diff --git a/server/initializers/migrations/0025-video-dislikes.ts b/server/initializers/migrations/0025-video-dislikes.ts new file mode 100644 index 000000000..57e54e904 --- /dev/null +++ b/server/initializers/migrations/0025-video-dislikes.ts @@ -0,0 +1,22 @@ +// utils = { transaction, queryInterface, sequelize, Sequelize } +function up (utils, finalCallback) { + const q = utils.queryInterface + const Sequelize = utils.Sequelize + + const data = { + type: Sequelize.INTEGER, + allowNull: false, + defaultValue: 0 + } + + q.addColumn('Videos', 'dislikes', data, { transaction: utils.transaction }).asCallback(finalCallback) +} + +function down (options, callback) { + throw new Error('Not implemented.') +} + +export { + up, + down +} diff --git a/server/initializers/migrations/0030-video-category.js b/server/initializers/migrations/0030-video-category.js deleted file mode 100644 index ada95b2fe..000000000 --- a/server/initializers/migrations/0030-video-category.js +++ /dev/null @@ -1,34 +0,0 @@ -'use strict' - -const waterfall = require('async/waterfall') - -// utils = { transaction, queryInterface, sequelize, Sequelize } -exports.up = function (utils, finalCallback) { - const q = utils.queryInterface - const Sequelize = utils.Sequelize - - const data = { - type: Sequelize.INTEGER, - allowNull: false, - defaultValue: 0 - } - - waterfall([ - - function addCategoryColumn (callback) { - q.addColumn('Videos', 'category', data, { transaction: utils.transaction }).asCallback(function (err) { - return callback(err) - }) - }, - - function nullOnDefault (callback) { - data.defaultValue = null - - q.changeColumn('Videos', 'category', data, { transaction: utils.transaction }).asCallback(callback) - } - ], finalCallback) -} - -exports.down = function (options, callback) { - throw new Error('Not implemented.') -} diff --git a/server/initializers/migrations/0030-video-category.ts b/server/initializers/migrations/0030-video-category.ts new file mode 100644 index 000000000..1073f449c --- /dev/null +++ b/server/initializers/migrations/0030-video-category.ts @@ -0,0 +1,37 @@ +import { waterfall } from 'async' + +// utils = { transaction, queryInterface, sequelize, Sequelize } +function up (utils, finalCallback) { + const q = utils.queryInterface + const Sequelize = utils.Sequelize + + const data = { + type: Sequelize.INTEGER, + allowNull: false, + defaultValue: 0 + } + + waterfall([ + + function addCategoryColumn (callback) { + q.addColumn('Videos', 'category', data, { transaction: utils.transaction }).asCallback(function (err) { + return callback(err) + }) + }, + + function nullOnDefault (callback) { + data.defaultValue = null + + q.changeColumn('Videos', 'category', data, { transaction: utils.transaction }).asCallback(callback) + } + ], finalCallback) +} + +function down (options, callback) { + throw new Error('Not implemented.') +} + +export { + up, + down +} diff --git a/server/initializers/migrations/0035-video-licence.js b/server/initializers/migrations/0035-video-licence.js deleted file mode 100644 index 9cf75858d..000000000 --- a/server/initializers/migrations/0035-video-licence.js +++ /dev/null @@ -1,34 +0,0 @@ -'use strict' - -const waterfall = require('async/waterfall') - -// utils = { transaction, queryInterface, sequelize, Sequelize } -exports.up = function (utils, finalCallback) { - const q = utils.queryInterface - const Sequelize = utils.Sequelize - - const data = { - type: Sequelize.INTEGER, - allowNull: false, - defaultValue: 0 - } - - waterfall([ - - function addLicenceColumn (callback) { - q.addColumn('Videos', 'licence', data, { transaction: utils.transaction }).asCallback(function (err) { - return callback(err) - }) - }, - - function nullOnDefault (callback) { - data.defaultValue = null - - q.changeColumn('Videos', 'licence', data, { transaction: utils.transaction }).asCallback(callback) - } - ], finalCallback) -} - -exports.down = function (options, callback) { - throw new Error('Not implemented.') -} diff --git a/server/initializers/migrations/0035-video-licence.ts b/server/initializers/migrations/0035-video-licence.ts new file mode 100644 index 000000000..9316b3c37 --- /dev/null +++ b/server/initializers/migrations/0035-video-licence.ts @@ -0,0 +1,37 @@ +import { waterfall } from 'async' + +// utils = { transaction, queryInterface, sequelize, Sequelize } +function up (utils, finalCallback) { + const q = utils.queryInterface + const Sequelize = utils.Sequelize + + const data = { + type: Sequelize.INTEGER, + allowNull: false, + defaultValue: 0 + } + + waterfall([ + + function addLicenceColumn (callback) { + q.addColumn('Videos', 'licence', data, { transaction: utils.transaction }).asCallback(function (err) { + return callback(err) + }) + }, + + function nullOnDefault (callback) { + data.defaultValue = null + + q.changeColumn('Videos', 'licence', data, { transaction: utils.transaction }).asCallback(callback) + } + ], finalCallback) +} + +function down (options, callback) { + throw new Error('Not implemented.') +} + +export { + up, + down +} diff --git a/server/initializers/migrations/0040-video-nsfw.js b/server/initializers/migrations/0040-video-nsfw.js deleted file mode 100644 index 7f3692b28..000000000 --- a/server/initializers/migrations/0040-video-nsfw.js +++ /dev/null @@ -1,34 +0,0 @@ -'use strict' - -const waterfall = require('async/waterfall') - -// utils = { transaction, queryInterface, sequelize, Sequelize } -exports.up = function (utils, finalCallback) { - const q = utils.queryInterface - const Sequelize = utils.Sequelize - - const data = { - type: Sequelize.BOOLEAN, - allowNull: false, - defaultValue: false - } - - waterfall([ - - function addNSFWColumn (callback) { - q.addColumn('Videos', 'nsfw', data, { transaction: utils.transaction }).asCallback(function (err) { - return callback(err) - }) - }, - - function nullOnDefault (callback) { - data.defaultValue = null - - q.changeColumn('Videos', 'nsfw', data, { transaction: utils.transaction }).asCallback(callback) - } - ], finalCallback) -} - -exports.down = function (options, callback) { - throw new Error('Not implemented.') -} diff --git a/server/initializers/migrations/0040-video-nsfw.ts b/server/initializers/migrations/0040-video-nsfw.ts new file mode 100644 index 000000000..c61f496f1 --- /dev/null +++ b/server/initializers/migrations/0040-video-nsfw.ts @@ -0,0 +1,37 @@ +import { waterfall } from 'async' + +// utils = { transaction, queryInterface, sequelize, Sequelize } +function up (utils, finalCallback) { + const q = utils.queryInterface + const Sequelize = utils.Sequelize + + const data = { + type: Sequelize.BOOLEAN, + allowNull: false, + defaultValue: false + } + + waterfall([ + + function addNSFWColumn (callback) { + q.addColumn('Videos', 'nsfw', data, { transaction: utils.transaction }).asCallback(function (err) { + return callback(err) + }) + }, + + function nullOnDefault (callback) { + data.defaultValue = null + + q.changeColumn('Videos', 'nsfw', data, { transaction: utils.transaction }).asCallback(callback) + } + ], finalCallback) +} + +function down (options, callback) { + throw new Error('Not implemented.') +} + +export { + up, + down +} diff --git a/server/initializers/migrations/0045-user-display-nsfw.js b/server/initializers/migrations/0045-user-display-nsfw.js deleted file mode 100644 index 03624e593..000000000 --- a/server/initializers/migrations/0045-user-display-nsfw.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict' - -// utils = { transaction, queryInterface, sequelize, Sequelize } -exports.up = function (utils, finalCallback) { - const q = utils.queryInterface - const Sequelize = utils.Sequelize - - const data = { - type: Sequelize.BOOLEAN, - allowNull: false, - defaultValue: false - } - - q.addColumn('Users', 'displayNSFW', data, { transaction: utils.transaction }).asCallback(finalCallback) -} - -exports.down = function (options, callback) { - throw new Error('Not implemented.') -} diff --git a/server/initializers/migrations/0045-user-display-nsfw.ts b/server/initializers/migrations/0045-user-display-nsfw.ts new file mode 100644 index 000000000..1ca317795 --- /dev/null +++ b/server/initializers/migrations/0045-user-display-nsfw.ts @@ -0,0 +1,22 @@ +// utils = { transaction, queryInterface, sequelize, Sequelize } +function up (utils, finalCallback) { + const q = utils.queryInterface + const Sequelize = utils.Sequelize + + const data = { + type: Sequelize.BOOLEAN, + allowNull: false, + defaultValue: false + } + + q.addColumn('Users', 'displayNSFW', data, { transaction: utils.transaction }).asCallback(finalCallback) +} + +function down (options, callback) { + throw new Error('Not implemented.') +} + +export { + up, + down +} diff --git a/server/initializers/migrations/0050-video-language.js b/server/initializers/migrations/0050-video-language.js deleted file mode 100644 index 1c978758d..000000000 --- a/server/initializers/migrations/0050-video-language.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict' - -// utils = { transaction, queryInterface, sequelize, Sequelize } -exports.up = function (utils, finalCallback) { - const q = utils.queryInterface - const Sequelize = utils.Sequelize - - const data = { - type: Sequelize.INTEGER, - allowNull: true, - defaultValue: null - } - - q.addColumn('Videos', 'language', data, { transaction: utils.transaction }).asCallback(finalCallback) -} - -exports.down = function (options, callback) { - throw new Error('Not implemented.') -} diff --git a/server/initializers/migrations/0050-video-language.ts b/server/initializers/migrations/0050-video-language.ts new file mode 100644 index 000000000..95d0a473a --- /dev/null +++ b/server/initializers/migrations/0050-video-language.ts @@ -0,0 +1,22 @@ +// utils = { transaction, queryInterface, sequelize, Sequelize } +function up (utils, finalCallback) { + const q = utils.queryInterface + const Sequelize = utils.Sequelize + + const data = { + type: Sequelize.INTEGER, + allowNull: true, + defaultValue: null + } + + q.addColumn('Videos', 'language', data, { transaction: utils.transaction }).asCallback(finalCallback) +} + +function down (options, callback) { + throw new Error('Not implemented.') +} + +export { + up, + down +} diff --git a/server/initializers/migrator.js b/server/initializers/migrator.js deleted file mode 100644 index 9a6415b1a..000000000 --- a/server/initializers/migrator.js +++ /dev/null @@ -1,139 +0,0 @@ -'use strict' - -const waterfall = require('async/waterfall') -const eachSeries = require('async/eachSeries') -const fs = require('fs') -const path = require('path') - -const constants = require('./constants') -const db = require('./database') -const logger = require('../helpers/logger') - -const migrator = { - migrate: migrate -} - -function migrate (finalCallback) { - waterfall([ - - function checkApplicationTableExists (callback) { - db.sequelize.getQueryInterface().showAllTables().asCallback(function (err, tables) { - if (err) return callback(err) - - // No tables, we don't need to migrate anything - // The installer will do that - if (tables.length === 0) return finalCallback(null) - - return callback(null) - }) - }, - - function loadMigrationVersion (callback) { - db.Application.loadMigrationVersion(callback) - }, - - function createMigrationRowIfNotExists (actualVersion, callback) { - if (actualVersion === null) { - db.Application.create({ - migrationVersion: 0 - }, function (err) { - return callback(err, 0) - }) - } - - return callback(null, actualVersion) - }, - - function abortMigrationIfNotNeeded (actualVersion, callback) { - // No need migrations - if (actualVersion >= constants.LAST_MIGRATION_VERSION) return finalCallback(null) - - return callback(null, actualVersion) - }, - - function getMigrations (actualVersion, callback) { - // If there are a new migration scripts - logger.info('Begin migrations.') - - getMigrationScripts(function (err, migrationScripts) { - return callback(err, actualVersion, migrationScripts) - }) - }, - - function doMigrations (actualVersion, migrationScripts, callback) { - eachSeries(migrationScripts, function (entity, callbackEach) { - executeMigration(actualVersion, entity, callbackEach) - }, function (err) { - if (err) return callback(err) - - logger.info('Migrations finished. New migration version schema: %s', constants.LAST_MIGRATION_VERSION) - return callback(null) - }) - } - ], finalCallback) -} - -// --------------------------------------------------------------------------- - -module.exports = migrator - -// --------------------------------------------------------------------------- - -function getMigrationScripts (callback) { - fs.readdir(path.join(__dirname, 'migrations'), function (err, files) { - if (err) return callback(err) - - const filesToMigrate = [] - - files.forEach(function (file) { - // Filename is something like 'version-blabla.js' - const version = file.split('-')[0] - filesToMigrate.push({ - version, - script: file - }) - }) - - return callback(err, filesToMigrate) - }) -} - -function executeMigration (actualVersion, entity, callback) { - const versionScript = parseInt(entity.version) - - // Do not execute old migration scripts - if (versionScript <= actualVersion) return callback(null) - - // Load the migration module and run it - const migrationScriptName = entity.script - logger.info('Executing %s migration script.', migrationScriptName) - - const migrationScript = require(path.join(__dirname, 'migrations', migrationScriptName)) - - db.sequelize.transaction().asCallback(function (err, t) { - if (err) return callback(err) - - const options = { - transaction: t, - queryInterface: db.sequelize.getQueryInterface(), - sequelize: db.sequelize, - Sequelize: db.Sequelize - } - migrationScript.up(options, function (err) { - if (err) { - t.rollback() - return callback(err) - } - - // Update the new migration version - db.Application.updateMigrationVersion(versionScript, t, function (err) { - if (err) { - t.rollback() - return callback(err) - } - - t.commit().asCallback(callback) - }) - }) - }) -} diff --git a/server/initializers/migrator.ts b/server/initializers/migrator.ts new file mode 100644 index 000000000..cfa3220e0 --- /dev/null +++ b/server/initializers/migrator.ts @@ -0,0 +1,134 @@ +import { waterfall, eachSeries } from 'async' +import fs = require('fs') +import path = require('path') + +const db = require('./database') +import { LAST_MIGRATION_VERSION } from './constants' +import { logger } from '../helpers' + +function migrate (finalCallback) { + waterfall([ + + function checkApplicationTableExists (callback) { + db.sequelize.getQueryInterface().showAllTables().asCallback(function (err, tables) { + if (err) return callback(err) + + // No tables, we don't need to migrate anything + // The installer will do that + if (tables.length === 0) return finalCallback(null) + + return callback(null) + }) + }, + + function loadMigrationVersion (callback) { + db.Application.loadMigrationVersion(callback) + }, + + function createMigrationRowIfNotExists (actualVersion, callback) { + if (actualVersion === null) { + db.Application.create({ + migrationVersion: 0 + }, function (err) { + return callback(err, 0) + }) + } + + return callback(null, actualVersion) + }, + + function abortMigrationIfNotNeeded (actualVersion, callback) { + // No need migrations + if (actualVersion >= LAST_MIGRATION_VERSION) return finalCallback(null) + + return callback(null, actualVersion) + }, + + function getMigrations (actualVersion, callback) { + // If there are a new migration scripts + logger.info('Begin migrations.') + + getMigrationScripts(function (err, migrationScripts) { + return callback(err, actualVersion, migrationScripts) + }) + }, + + function doMigrations (actualVersion, migrationScripts, callback) { + eachSeries(migrationScripts, function (entity, callbackEach) { + executeMigration(actualVersion, entity, callbackEach) + }, function (err) { + if (err) return callback(err) + + logger.info('Migrations finished. New migration version schema: %s', LAST_MIGRATION_VERSION) + return callback(null) + }) + } + ], finalCallback) +} + +// --------------------------------------------------------------------------- + +export { + migrate +} + +// --------------------------------------------------------------------------- + +function getMigrationScripts (callback) { + fs.readdir(path.join(__dirname, 'migrations'), function (err, files) { + if (err) return callback(err) + + const filesToMigrate = [] + + files.forEach(function (file) { + // Filename is something like 'version-blabla.js' + const version = file.split('-')[0] + filesToMigrate.push({ + version, + script: file + }) + }) + + return callback(err, filesToMigrate) + }) +} + +function executeMigration (actualVersion, entity, callback) { + const versionScript = parseInt(entity.version) + + // Do not execute old migration scripts + if (versionScript <= actualVersion) return callback(null) + + // Load the migration module and run it + const migrationScriptName = entity.script + logger.info('Executing %s migration script.', migrationScriptName) + + const migrationScript = require(path.join(__dirname, 'migrations', migrationScriptName)) + + db.sequelize.transaction().asCallback(function (err, t) { + if (err) return callback(err) + + const options = { + transaction: t, + queryInterface: db.sequelize.getQueryInterface(), + sequelize: db.sequelize, + Sequelize: db.Sequelize + } + migrationScript.up(options, function (err) { + if (err) { + t.rollback() + return callback(err) + } + + // Update the new migration version + db.Application.updateMigrationVersion(versionScript, t, function (err) { + if (err) { + t.rollback() + return callback(err) + } + + t.commit().asCallback(callback) + }) + }) + }) +} -- cgit v1.2.3