はじまりの大地
このコミットが含まれているのは:
@@ -0,0 +1,380 @@
|
||||
import { uniqify } from '@peertube/peertube-core-utils'
|
||||
import { getFFmpegVersion } from '@peertube/peertube-ffmpeg'
|
||||
import { VideoRedundancyConfigFilter } from '@peertube/peertube-models'
|
||||
import { isProdInstance } from '@peertube/peertube-node-utils'
|
||||
import config from 'config'
|
||||
import { readFileSync, writeFileSync } from 'fs'
|
||||
import { basename } from 'path'
|
||||
import { URL } from 'url'
|
||||
import { parseBytes, parseSemVersion } from '../helpers/core-utils.js'
|
||||
import { isArray } from '../helpers/custom-validators/misc.js'
|
||||
import { logger } from '../helpers/logger.js'
|
||||
import { ApplicationModel, getServerActor } from '../models/application/application.js'
|
||||
import { OAuthClientModel } from '../models/oauth/oauth-client.js'
|
||||
import { UserModel } from '../models/user/user.js'
|
||||
import { CONFIG, getLocalConfigFilePath, isEmailEnabled, reloadConfig } from './config.js'
|
||||
import { WEBSERVER } from './constants.js'
|
||||
|
||||
async function checkActivityPubUrls () {
|
||||
const actor = await getServerActor()
|
||||
|
||||
const parsed = new URL(actor.url)
|
||||
if (WEBSERVER.HOST !== parsed.host) {
|
||||
const NODE_ENV = config.util.getEnv('NODE_ENV')
|
||||
const NODE_CONFIG_DIR = config.util.getEnv('NODE_CONFIG_DIR')
|
||||
|
||||
logger.warn(
|
||||
'It seems PeerTube was started (and created some data) with another domain name. ' +
|
||||
'This means you will not be able to federate! ' +
|
||||
'Please use %s %s npm run update-host to fix this.',
|
||||
NODE_CONFIG_DIR ? `NODE_CONFIG_DIR=${NODE_CONFIG_DIR}` : '',
|
||||
NODE_ENV ? `NODE_ENV=${NODE_ENV}` : ''
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Some checks on configuration files or throw if there is an error
|
||||
function checkConfig () {
|
||||
|
||||
const configFiles = config.util.getConfigSources().map(s => s.name).join(' -> ')
|
||||
logger.info('Using following configuration file hierarchy: %s.', configFiles)
|
||||
|
||||
checkRemovedConfigKeys()
|
||||
|
||||
checkSecretsConfig()
|
||||
checkEmailConfig()
|
||||
checkNSFWPolicyConfig()
|
||||
checkLocalRedundancyConfig()
|
||||
checkRemoteRedundancyConfig()
|
||||
checkStorageConfig()
|
||||
checkTranscodingConfig()
|
||||
checkImportConfig()
|
||||
checkBroadcastMessageConfig()
|
||||
checkSearchConfig()
|
||||
checkLiveConfig()
|
||||
checkObjectStorageConfig()
|
||||
checkVideoStudioConfig()
|
||||
checkThumbnailsConfig()
|
||||
}
|
||||
|
||||
// We get db by param to not import it in this file (import orders)
|
||||
async function clientsExist () {
|
||||
const totalClients = await OAuthClientModel.countTotal()
|
||||
|
||||
return totalClients !== 0
|
||||
}
|
||||
|
||||
// We get db by param to not import it in this file (import orders)
|
||||
async function usersExist () {
|
||||
const totalUsers = await UserModel.countTotal()
|
||||
|
||||
return totalUsers !== 0
|
||||
}
|
||||
|
||||
// We get db by param to not import it in this file (import orders)
|
||||
async function applicationExist () {
|
||||
const totalApplication = await ApplicationModel.countTotal()
|
||||
|
||||
return totalApplication !== 0
|
||||
}
|
||||
|
||||
async function checkFFmpegVersion () {
|
||||
const version = await getFFmpegVersion()
|
||||
const semvar = parseSemVersion(version)
|
||||
|
||||
if (!semvar) {
|
||||
logger.warn('Your ffmpeg version (%s) does not use semvar. Unable to determine version compatibility.', version)
|
||||
return
|
||||
}
|
||||
|
||||
const { major, minor, patch } = semvar
|
||||
|
||||
if (major < 4 || (major === 4 && minor < 1)) {
|
||||
logger.warn('Your ffmpeg version (%s) is outdated. PeerTube supports ffmpeg >= 4.1. Please upgrade ffmpeg.', version)
|
||||
}
|
||||
|
||||
if (major === 4 && minor === 4 && patch === 0) {
|
||||
logger.warn('There is a bug in ffmpeg 4.4.0 with HLS videos. Please upgrade ffmpeg.')
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
applicationExist,
|
||||
checkActivityPubUrls, checkConfig, checkFFmpegVersion, clientsExist, usersExist
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function checkRemovedConfigKeys () {
|
||||
// Moved configuration keys
|
||||
if (config.has('services.csp-logger')) {
|
||||
logger.warn('services.csp-logger configuration has been renamed to csp.report_uri. Please update your configuration file.')
|
||||
}
|
||||
|
||||
if (config.has('transcoding.webtorrent.enabled')) {
|
||||
const localConfigPath = getLocalConfigFilePath()
|
||||
|
||||
const content = readFileSync(localConfigPath, { encoding: 'utf-8' })
|
||||
if (!content.includes('"webtorrent"')) {
|
||||
throw new Error('Please rename transcoding.webtorrent.enabled key to transcoding.web_videos.enabled in your configuration file')
|
||||
}
|
||||
|
||||
try {
|
||||
logger.info(
|
||||
'Replacing "transcoding.webtorrent.enabled" key to "transcoding.web_videos.enabled" in your local configuration ' + localConfigPath
|
||||
)
|
||||
|
||||
writeFileSync(localConfigPath, content.replace('"webtorrent"', '"web_videos"'), { encoding: 'utf-8' })
|
||||
|
||||
reloadConfig()
|
||||
.catch(err => logger.error('Cannot reload configuration', { err }))
|
||||
} catch (err) {
|
||||
logger.error('Cannot write new configuration to file ' + localConfigPath, { err })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checkSecretsConfig () {
|
||||
if (!CONFIG.SECRETS.PEERTUBE) {
|
||||
throw new Error('secrets.peertube is missing in config. Generate one using `openssl rand -hex 32`')
|
||||
}
|
||||
}
|
||||
|
||||
function checkEmailConfig () {
|
||||
if (!isEmailEnabled()) {
|
||||
if (CONFIG.SIGNUP.ENABLED && CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) {
|
||||
throw new Error('SMTP is not configured but you require signup email verification.')
|
||||
}
|
||||
|
||||
if (CONFIG.SIGNUP.ENABLED && CONFIG.SIGNUP.REQUIRES_APPROVAL) {
|
||||
// eslint-disable-next-line max-len
|
||||
logger.warn('SMTP is not configured but signup approval is enabled: PeerTube will not be able to send an email to the user upon acceptance/rejection of the registration request')
|
||||
}
|
||||
|
||||
if (CONFIG.CONTACT_FORM.ENABLED) {
|
||||
logger.warn('SMTP is not configured so the contact form will not work.')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checkNSFWPolicyConfig () {
|
||||
const defaultNSFWPolicy = CONFIG.INSTANCE.DEFAULT_NSFW_POLICY
|
||||
|
||||
const available = [ 'do_not_list', 'blur', 'display' ]
|
||||
if (available.includes(defaultNSFWPolicy) === false) {
|
||||
throw new Error('NSFW policy setting should be ' + available.join(' or ') + ' instead of ' + defaultNSFWPolicy)
|
||||
}
|
||||
}
|
||||
|
||||
function checkLocalRedundancyConfig () {
|
||||
const redundancyVideos = CONFIG.REDUNDANCY.VIDEOS.STRATEGIES
|
||||
|
||||
if (isArray(redundancyVideos)) {
|
||||
const available = [ 'most-views', 'trending', 'recently-added' ]
|
||||
|
||||
for (const r of redundancyVideos) {
|
||||
if (available.includes(r.strategy) === false) {
|
||||
throw new Error('Videos redundancy should have ' + available.join(' or ') + ' strategy instead of ' + r.strategy)
|
||||
}
|
||||
|
||||
// Lifetime should not be < 10 hours
|
||||
if (isProdInstance() && r.minLifetime < 1000 * 3600 * 10) {
|
||||
throw new Error('Video redundancy minimum lifetime should be >= 10 hours for strategy ' + r.strategy)
|
||||
}
|
||||
}
|
||||
|
||||
const filtered = uniqify(redundancyVideos.map(r => r.strategy))
|
||||
if (filtered.length !== redundancyVideos.length) {
|
||||
throw new Error('Redundancy video entries should have unique strategies')
|
||||
}
|
||||
|
||||
const recentlyAddedStrategy = redundancyVideos.find(r => r.strategy === 'recently-added')
|
||||
if (recentlyAddedStrategy && isNaN(recentlyAddedStrategy.minViews)) {
|
||||
throw new Error('Min views in recently added strategy is not a number')
|
||||
}
|
||||
} else {
|
||||
throw new Error('Videos redundancy should be an array (you must uncomment lines containing - too)')
|
||||
}
|
||||
}
|
||||
|
||||
function checkRemoteRedundancyConfig () {
|
||||
const acceptFrom = CONFIG.REMOTE_REDUNDANCY.VIDEOS.ACCEPT_FROM
|
||||
const acceptFromValues = new Set<VideoRedundancyConfigFilter>([ 'nobody', 'anybody', 'followings' ])
|
||||
|
||||
if (acceptFromValues.has(acceptFrom) === false) {
|
||||
throw new Error('remote_redundancy.videos.accept_from has an incorrect value')
|
||||
}
|
||||
}
|
||||
|
||||
function checkStorageConfig () {
|
||||
// Check storage directory locations
|
||||
if (isProdInstance()) {
|
||||
const configStorage = config.get<{ [ name: string ]: string }>('storage')
|
||||
|
||||
for (const key of Object.keys(configStorage)) {
|
||||
if (configStorage[key].startsWith('storage/')) {
|
||||
logger.warn(
|
||||
'Directory of %s should not be in the production directory of PeerTube. Please check your production configuration file.',
|
||||
key
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const webVideosDirname = basename(CONFIG.STORAGE.WEB_VIDEOS_DIR)
|
||||
if (webVideosDirname !== 'web-videos') {
|
||||
logger.warn(`storage.web_videos configuration should have a "web-videos" directory name (current value: "${webVideosDirname}")`)
|
||||
}
|
||||
}
|
||||
|
||||
if (CONFIG.STORAGE.WEB_VIDEOS_DIR === CONFIG.STORAGE.REDUNDANCY_DIR) {
|
||||
logger.warn('Redundancy directory should be different than the videos folder.')
|
||||
}
|
||||
}
|
||||
|
||||
function checkTranscodingConfig () {
|
||||
if (CONFIG.TRANSCODING.ENABLED) {
|
||||
if (CONFIG.TRANSCODING.WEB_VIDEOS.ENABLED === false && CONFIG.TRANSCODING.HLS.ENABLED === false) {
|
||||
throw new Error('You need to enable at least Web Video transcoding or HLS transcoding.')
|
||||
}
|
||||
|
||||
if (CONFIG.TRANSCODING.CONCURRENCY <= 0) {
|
||||
throw new Error('Transcoding concurrency should be > 0')
|
||||
}
|
||||
}
|
||||
|
||||
if (CONFIG.IMPORT.VIDEOS.HTTP.ENABLED || CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED) {
|
||||
if (CONFIG.IMPORT.VIDEOS.CONCURRENCY <= 0) {
|
||||
throw new Error('Video import concurrency should be > 0')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checkImportConfig () {
|
||||
if (CONFIG.IMPORT.VIDEO_CHANNEL_SYNCHRONIZATION.ENABLED && !CONFIG.IMPORT.VIDEOS.HTTP) {
|
||||
throw new Error('You need to enable HTTP import to allow synchronization')
|
||||
}
|
||||
}
|
||||
|
||||
function checkBroadcastMessageConfig () {
|
||||
if (CONFIG.BROADCAST_MESSAGE.ENABLED) {
|
||||
const currentLevel = CONFIG.BROADCAST_MESSAGE.LEVEL
|
||||
const available = [ 'info', 'warning', 'error' ]
|
||||
|
||||
if (available.includes(currentLevel) === false) {
|
||||
throw new Error('Broadcast message level should be ' + available.join(' or ') + ' instead of ' + currentLevel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checkSearchConfig () {
|
||||
if (CONFIG.SEARCH.SEARCH_INDEX.ENABLED === true) {
|
||||
if (CONFIG.SEARCH.REMOTE_URI.USERS === false) {
|
||||
throw new Error('You cannot enable search index without enabling remote URI search for users.')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checkLiveConfig () {
|
||||
if (CONFIG.LIVE.ENABLED === true) {
|
||||
if (CONFIG.LIVE.ALLOW_REPLAY === true && CONFIG.TRANSCODING.ENABLED === false) {
|
||||
throw new Error('Live allow replay cannot be enabled if transcoding is not enabled.')
|
||||
}
|
||||
|
||||
if (CONFIG.LIVE.RTMP.ENABLED === false && CONFIG.LIVE.RTMPS.ENABLED === false) {
|
||||
throw new Error('You must enable at least RTMP or RTMPS')
|
||||
}
|
||||
|
||||
if (CONFIG.LIVE.RTMPS.ENABLED) {
|
||||
if (!CONFIG.LIVE.RTMPS.KEY_FILE) {
|
||||
throw new Error('You must specify a key file to enable RTMPS')
|
||||
}
|
||||
|
||||
if (!CONFIG.LIVE.RTMPS.CERT_FILE) {
|
||||
throw new Error('You must specify a cert file to enable RTMPS')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checkObjectStorageConfig () {
|
||||
if (CONFIG.OBJECT_STORAGE.ENABLED !== true) return
|
||||
|
||||
if (!CONFIG.OBJECT_STORAGE.WEB_VIDEOS.BUCKET_NAME) {
|
||||
throw new Error('videos_bucket should be set when object storage support is enabled.')
|
||||
}
|
||||
|
||||
if (!CONFIG.OBJECT_STORAGE.STREAMING_PLAYLISTS.BUCKET_NAME) {
|
||||
throw new Error('streaming_playlists_bucket should be set when object storage support is enabled.')
|
||||
}
|
||||
|
||||
// Check web videos and hls videos are not in the same bucket or directory
|
||||
if (
|
||||
CONFIG.OBJECT_STORAGE.WEB_VIDEOS.BUCKET_NAME === CONFIG.OBJECT_STORAGE.STREAMING_PLAYLISTS.BUCKET_NAME &&
|
||||
CONFIG.OBJECT_STORAGE.WEB_VIDEOS.PREFIX === CONFIG.OBJECT_STORAGE.STREAMING_PLAYLISTS.PREFIX
|
||||
) {
|
||||
if (CONFIG.OBJECT_STORAGE.WEB_VIDEOS.PREFIX === '') {
|
||||
throw new Error('Bucket prefixes should be set when the same bucket is used for both types of video.')
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
'Bucket prefixes should be set to different values when the same bucket is used for both types of video.'
|
||||
)
|
||||
}
|
||||
|
||||
if (CONFIG.TRANSCODING.ORIGINAL_FILE.KEEP) {
|
||||
|
||||
if (!CONFIG.OBJECT_STORAGE.ORIGINAL_VIDEO_FILES.BUCKET_NAME) {
|
||||
throw new Error('original_video_files_bucket should be set when object storage support is enabled.')
|
||||
}
|
||||
|
||||
// Check web videos/hls videos are not in the same bucket or directory as original video files
|
||||
if (
|
||||
CONFIG.OBJECT_STORAGE.WEB_VIDEOS.BUCKET_NAME === CONFIG.OBJECT_STORAGE.ORIGINAL_VIDEO_FILES.BUCKET_NAME &&
|
||||
CONFIG.OBJECT_STORAGE.WEB_VIDEOS.PREFIX === CONFIG.OBJECT_STORAGE.ORIGINAL_VIDEO_FILES.PREFIX
|
||||
) {
|
||||
if (CONFIG.OBJECT_STORAGE.WEB_VIDEOS.PREFIX === '') {
|
||||
throw new Error('Bucket prefixes should be set when the same bucket is used for both original and web video files.')
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
'Bucket prefixes should be set to different values when the same bucket is used for both original and web video files.'
|
||||
)
|
||||
}
|
||||
|
||||
if (
|
||||
CONFIG.OBJECT_STORAGE.STREAMING_PLAYLISTS.BUCKET_NAME === CONFIG.OBJECT_STORAGE.ORIGINAL_VIDEO_FILES.BUCKET_NAME &&
|
||||
CONFIG.OBJECT_STORAGE.STREAMING_PLAYLISTS.PREFIX === CONFIG.OBJECT_STORAGE.ORIGINAL_VIDEO_FILES.PREFIX
|
||||
) {
|
||||
if (CONFIG.OBJECT_STORAGE.STREAMING_PLAYLISTS.PREFIX === '') {
|
||||
throw new Error('Bucket prefixes should be set when the same bucket is used for both original and hls files.')
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
'Bucket prefixes should be set to different values when the same bucket is used for both original and hls files.'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (CONFIG.OBJECT_STORAGE.MAX_UPLOAD_PART > parseBytes('250MB')) {
|
||||
// eslint-disable-next-line max-len
|
||||
logger.warn(`Object storage max upload part seems to have a big value (${CONFIG.OBJECT_STORAGE.MAX_UPLOAD_PART} bytes). Consider using a lower one (like 100MB).`)
|
||||
}
|
||||
}
|
||||
|
||||
function checkVideoStudioConfig () {
|
||||
if (CONFIG.VIDEO_STUDIO.ENABLED === true && CONFIG.TRANSCODING.ENABLED === false) {
|
||||
throw new Error('Video studio cannot be enabled if transcoding is disabled')
|
||||
}
|
||||
}
|
||||
|
||||
function checkThumbnailsConfig () {
|
||||
if (CONFIG.THUMBNAILS.GENERATION_FROM_VIDEO.FRAMES_TO_ANALYZE < 2) {
|
||||
throw new Error('thumbnails.generation_from_video.frames_to_analyze must be a number greater than 1')
|
||||
}
|
||||
|
||||
if (!isArray(CONFIG.THUMBNAILS.SIZES) || CONFIG.THUMBNAILS.SIZES.length !== 2) {
|
||||
throw new Error('thumbnails.sizes must be an array of 2 sizes')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
import config from 'config'
|
||||
import { promisify0 } from '@peertube/peertube-core-utils'
|
||||
import { parseSemVersion } from '../helpers/core-utils.js'
|
||||
import { logger } from '../helpers/logger.js'
|
||||
|
||||
// ONLY USE CORE MODULES IN THIS FILE!
|
||||
|
||||
// Check the config files
|
||||
function checkMissedConfig () {
|
||||
const required = [ 'listen.port', 'listen.hostname',
|
||||
'webserver.https', 'webserver.hostname', 'webserver.port',
|
||||
'secrets.peertube',
|
||||
'trust_proxy',
|
||||
'oauth2.token_lifetime.access_token', 'oauth2.token_lifetime.refresh_token',
|
||||
'database.hostname', 'database.port', 'database.username', 'database.password', 'database.pool.max',
|
||||
'smtp.hostname', 'smtp.port', 'smtp.username', 'smtp.password', 'smtp.tls', 'smtp.from_address',
|
||||
'email.body.signature', 'email.subject.prefix',
|
||||
'storage.avatars', 'storage.web_videos', 'storage.logs', 'storage.previews', 'storage.thumbnails', 'storage.torrents', 'storage.cache',
|
||||
'storage.redundancy', 'storage.tmp', 'storage.streaming_playlists', 'storage.plugins', 'storage.well_known',
|
||||
'log.level', 'log.rotation.enabled', 'log.rotation.max_file_size', 'log.rotation.max_files', 'log.anonymize_ip',
|
||||
'log.log_ping_requests', 'log.log_tracker_unknown_infohash', 'log.prettify_sql', 'log.accept_client_log',
|
||||
'open_telemetry.metrics.enabled', 'open_telemetry.metrics.playback_stats_interval',
|
||||
'open_telemetry.metrics.prometheus_exporter.hostname', 'open_telemetry.metrics.prometheus_exporter.port',
|
||||
'open_telemetry.tracing.enabled', 'open_telemetry.tracing.jaeger_exporter.endpoint',
|
||||
'open_telemetry.metrics.http_request_duration.enabled',
|
||||
'user.history.videos.enabled', 'user.video_quota', 'user.video_quota_daily',
|
||||
'video_channels.max_per_user',
|
||||
'csp.enabled', 'csp.report_only', 'csp.report_uri',
|
||||
'security.frameguard.enabled', 'security.powered_by_header.enabled',
|
||||
'cache.previews.size', 'cache.captions.size', 'cache.torrents.size', 'cache.storyboards.size',
|
||||
'admin.email', 'contact_form.enabled',
|
||||
'signup.enabled', 'signup.limit', 'signup.requires_approval', 'signup.requires_email_verification', 'signup.minimum_age',
|
||||
'signup.filters.cidr.whitelist', 'signup.filters.cidr.blacklist',
|
||||
'redundancy.videos.strategies', 'redundancy.videos.check_interval',
|
||||
'transcoding.enabled', 'transcoding.original_file.keep', 'transcoding.threads', 'transcoding.allow_additional_extensions',
|
||||
'transcoding.web_videos.enabled', 'transcoding.hls.enabled', 'transcoding.profile', 'transcoding.concurrency',
|
||||
'transcoding.resolutions.0p', 'transcoding.resolutions.144p', 'transcoding.resolutions.240p', 'transcoding.resolutions.360p',
|
||||
'transcoding.resolutions.480p', 'transcoding.resolutions.720p', 'transcoding.resolutions.1080p', 'transcoding.resolutions.1440p',
|
||||
'transcoding.resolutions.2160p', 'transcoding.always_transcode_original_resolution', 'transcoding.remote_runners.enabled',
|
||||
'video_studio.enabled', 'video_studio.remote_runners.enabled',
|
||||
'video_file.update.enabled',
|
||||
'remote_runners.stalled_jobs.vod', 'remote_runners.stalled_jobs.live',
|
||||
'thumbnails.generation_from_video.frames_to_analyze', 'thumbnails.sizes',
|
||||
'import.videos.http.enabled', 'import.videos.torrent.enabled', 'import.videos.concurrency', 'import.videos.timeout',
|
||||
'import.video_channel_synchronization.enabled', 'import.video_channel_synchronization.max_per_user',
|
||||
'import.video_channel_synchronization.check_interval', 'import.video_channel_synchronization.videos_limit_per_synchronization',
|
||||
'import.video_channel_synchronization.full_sync_videos_limit',
|
||||
'auto_blacklist.videos.of_users.enabled', 'trending.videos.interval_days',
|
||||
'client.videos.miniature.display_author_avatar',
|
||||
'client.videos.miniature.prefer_author_display_name', 'client.menu.login.redirect_on_single_external_auth',
|
||||
'defaults.publish.download_enabled', 'defaults.publish.comments_policy', 'defaults.publish.privacy', 'defaults.publish.licence',
|
||||
'instance.name', 'instance.short_description', 'instance.description', 'instance.terms', 'instance.default_client_route',
|
||||
'instance.is_nsfw', 'instance.default_nsfw_policy', 'instance.robots', 'instance.securitytxt',
|
||||
'services.twitter.username',
|
||||
'followers.instance.enabled', 'followers.instance.manual_approval',
|
||||
'tracker.enabled', 'tracker.private', 'tracker.reject_too_many_announces',
|
||||
'history.videos.max_age', 'views.videos.remote.max_age', 'views.videos.local_buffer_update_interval', 'views.videos.view_expiration',
|
||||
'views.videos.watching_interval.anonymous', 'views.videos.watching_interval.users',
|
||||
'rates_limit.api.window', 'rates_limit.api.max', 'rates_limit.login.window', 'rates_limit.login.max',
|
||||
'rates_limit.signup.window', 'rates_limit.signup.max', 'rates_limit.ask_send_email.window', 'rates_limit.ask_send_email.max',
|
||||
'rates_limit.receive_client_log.window', 'rates_limit.receive_client_log.max', 'rates_limit.plugins.window', 'rates_limit.plugins.max',
|
||||
'rates_limit.well_known.window', 'rates_limit.well_known.max', 'rates_limit.feeds.window', 'rates_limit.feeds.max',
|
||||
'rates_limit.activity_pub.window', 'rates_limit.activity_pub.max', 'rates_limit.client.window', 'rates_limit.client.max',
|
||||
'static_files.private_files_require_auth',
|
||||
'object_storage.enabled', 'object_storage.endpoint', 'object_storage.region', 'object_storage.upload_acl.public',
|
||||
'object_storage.upload_acl.private', 'object_storage.proxy.proxify_private_files', 'object_storage.credentials.access_key_id',
|
||||
'object_storage.credentials.secret_access_key', 'object_storage.max_upload_part', 'object_storage.streaming_playlists.bucket_name',
|
||||
'object_storage.streaming_playlists.prefix', 'object_storage.streaming_playlists.base_url', 'object_storage.web_videos.bucket_name',
|
||||
'object_storage.web_videos.prefix', 'object_storage.web_videos.base_url', 'object_storage.original_video_files.bucket_name',
|
||||
'object_storage.original_video_files.prefix', 'object_storage.original_video_files.base_url', 'object_storage.max_request_attempts',
|
||||
'theme.default',
|
||||
'feeds.videos.count', 'feeds.comments.count',
|
||||
'geo_ip.enabled', 'geo_ip.country.database_url', 'geo_ip.city.database_url',
|
||||
'remote_redundancy.videos.accept_from',
|
||||
'federation.videos.federate_unlisted', 'federation.videos.cleanup_remote_interactions',
|
||||
'peertube.check_latest_version.enabled', 'peertube.check_latest_version.url',
|
||||
'search.remote_uri.users', 'search.remote_uri.anonymous', 'search.search_index.enabled', 'search.search_index.url',
|
||||
'search.search_index.disable_local_search', 'search.search_index.is_default_search',
|
||||
'live.enabled', 'live.allow_replay', 'live.latency_setting.enabled', 'live.max_duration',
|
||||
'live.max_user_lives', 'live.max_instance_lives',
|
||||
'live.rtmp.enabled', 'live.rtmp.port', 'live.rtmp.hostname', 'live.rtmp.public_hostname',
|
||||
'live.rtmps.enabled', 'live.rtmps.port', 'live.rtmps.hostname', 'live.rtmps.public_hostname',
|
||||
'live.rtmps.key_file', 'live.rtmps.cert_file',
|
||||
'live.transcoding.enabled', 'live.transcoding.threads', 'live.transcoding.profile',
|
||||
'live.transcoding.resolutions.144p', 'live.transcoding.resolutions.240p', 'live.transcoding.resolutions.360p',
|
||||
'live.transcoding.resolutions.480p', 'live.transcoding.resolutions.720p', 'live.transcoding.resolutions.1080p',
|
||||
'live.transcoding.resolutions.1440p', 'live.transcoding.resolutions.2160p', 'live.transcoding.always_transcode_original_resolution',
|
||||
'live.transcoding.remote_runners.enabled',
|
||||
'storyboards.enabled'
|
||||
]
|
||||
|
||||
const requiredAlternatives = [
|
||||
[ // set
|
||||
[ 'redis.hostname', 'redis.port' ], // alternative
|
||||
[ 'redis.socket' ],
|
||||
[ 'redis.sentinel.master_name', 'redis.sentinel.sentinels[0].hostname', 'redis.sentinel.sentinels[0].port' ]
|
||||
]
|
||||
]
|
||||
const miss: string[] = []
|
||||
|
||||
for (const key of required) {
|
||||
if (!config.has(key)) {
|
||||
miss.push(key)
|
||||
}
|
||||
}
|
||||
|
||||
const redundancyVideos = config.get<any>('redundancy.videos.strategies')
|
||||
|
||||
if (Array.isArray(redundancyVideos)) {
|
||||
for (const r of redundancyVideos) {
|
||||
if (!r.size) miss.push('redundancy.videos.strategies.size')
|
||||
if (!r.min_lifetime) miss.push('redundancy.videos.strategies.min_lifetime')
|
||||
}
|
||||
}
|
||||
|
||||
const missingAlternatives = requiredAlternatives.filter(
|
||||
set => !set.find(alternative => !alternative.find(key => !config.has(key)))
|
||||
)
|
||||
|
||||
missingAlternatives
|
||||
.forEach(set => set[0].forEach(key => miss.push(key)))
|
||||
|
||||
return miss
|
||||
}
|
||||
|
||||
// Check the available codecs
|
||||
// We get CONFIG by param to not import it in this file (import orders)
|
||||
async function checkFFmpeg (CONFIG: { TRANSCODING: { ENABLED: boolean } }) {
|
||||
if (CONFIG.TRANSCODING.ENABLED === false) return undefined
|
||||
|
||||
const Ffmpeg = (await import('fluent-ffmpeg')).default
|
||||
const getAvailableCodecsPromise = promisify0(Ffmpeg.getAvailableCodecs)
|
||||
const codecs = await getAvailableCodecsPromise()
|
||||
const canEncode = [ 'libx264' ]
|
||||
|
||||
for (const codec of canEncode) {
|
||||
if (codecs[codec] === undefined) {
|
||||
throw new Error('Unknown codec ' + codec + ' in FFmpeg.')
|
||||
}
|
||||
|
||||
if (codecs[codec].canEncode !== true) {
|
||||
throw new Error('Unavailable encode codec ' + codec + ' in FFmpeg')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checkNodeVersion () {
|
||||
const v = process.version
|
||||
const { major } = parseSemVersion(v)
|
||||
|
||||
logger.debug('Checking NodeJS version %s.', v)
|
||||
|
||||
if (major <= 12) {
|
||||
throw new Error('Your NodeJS version ' + v + ' is not supported. Please upgrade.')
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
checkFFmpeg,
|
||||
checkMissedConfig,
|
||||
checkNodeVersion
|
||||
}
|
||||
@@ -0,0 +1,770 @@
|
||||
import bytes from 'bytes'
|
||||
import { IConfig } from 'config'
|
||||
import { createRequire } from 'module'
|
||||
import { dirname, join } from 'path'
|
||||
import {
|
||||
BroadcastMessageLevel,
|
||||
NSFWPolicyType,
|
||||
VideoCommentPolicyType,
|
||||
VideoPrivacyType,
|
||||
VideoRedundancyConfigFilter,
|
||||
VideosRedundancyStrategy
|
||||
} from '@peertube/peertube-models'
|
||||
import { decacheModule } from '@server/helpers/decache.js'
|
||||
import { buildPath, root } from '@peertube/peertube-node-utils'
|
||||
import { parseBytes, parseDurationToMs } from '../helpers/core-utils.js'
|
||||
import { TranscriptionEngineName, WhisperBuiltinModelName } from '@peertube/peertube-transcription'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
let config: IConfig = require('config')
|
||||
|
||||
const configChangedHandlers: Function[] = []
|
||||
|
||||
const CONFIG = {
|
||||
CUSTOM_FILE: getLocalConfigFilePath(),
|
||||
LISTEN: {
|
||||
PORT: config.get<number>('listen.port'),
|
||||
HOSTNAME: config.get<string>('listen.hostname')
|
||||
},
|
||||
SECRETS: {
|
||||
PEERTUBE: config.get<string>('secrets.peertube')
|
||||
},
|
||||
DATABASE: {
|
||||
DBNAME: config.has('database.name') ? config.get<string>('database.name') : 'peertube' + config.get<string>('database.suffix'),
|
||||
HOSTNAME: config.get<string>('database.hostname'),
|
||||
PORT: config.get<number>('database.port'),
|
||||
SSL: config.get<boolean>('database.ssl'),
|
||||
USERNAME: config.get<string>('database.username'),
|
||||
PASSWORD: config.get<string>('database.password'),
|
||||
POOL: {
|
||||
MAX: config.get<number>('database.pool.max')
|
||||
}
|
||||
},
|
||||
REDIS: {
|
||||
HOSTNAME: config.has('redis.hostname') ? config.get<string>('redis.hostname') : null,
|
||||
PORT: config.has('redis.port') ? config.get<number>('redis.port') : null,
|
||||
SOCKET: config.has('redis.socket') ? config.get<string>('redis.socket') : null,
|
||||
AUTH: config.has('redis.auth') ? config.get<string>('redis.auth') : null,
|
||||
DB: config.has('redis.db') ? config.get<number>('redis.db') : null,
|
||||
SENTINEL: {
|
||||
ENABLED: config.has('redis.sentinel.enabled') ? config.get<boolean>('redis.sentinel.enabled') : false,
|
||||
ENABLE_TLS: config.has('redis.sentinel.enable_tls') ? config.get<boolean>('redis.sentinel.enable_tls') : false,
|
||||
SENTINELS: config.has('redis.sentinel.sentinels') ? config.get<{ hostname: string, port: number }[]>('redis.sentinel.sentinels') : [],
|
||||
MASTER_NAME: config.has('redis.sentinel.master_name') ? config.get<string>('redis.sentinel.master_name') : null
|
||||
}
|
||||
},
|
||||
SMTP: {
|
||||
TRANSPORT: config.has('smtp.transport') ? config.get<string>('smtp.transport') : 'smtp',
|
||||
SENDMAIL: config.has('smtp.sendmail') ? config.get<string>('smtp.sendmail') : null,
|
||||
HOSTNAME: config.get<string>('smtp.hostname'),
|
||||
PORT: config.get<number>('smtp.port'),
|
||||
USERNAME: config.get<string>('smtp.username'),
|
||||
PASSWORD: config.get<string>('smtp.password'),
|
||||
TLS: config.get<boolean>('smtp.tls'),
|
||||
DISABLE_STARTTLS: config.get<boolean>('smtp.disable_starttls'),
|
||||
CA_FILE: config.get<string>('smtp.ca_file'),
|
||||
FROM_ADDRESS: config.get<string>('smtp.from_address')
|
||||
},
|
||||
EMAIL: {
|
||||
BODY: {
|
||||
SIGNATURE: config.get<string>('email.body.signature')
|
||||
},
|
||||
SUBJECT: {
|
||||
PREFIX: config.get<string>('email.subject.prefix') + ' '
|
||||
}
|
||||
},
|
||||
|
||||
CLIENT: {
|
||||
VIDEOS: {
|
||||
MINIATURE: {
|
||||
get PREFER_AUTHOR_DISPLAY_NAME () { return config.get<boolean>('client.videos.miniature.prefer_author_display_name') },
|
||||
get DISPLAY_AUTHOR_AVATAR () { return config.get<boolean>('client.videos.miniature.display_author_avatar') }
|
||||
},
|
||||
RESUMABLE_UPLOAD: {
|
||||
get MAX_CHUNK_SIZE () { return parseBytes(config.get<number>('client.videos.resumable_upload.max_chunk_size') || 0) }
|
||||
}
|
||||
},
|
||||
MENU: {
|
||||
LOGIN: {
|
||||
get REDIRECT_ON_SINGLE_EXTERNAL_AUTH () { return config.get<boolean>('client.menu.login.redirect_on_single_external_auth') }
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
DEFAULTS: {
|
||||
PUBLISH: {
|
||||
DOWNLOAD_ENABLED: config.get<boolean>('defaults.publish.download_enabled'),
|
||||
COMMENTS_POLICY: config.get<VideoCommentPolicyType>('defaults.publish.comments_policy'),
|
||||
PRIVACY: config.get<VideoPrivacyType>('defaults.publish.privacy'),
|
||||
LICENCE: config.get<number>('defaults.publish.licence')
|
||||
},
|
||||
P2P: {
|
||||
WEBAPP: {
|
||||
ENABLED: config.get<boolean>('defaults.p2p.webapp.enabled')
|
||||
},
|
||||
EMBED: {
|
||||
ENABLED: config.get<boolean>('defaults.p2p.embed.enabled')
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
STORAGE: {
|
||||
TMP_DIR: buildPath(config.get<string>('storage.tmp')),
|
||||
TMP_PERSISTENT_DIR: buildPath(config.get<string>('storage.tmp_persistent')),
|
||||
BIN_DIR: buildPath(config.get<string>('storage.bin')),
|
||||
ACTOR_IMAGES_DIR: buildPath(config.get<string>('storage.avatars')),
|
||||
LOG_DIR: buildPath(config.get<string>('storage.logs')),
|
||||
WEB_VIDEOS_DIR: buildPath(config.get<string>('storage.web_videos')),
|
||||
STREAMING_PLAYLISTS_DIR: buildPath(config.get<string>('storage.streaming_playlists')),
|
||||
ORIGINAL_VIDEO_FILES_DIR: buildPath(config.get<string>('storage.original_video_files')),
|
||||
REDUNDANCY_DIR: buildPath(config.get<string>('storage.redundancy')),
|
||||
THUMBNAILS_DIR: buildPath(config.get<string>('storage.thumbnails')),
|
||||
STORYBOARDS_DIR: buildPath(config.get<string>('storage.storyboards')),
|
||||
PREVIEWS_DIR: buildPath(config.get<string>('storage.previews')),
|
||||
CAPTIONS_DIR: buildPath(config.get<string>('storage.captions')),
|
||||
TORRENTS_DIR: buildPath(config.get<string>('storage.torrents')),
|
||||
CACHE_DIR: buildPath(config.get<string>('storage.cache')),
|
||||
PLUGINS_DIR: buildPath(config.get<string>('storage.plugins')),
|
||||
CLIENT_OVERRIDES_DIR: buildPath(config.get<string>('storage.client_overrides')),
|
||||
WELL_KNOWN_DIR: buildPath(config.get<string>('storage.well_known'))
|
||||
},
|
||||
STATIC_FILES: {
|
||||
PRIVATE_FILES_REQUIRE_AUTH: config.get<boolean>('static_files.private_files_require_auth')
|
||||
},
|
||||
OBJECT_STORAGE: {
|
||||
ENABLED: config.get<boolean>('object_storage.enabled'),
|
||||
MAX_UPLOAD_PART: bytes.parse(config.get<string>('object_storage.max_upload_part')),
|
||||
MAX_REQUEST_ATTEMPTS: config.get<number>('object_storage.max_request_attempts'),
|
||||
ENDPOINT: config.get<string>('object_storage.endpoint'),
|
||||
REGION: config.get<string>('object_storage.region'),
|
||||
UPLOAD_ACL: {
|
||||
PUBLIC: config.get<string>('object_storage.upload_acl.public'),
|
||||
PRIVATE: config.get<string>('object_storage.upload_acl.private')
|
||||
},
|
||||
CREDENTIALS: {
|
||||
ACCESS_KEY_ID: config.get<string>('object_storage.credentials.access_key_id'),
|
||||
SECRET_ACCESS_KEY: config.get<string>('object_storage.credentials.secret_access_key')
|
||||
},
|
||||
PROXY: {
|
||||
PROXIFY_PRIVATE_FILES: config.get<boolean>('object_storage.proxy.proxify_private_files')
|
||||
},
|
||||
WEB_VIDEOS: {
|
||||
BUCKET_NAME: config.get<string>('object_storage.web_videos.bucket_name'),
|
||||
PREFIX: config.get<string>('object_storage.web_videos.prefix'),
|
||||
BASE_URL: config.get<string>('object_storage.web_videos.base_url')
|
||||
},
|
||||
STREAMING_PLAYLISTS: {
|
||||
BUCKET_NAME: config.get<string>('object_storage.streaming_playlists.bucket_name'),
|
||||
PREFIX: config.get<string>('object_storage.streaming_playlists.prefix'),
|
||||
BASE_URL: config.get<string>('object_storage.streaming_playlists.base_url'),
|
||||
STORE_LIVE_STREAMS: config.get<string>('object_storage.streaming_playlists.store_live_streams')
|
||||
},
|
||||
USER_EXPORTS: {
|
||||
BUCKET_NAME: config.get<string>('object_storage.user_exports.bucket_name'),
|
||||
PREFIX: config.get<string>('object_storage.user_exports.prefix'),
|
||||
BASE_URL: config.get<string>('object_storage.user_exports.base_url')
|
||||
},
|
||||
ORIGINAL_VIDEO_FILES: {
|
||||
BUCKET_NAME: config.get<string>('object_storage.original_video_files.bucket_name'),
|
||||
PREFIX: config.get<string>('object_storage.original_video_files.prefix'),
|
||||
BASE_URL: config.get<string>('object_storage.original_video_files.base_url')
|
||||
}
|
||||
},
|
||||
WEBSERVER: {
|
||||
SCHEME: config.get<boolean>('webserver.https') === true ? 'https' : 'http',
|
||||
WS: config.get<boolean>('webserver.https') === true ? 'wss' : 'ws',
|
||||
HOSTNAME: config.get<string>('webserver.hostname'),
|
||||
PORT: config.get<number>('webserver.port')
|
||||
},
|
||||
OAUTH2: {
|
||||
TOKEN_LIFETIME: {
|
||||
ACCESS_TOKEN: parseDurationToMs(config.get<string>('oauth2.token_lifetime.access_token')),
|
||||
REFRESH_TOKEN: parseDurationToMs(config.get<string>('oauth2.token_lifetime.refresh_token'))
|
||||
}
|
||||
},
|
||||
RATES_LIMIT: {
|
||||
API: {
|
||||
WINDOW_MS: parseDurationToMs(config.get<string>('rates_limit.api.window')),
|
||||
MAX: config.get<number>('rates_limit.api.max')
|
||||
},
|
||||
SIGNUP: {
|
||||
WINDOW_MS: parseDurationToMs(config.get<string>('rates_limit.signup.window')),
|
||||
MAX: config.get<number>('rates_limit.signup.max')
|
||||
},
|
||||
LOGIN: {
|
||||
WINDOW_MS: parseDurationToMs(config.get<string>('rates_limit.login.window')),
|
||||
MAX: config.get<number>('rates_limit.login.max')
|
||||
},
|
||||
RECEIVE_CLIENT_LOG: {
|
||||
WINDOW_MS: parseDurationToMs(config.get<string>('rates_limit.receive_client_log.window')),
|
||||
MAX: config.get<number>('rates_limit.receive_client_log.max')
|
||||
},
|
||||
ASK_SEND_EMAIL: {
|
||||
WINDOW_MS: parseDurationToMs(config.get<string>('rates_limit.ask_send_email.window')),
|
||||
MAX: config.get<number>('rates_limit.ask_send_email.max')
|
||||
},
|
||||
PLUGINS: {
|
||||
WINDOW_MS: parseDurationToMs(config.get<string>('rates_limit.plugins.window')),
|
||||
MAX: config.get<number>('rates_limit.plugins.max')
|
||||
},
|
||||
WELL_KNOWN: {
|
||||
WINDOW_MS: parseDurationToMs(config.get<string>('rates_limit.well_known.window')),
|
||||
MAX: config.get<number>('rates_limit.well_known.max')
|
||||
},
|
||||
FEEDS: {
|
||||
WINDOW_MS: parseDurationToMs(config.get<string>('rates_limit.feeds.window')),
|
||||
MAX: config.get<number>('rates_limit.feeds.max')
|
||||
},
|
||||
ACTIVITY_PUB: {
|
||||
WINDOW_MS: parseDurationToMs(config.get<string>('rates_limit.activity_pub.window')),
|
||||
MAX: config.get<number>('rates_limit.activity_pub.max')
|
||||
},
|
||||
CLIENT: {
|
||||
WINDOW_MS: parseDurationToMs(config.get<string>('rates_limit.client.window')),
|
||||
MAX: config.get<number>('rates_limit.client.max')
|
||||
}
|
||||
},
|
||||
TRUST_PROXY: config.get<string[]>('trust_proxy'),
|
||||
LOG: {
|
||||
LEVEL: config.get<string>('log.level'),
|
||||
ROTATION: {
|
||||
ENABLED: config.get<boolean>('log.rotation.enabled'),
|
||||
MAX_FILE_SIZE: bytes.parse(config.get<string>('log.rotation.max_file_size')),
|
||||
MAX_FILES: config.get<number>('log.rotation.max_files')
|
||||
},
|
||||
ANONYMIZE_IP: config.get<boolean>('log.anonymize_ip'),
|
||||
LOG_PING_REQUESTS: config.get<boolean>('log.log_ping_requests'),
|
||||
LOG_TRACKER_UNKNOWN_INFOHASH: config.get<boolean>('log.log_tracker_unknown_infohash'),
|
||||
LOG_HTTP_REQUESTS: config.get<boolean>('log.log_http_requests'),
|
||||
PRETTIFY_SQL: config.get<boolean>('log.prettify_sql'),
|
||||
ACCEPT_CLIENT_LOG: config.get<boolean>('log.accept_client_log')
|
||||
},
|
||||
OPEN_TELEMETRY: {
|
||||
METRICS: {
|
||||
ENABLED: config.get<boolean>('open_telemetry.metrics.enabled'),
|
||||
|
||||
PLAYBACK_STATS_INTERVAL: parseDurationToMs(config.get<string>('open_telemetry.metrics.playback_stats_interval')),
|
||||
|
||||
HTTP_REQUEST_DURATION: {
|
||||
ENABLED: config.get<boolean>('open_telemetry.metrics.http_request_duration.enabled')
|
||||
},
|
||||
|
||||
PROMETHEUS_EXPORTER: {
|
||||
HOSTNAME: config.get<string>('open_telemetry.metrics.prometheus_exporter.hostname'),
|
||||
PORT: config.get<number>('open_telemetry.metrics.prometheus_exporter.port')
|
||||
}
|
||||
},
|
||||
TRACING: {
|
||||
ENABLED: config.get<boolean>('open_telemetry.tracing.enabled'),
|
||||
|
||||
JAEGER_EXPORTER: {
|
||||
ENDPOINT: config.get<string>('open_telemetry.tracing.jaeger_exporter.endpoint')
|
||||
}
|
||||
}
|
||||
},
|
||||
TRENDING: {
|
||||
VIDEOS: {
|
||||
INTERVAL_DAYS: config.get<number>('trending.videos.interval_days'),
|
||||
ALGORITHMS: {
|
||||
get ENABLED () { return config.get<string[]>('trending.videos.algorithms.enabled') },
|
||||
get DEFAULT () { return config.get<string>('trending.videos.algorithms.default') }
|
||||
}
|
||||
}
|
||||
},
|
||||
REDUNDANCY: {
|
||||
VIDEOS: {
|
||||
CHECK_INTERVAL: parseDurationToMs(config.get<string>('redundancy.videos.check_interval')),
|
||||
STRATEGIES: buildVideosRedundancy(config.get<any[]>('redundancy.videos.strategies'))
|
||||
}
|
||||
},
|
||||
REMOTE_REDUNDANCY: {
|
||||
VIDEOS: {
|
||||
ACCEPT_FROM: config.get<VideoRedundancyConfigFilter>('remote_redundancy.videos.accept_from')
|
||||
}
|
||||
},
|
||||
CSP: {
|
||||
ENABLED: config.get<boolean>('csp.enabled'),
|
||||
REPORT_ONLY: config.get<boolean>('csp.report_only'),
|
||||
REPORT_URI: config.get<string>('csp.report_uri')
|
||||
},
|
||||
SECURITY: {
|
||||
FRAMEGUARD: {
|
||||
ENABLED: config.get<boolean>('security.frameguard.enabled')
|
||||
},
|
||||
POWERED_BY_HEADER: {
|
||||
ENABLED: config.get<boolean>('security.powered_by_header.enabled')
|
||||
}
|
||||
},
|
||||
TRACKER: {
|
||||
ENABLED: config.get<boolean>('tracker.enabled'),
|
||||
PRIVATE: config.get<boolean>('tracker.private'),
|
||||
REJECT_TOO_MANY_ANNOUNCES: config.get<boolean>('tracker.reject_too_many_announces')
|
||||
},
|
||||
HISTORY: {
|
||||
VIDEOS: {
|
||||
MAX_AGE: parseDurationToMs(config.get('history.videos.max_age'))
|
||||
}
|
||||
},
|
||||
VIEWS: {
|
||||
VIDEOS: {
|
||||
REMOTE: {
|
||||
MAX_AGE: parseDurationToMs(config.get('views.videos.remote.max_age'))
|
||||
},
|
||||
LOCAL_BUFFER_UPDATE_INTERVAL: parseDurationToMs(config.get('views.videos.local_buffer_update_interval')),
|
||||
VIEW_EXPIRATION: parseDurationToMs(config.get('views.videos.view_expiration')),
|
||||
COUNT_VIEW_AFTER: parseDurationToMs(config.get<number>('views.videos.count_view_after')),
|
||||
TRUST_VIEWER_SESSION_ID: config.get<boolean>('views.videos.trust_viewer_session_id'),
|
||||
WATCHING_INTERVAL: {
|
||||
ANONYMOUS: parseDurationToMs(config.get<string>('views.videos.watching_interval.anonymous')),
|
||||
USERS: parseDurationToMs(config.get<string>('views.videos.watching_interval.users'))
|
||||
}
|
||||
}
|
||||
},
|
||||
GEO_IP: {
|
||||
ENABLED: config.get<boolean>('geo_ip.enabled'),
|
||||
COUNTRY: {
|
||||
DATABASE_URL: config.get<string>('geo_ip.country.database_url')
|
||||
},
|
||||
CITY: {
|
||||
DATABASE_URL: config.get<string>('geo_ip.city.database_url')
|
||||
}
|
||||
},
|
||||
PLUGINS: {
|
||||
INDEX: {
|
||||
ENABLED: config.get<boolean>('plugins.index.enabled'),
|
||||
CHECK_LATEST_VERSIONS_INTERVAL: parseDurationToMs(config.get<string>('plugins.index.check_latest_versions_interval')),
|
||||
URL: config.get<string>('plugins.index.url')
|
||||
}
|
||||
},
|
||||
FEDERATION: {
|
||||
VIDEOS: {
|
||||
FEDERATE_UNLISTED: config.get<boolean>('federation.videos.federate_unlisted'),
|
||||
CLEANUP_REMOTE_INTERACTIONS: config.get<boolean>('federation.videos.cleanup_remote_interactions')
|
||||
},
|
||||
SIGN_FEDERATED_FETCHES: config.get<boolean>('federation.sign_federated_fetches')
|
||||
},
|
||||
PEERTUBE: {
|
||||
CHECK_LATEST_VERSION: {
|
||||
ENABLED: config.get<boolean>('peertube.check_latest_version.enabled'),
|
||||
URL: config.get<string>('peertube.check_latest_version.url')
|
||||
}
|
||||
},
|
||||
WEBADMIN: {
|
||||
CONFIGURATION: {
|
||||
EDITION: {
|
||||
ALLOWED: config.get<boolean>('webadmin.configuration.edition.allowed')
|
||||
}
|
||||
}
|
||||
},
|
||||
FEEDS: {
|
||||
VIDEOS: {
|
||||
COUNT: config.get<number>('feeds.videos.count')
|
||||
},
|
||||
COMMENTS: {
|
||||
COUNT: config.get<number>('feeds.comments.count')
|
||||
}
|
||||
},
|
||||
REMOTE_RUNNERS: {
|
||||
STALLED_JOBS: {
|
||||
LIVE: parseDurationToMs(config.get<string>('remote_runners.stalled_jobs.live')),
|
||||
VOD: parseDurationToMs(config.get<string>('remote_runners.stalled_jobs.vod'))
|
||||
}
|
||||
},
|
||||
THUMBNAILS: {
|
||||
GENERATION_FROM_VIDEO: {
|
||||
FRAMES_TO_ANALYZE: config.get<number>('thumbnails.generation_from_video.frames_to_analyze')
|
||||
},
|
||||
SIZES: config.get<{ width: number, height: number }[]>('thumbnails.sizes')
|
||||
},
|
||||
STATS: {
|
||||
REGISTRATION_REQUESTS: {
|
||||
ENABLED: config.get<boolean>('stats.registration_requests.enabled')
|
||||
},
|
||||
ABUSES: {
|
||||
ENABLED: config.get<boolean>('stats.abuses.enabled')
|
||||
},
|
||||
TOTAL_MODERATORS: {
|
||||
ENABLED: config.get<boolean>('stats.total_moderators.enabled')
|
||||
},
|
||||
TOTAL_ADMINS: {
|
||||
ENABLED: config.get<boolean>('stats.total_admins.enabled')
|
||||
}
|
||||
},
|
||||
ADMIN: {
|
||||
get EMAIL () { return config.get<string>('admin.email') }
|
||||
},
|
||||
CONTACT_FORM: {
|
||||
get ENABLED () { return config.get<boolean>('contact_form.enabled') }
|
||||
},
|
||||
SIGNUP: {
|
||||
get ENABLED () { return config.get<boolean>('signup.enabled') },
|
||||
get REQUIRES_APPROVAL () { return config.get<boolean>('signup.requires_approval') },
|
||||
get LIMIT () { return config.get<number>('signup.limit') },
|
||||
get REQUIRES_EMAIL_VERIFICATION () { return config.get<boolean>('signup.requires_email_verification') },
|
||||
get MINIMUM_AGE () { return config.get<number>('signup.minimum_age') },
|
||||
FILTERS: {
|
||||
CIDR: {
|
||||
get WHITELIST () { return config.get<string[]>('signup.filters.cidr.whitelist') },
|
||||
get BLACKLIST () { return config.get<string[]>('signup.filters.cidr.blacklist') }
|
||||
}
|
||||
}
|
||||
},
|
||||
USER: {
|
||||
HISTORY: {
|
||||
VIDEOS: {
|
||||
get ENABLED () { return config.get<boolean>('user.history.videos.enabled') }
|
||||
}
|
||||
},
|
||||
get VIDEO_QUOTA () { return parseBytes(config.get<number>('user.video_quota')) },
|
||||
get VIDEO_QUOTA_DAILY () { return parseBytes(config.get<number>('user.video_quota_daily')) },
|
||||
get DEFAULT_CHANNEL_NAME () { return config.get<string>('user.default_channel_name') }
|
||||
},
|
||||
VIDEO_CHANNELS: {
|
||||
get MAX_PER_USER () { return config.get<number>('video_channels.max_per_user') }
|
||||
},
|
||||
TRANSCODING: {
|
||||
get ENABLED () { return config.get<boolean>('transcoding.enabled') },
|
||||
ORIGINAL_FILE: {
|
||||
get KEEP () { return config.get<boolean>('transcoding.original_file.keep') }
|
||||
},
|
||||
get ALLOW_ADDITIONAL_EXTENSIONS () { return config.get<boolean>('transcoding.allow_additional_extensions') },
|
||||
get ALLOW_AUDIO_FILES () { return config.get<boolean>('transcoding.allow_audio_files') },
|
||||
get THREADS () { return config.get<number>('transcoding.threads') },
|
||||
get CONCURRENCY () { return config.get<number>('transcoding.concurrency') },
|
||||
get PROFILE () { return config.get<string>('transcoding.profile') },
|
||||
get ALWAYS_TRANSCODE_ORIGINAL_RESOLUTION () { return config.get<boolean>('transcoding.always_transcode_original_resolution') },
|
||||
RESOLUTIONS: {
|
||||
get '0p' () { return config.get<boolean>('transcoding.resolutions.0p') },
|
||||
get '144p' () { return config.get<boolean>('transcoding.resolutions.144p') },
|
||||
get '240p' () { return config.get<boolean>('transcoding.resolutions.240p') },
|
||||
get '360p' () { return config.get<boolean>('transcoding.resolutions.360p') },
|
||||
get '480p' () { return config.get<boolean>('transcoding.resolutions.480p') },
|
||||
get '720p' () { return config.get<boolean>('transcoding.resolutions.720p') },
|
||||
get '1080p' () { return config.get<boolean>('transcoding.resolutions.1080p') },
|
||||
get '1440p' () { return config.get<boolean>('transcoding.resolutions.1440p') },
|
||||
get '2160p' () { return config.get<boolean>('transcoding.resolutions.2160p') }
|
||||
},
|
||||
HLS: {
|
||||
get ENABLED () { return config.get<boolean>('transcoding.hls.enabled') }
|
||||
},
|
||||
WEB_VIDEOS: {
|
||||
get ENABLED () { return config.get<boolean>('transcoding.web_videos.enabled') }
|
||||
},
|
||||
REMOTE_RUNNERS: {
|
||||
get ENABLED () { return config.get<boolean>('transcoding.remote_runners.enabled') }
|
||||
}
|
||||
},
|
||||
LIVE: {
|
||||
get ENABLED () { return config.get<boolean>('live.enabled') },
|
||||
|
||||
get MAX_DURATION () { return parseDurationToMs(config.get<string>('live.max_duration')) },
|
||||
get MAX_INSTANCE_LIVES () { return config.get<number>('live.max_instance_lives') },
|
||||
get MAX_USER_LIVES () { return config.get<number>('live.max_user_lives') },
|
||||
|
||||
get ALLOW_REPLAY () { return config.get<boolean>('live.allow_replay') },
|
||||
|
||||
LATENCY_SETTING: {
|
||||
get ENABLED () { return config.get<boolean>('live.latency_setting.enabled') }
|
||||
},
|
||||
|
||||
RTMP: {
|
||||
get ENABLED () { return config.get<boolean>('live.rtmp.enabled') },
|
||||
get PORT () { return config.get<number>('live.rtmp.port') },
|
||||
get HOSTNAME () { return config.get<number>('live.rtmp.hostname') },
|
||||
get PUBLIC_HOSTNAME () { return config.get<number>('live.rtmp.public_hostname') }
|
||||
},
|
||||
|
||||
RTMPS: {
|
||||
get ENABLED () { return config.get<boolean>('live.rtmps.enabled') },
|
||||
get PORT () { return config.get<number>('live.rtmps.port') },
|
||||
get HOSTNAME () { return config.get<number>('live.rtmps.hostname') },
|
||||
get PUBLIC_HOSTNAME () { return config.get<number>('live.rtmps.public_hostname') },
|
||||
get KEY_FILE () { return config.get<string>('live.rtmps.key_file') },
|
||||
get CERT_FILE () { return config.get<string>('live.rtmps.cert_file') }
|
||||
},
|
||||
|
||||
TRANSCODING: {
|
||||
get ENABLED () { return config.get<boolean>('live.transcoding.enabled') },
|
||||
get THREADS () { return config.get<number>('live.transcoding.threads') },
|
||||
get PROFILE () { return config.get<string>('live.transcoding.profile') },
|
||||
|
||||
get ALWAYS_TRANSCODE_ORIGINAL_RESOLUTION () { return config.get<boolean>('live.transcoding.always_transcode_original_resolution') },
|
||||
|
||||
RESOLUTIONS: {
|
||||
get '144p' () { return config.get<boolean>('live.transcoding.resolutions.144p') },
|
||||
get '240p' () { return config.get<boolean>('live.transcoding.resolutions.240p') },
|
||||
get '360p' () { return config.get<boolean>('live.transcoding.resolutions.360p') },
|
||||
get '480p' () { return config.get<boolean>('live.transcoding.resolutions.480p') },
|
||||
get '720p' () { return config.get<boolean>('live.transcoding.resolutions.720p') },
|
||||
get '1080p' () { return config.get<boolean>('live.transcoding.resolutions.1080p') },
|
||||
get '1440p' () { return config.get<boolean>('live.transcoding.resolutions.1440p') },
|
||||
get '2160p' () { return config.get<boolean>('live.transcoding.resolutions.2160p') }
|
||||
},
|
||||
REMOTE_RUNNERS: {
|
||||
get ENABLED () { return config.get<boolean>('live.transcoding.remote_runners.enabled') }
|
||||
}
|
||||
}
|
||||
},
|
||||
VIDEO_STUDIO: {
|
||||
get ENABLED () { return config.get<boolean>('video_studio.enabled') },
|
||||
REMOTE_RUNNERS: {
|
||||
get ENABLED () { return config.get<boolean>('video_studio.remote_runners.enabled') }
|
||||
}
|
||||
},
|
||||
VIDEO_FILE: {
|
||||
UPDATE: {
|
||||
get ENABLED () { return config.get<boolean>('video_file.update.enabled') }
|
||||
}
|
||||
},
|
||||
VIDEO_TRANSCRIPTION: {
|
||||
get ENABLED () { return config.get<boolean>('video_transcription.enabled') },
|
||||
get ENGINE () { return config.get<TranscriptionEngineName>('video_transcription.engine') },
|
||||
get ENGINE_PATH () { return config.get<string>('video_transcription.engine_path') },
|
||||
get MODEL () { return config.get<WhisperBuiltinModelName>('video_transcription.model') },
|
||||
get MODEL_PATH () { return config.get<string>('video_transcription.model_path') },
|
||||
REMOTE_RUNNERS: {
|
||||
get ENABLED () { return config.get<boolean>('video_transcription.remote_runners.enabled') }
|
||||
}
|
||||
},
|
||||
IMPORT: {
|
||||
VIDEOS: {
|
||||
get CONCURRENCY () { return config.get<number>('import.videos.concurrency') },
|
||||
get TIMEOUT () { return parseDurationToMs(config.get<string>('import.videos.timeout')) },
|
||||
|
||||
HTTP: {
|
||||
get ENABLED () { return config.get<boolean>('import.videos.http.enabled') },
|
||||
|
||||
YOUTUBE_DL_RELEASE: {
|
||||
get URL () { return config.get<string>('import.videos.http.youtube_dl_release.url') },
|
||||
get NAME () { return config.get<string>('import.videos.http.youtube_dl_release.name') },
|
||||
get PYTHON_PATH () { return config.get<string>('import.videos.http.youtube_dl_release.python_path') }
|
||||
},
|
||||
|
||||
get FORCE_IPV4 () { return config.get<boolean>('import.videos.http.force_ipv4') }
|
||||
},
|
||||
TORRENT: {
|
||||
get ENABLED () { return config.get<boolean>('import.videos.torrent.enabled') }
|
||||
}
|
||||
},
|
||||
VIDEO_CHANNEL_SYNCHRONIZATION: {
|
||||
get ENABLED () { return config.get<boolean>('import.video_channel_synchronization.enabled') },
|
||||
get MAX_PER_USER () { return config.get<number>('import.video_channel_synchronization.max_per_user') },
|
||||
get CHECK_INTERVAL () { return parseDurationToMs(config.get<string>('import.video_channel_synchronization.check_interval')) },
|
||||
get VIDEOS_LIMIT_PER_SYNCHRONIZATION () {
|
||||
return config.get<number>('import.video_channel_synchronization.videos_limit_per_synchronization')
|
||||
},
|
||||
get FULL_SYNC_VIDEOS_LIMIT () {
|
||||
return config.get<number>('import.video_channel_synchronization.full_sync_videos_limit')
|
||||
}
|
||||
},
|
||||
USERS: {
|
||||
get ENABLED () { return config.get<boolean>('import.users.enabled') }
|
||||
}
|
||||
},
|
||||
EXPORT: {
|
||||
USERS: {
|
||||
get ENABLED () { return config.get<boolean>('export.users.enabled') },
|
||||
get MAX_USER_VIDEO_QUOTA () { return parseBytes(config.get<string>('export.users.max_user_video_quota')) },
|
||||
get EXPORT_EXPIRATION () { return parseDurationToMs(config.get<string>('export.users.export_expiration')) }
|
||||
}
|
||||
},
|
||||
AUTO_BLACKLIST: {
|
||||
VIDEOS: {
|
||||
OF_USERS: {
|
||||
get ENABLED () { return config.get<boolean>('auto_blacklist.videos.of_users.enabled') }
|
||||
}
|
||||
}
|
||||
},
|
||||
CACHE: {
|
||||
PREVIEWS: {
|
||||
get SIZE () { return config.get<number>('cache.previews.size') }
|
||||
},
|
||||
VIDEO_CAPTIONS: {
|
||||
get SIZE () { return config.get<number>('cache.captions.size') }
|
||||
},
|
||||
TORRENTS: {
|
||||
get SIZE () { return config.get<number>('cache.torrents.size') }
|
||||
},
|
||||
STORYBOARDS: {
|
||||
get SIZE () { return config.get<number>('cache.storyboards.size') }
|
||||
}
|
||||
},
|
||||
INSTANCE: {
|
||||
get NAME () { return config.get<string>('instance.name') },
|
||||
get SHORT_DESCRIPTION () { return config.get<string>('instance.short_description') },
|
||||
get DESCRIPTION () { return config.get<string>('instance.description') },
|
||||
get TERMS () { return config.get<string>('instance.terms') },
|
||||
get CODE_OF_CONDUCT () { return config.get<string>('instance.code_of_conduct') },
|
||||
|
||||
get CREATION_REASON () { return config.get<string>('instance.creation_reason') },
|
||||
|
||||
get MODERATION_INFORMATION () { return config.get<string>('instance.moderation_information') },
|
||||
get ADMINISTRATOR () { return config.get<string>('instance.administrator') },
|
||||
get MAINTENANCE_LIFETIME () { return config.get<string>('instance.maintenance_lifetime') },
|
||||
get BUSINESS_MODEL () { return config.get<string>('instance.business_model') },
|
||||
get HARDWARE_INFORMATION () { return config.get<string>('instance.hardware_information') },
|
||||
|
||||
get LANGUAGES () { return config.get<string[]>('instance.languages') || [] },
|
||||
get CATEGORIES () { return config.get<number[]>('instance.categories') || [] },
|
||||
|
||||
get IS_NSFW () { return config.get<boolean>('instance.is_nsfw') },
|
||||
get DEFAULT_NSFW_POLICY () { return config.get<NSFWPolicyType>('instance.default_nsfw_policy') },
|
||||
|
||||
get DEFAULT_CLIENT_ROUTE () { return config.get<string>('instance.default_client_route') },
|
||||
|
||||
CUSTOMIZATIONS: {
|
||||
get JAVASCRIPT () { return config.get<string>('instance.customizations.javascript') },
|
||||
get CSS () { return config.get<string>('instance.customizations.css') }
|
||||
},
|
||||
get ROBOTS () { return config.get<string>('instance.robots') },
|
||||
get SECURITYTXT () { return config.get<string>('instance.securitytxt') }
|
||||
},
|
||||
SERVICES: {
|
||||
TWITTER: {
|
||||
get USERNAME () { return config.get<string>('services.twitter.username') }
|
||||
}
|
||||
},
|
||||
FOLLOWERS: {
|
||||
INSTANCE: {
|
||||
get ENABLED () { return config.get<boolean>('followers.instance.enabled') },
|
||||
get MANUAL_APPROVAL () { return config.get<boolean>('followers.instance.manual_approval') }
|
||||
}
|
||||
},
|
||||
FOLLOWINGS: {
|
||||
INSTANCE: {
|
||||
AUTO_FOLLOW_BACK: {
|
||||
get ENABLED () {
|
||||
return config.get<boolean>('followings.instance.auto_follow_back.enabled')
|
||||
}
|
||||
},
|
||||
AUTO_FOLLOW_INDEX: {
|
||||
get ENABLED () {
|
||||
return config.get<boolean>('followings.instance.auto_follow_index.enabled')
|
||||
},
|
||||
get INDEX_URL () {
|
||||
return config.get<string>('followings.instance.auto_follow_index.index_url')
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
THEME: {
|
||||
get DEFAULT () { return config.get<string>('theme.default') }
|
||||
},
|
||||
BROADCAST_MESSAGE: {
|
||||
get ENABLED () { return config.get<boolean>('broadcast_message.enabled') },
|
||||
get MESSAGE () { return config.get<string>('broadcast_message.message') },
|
||||
get LEVEL () { return config.get<BroadcastMessageLevel>('broadcast_message.level') },
|
||||
get DISMISSABLE () { return config.get<boolean>('broadcast_message.dismissable') }
|
||||
},
|
||||
SEARCH: {
|
||||
REMOTE_URI: {
|
||||
get USERS () { return config.get<boolean>('search.remote_uri.users') },
|
||||
get ANONYMOUS () { return config.get<boolean>('search.remote_uri.anonymous') }
|
||||
},
|
||||
SEARCH_INDEX: {
|
||||
get ENABLED () { return config.get<boolean>('search.search_index.enabled') },
|
||||
get URL () { return config.get<string>('search.search_index.url') },
|
||||
get DISABLE_LOCAL_SEARCH () { return config.get<boolean>('search.search_index.disable_local_search') },
|
||||
get IS_DEFAULT_SEARCH () { return config.get<boolean>('search.search_index.is_default_search') }
|
||||
}
|
||||
},
|
||||
STORYBOARDS: {
|
||||
get ENABLED () { return config.get<boolean>('storyboards.enabled') }
|
||||
}
|
||||
}
|
||||
|
||||
function registerConfigChangedHandler (fun: Function) {
|
||||
configChangedHandlers.push(fun)
|
||||
}
|
||||
|
||||
function isEmailEnabled () {
|
||||
if (CONFIG.SMTP.TRANSPORT === 'sendmail' && CONFIG.SMTP.SENDMAIL) return true
|
||||
|
||||
if (CONFIG.SMTP.TRANSPORT === 'smtp' && CONFIG.SMTP.HOSTNAME && CONFIG.SMTP.PORT) return true
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
function getLocalConfigFilePath () {
|
||||
const localConfigDir = getLocalConfigDir()
|
||||
|
||||
let filename = 'local'
|
||||
if (process.env.NODE_ENV) filename += `-${process.env.NODE_ENV}`
|
||||
if (process.env.NODE_APP_INSTANCE) filename += `-${process.env.NODE_APP_INSTANCE}`
|
||||
|
||||
return join(localConfigDir, filename + '.json')
|
||||
}
|
||||
|
||||
function getConfigModule () {
|
||||
return config
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
CONFIG,
|
||||
getConfigModule,
|
||||
getLocalConfigFilePath,
|
||||
registerConfigChangedHandler,
|
||||
isEmailEnabled
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function getLocalConfigDir () {
|
||||
if (process.env.PEERTUBE_LOCAL_CONFIG) return process.env.PEERTUBE_LOCAL_CONFIG
|
||||
|
||||
const configSources = config.util.getConfigSources()
|
||||
if (configSources.length === 0) throw new Error('Invalid config source.')
|
||||
|
||||
return dirname(configSources[0].name)
|
||||
}
|
||||
|
||||
function buildVideosRedundancy (objs: any[]): VideosRedundancyStrategy[] {
|
||||
if (!objs) return []
|
||||
|
||||
if (!Array.isArray(objs)) return objs
|
||||
|
||||
return objs.map(obj => {
|
||||
return Object.assign({}, obj, {
|
||||
minLifetime: parseDurationToMs(obj.min_lifetime),
|
||||
size: bytes.parse(obj.size),
|
||||
minViews: obj.min_views
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export function reloadConfig () {
|
||||
|
||||
function getConfigDirectories () {
|
||||
if (process.env.NODE_CONFIG_DIR) {
|
||||
return process.env.NODE_CONFIG_DIR.split(':')
|
||||
}
|
||||
|
||||
return [ join(root(), 'config') ]
|
||||
}
|
||||
|
||||
function purge () {
|
||||
const directories = getConfigDirectories()
|
||||
|
||||
for (const fileName in require.cache) {
|
||||
if (directories.some((dir) => fileName.includes(dir)) === false) {
|
||||
continue
|
||||
}
|
||||
|
||||
delete require.cache[fileName]
|
||||
}
|
||||
|
||||
decacheModule(require, 'config')
|
||||
}
|
||||
|
||||
purge()
|
||||
|
||||
config = require('config')
|
||||
|
||||
for (const configChangedHandler of configChangedHandlers) {
|
||||
configChangedHandler()
|
||||
}
|
||||
|
||||
return Promise.resolve()
|
||||
}
|
||||
ファイル差分が大きすぎるため省略します
差分を読込み
@@ -0,0 +1,248 @@
|
||||
import { isTestOrDevInstance } from '@peertube/peertube-node-utils'
|
||||
import { ActorCustomPageModel } from '@server/models/account/actor-custom-page.js'
|
||||
import { AutomaticTagModel } from '@server/models/automatic-tag/automatic-tag.js'
|
||||
import { VideoAutomaticTagModel } from '@server/models/automatic-tag/video-automatic-tag.js'
|
||||
import { CommentAutomaticTagModel } from '@server/models/automatic-tag/comment-automatic-tag.js'
|
||||
import { RunnerJobModel } from '@server/models/runner/runner-job.js'
|
||||
import { RunnerRegistrationTokenModel } from '@server/models/runner/runner-registration-token.js'
|
||||
import { RunnerModel } from '@server/models/runner/runner.js'
|
||||
import { TrackerModel } from '@server/models/server/tracker.js'
|
||||
import { VideoTrackerModel } from '@server/models/server/video-tracker.js'
|
||||
import { UserExportModel } from '@server/models/user/user-export.js'
|
||||
import { UserImportModel } from '@server/models/user/user-import.js'
|
||||
import { UserNotificationModel } from '@server/models/user/user-notification.js'
|
||||
import { UserRegistrationModel } from '@server/models/user/user-registration.js'
|
||||
import { UserVideoHistoryModel } from '@server/models/user/user-video-history.js'
|
||||
import { UserModel } from '@server/models/user/user.js'
|
||||
import { StoryboardModel } from '@server/models/video/storyboard.js'
|
||||
import { VideoChannelSyncModel } from '@server/models/video/video-channel-sync.js'
|
||||
import { VideoChapterModel } from '@server/models/video/video-chapter.js'
|
||||
import { VideoJobInfoModel } from '@server/models/video/video-job-info.js'
|
||||
import { VideoLiveReplaySettingModel } from '@server/models/video/video-live-replay-setting.js'
|
||||
import { VideoLiveSessionModel } from '@server/models/video/video-live-session.js'
|
||||
import { VideoPasswordModel } from '@server/models/video/video-password.js'
|
||||
import { VideoSourceModel } from '@server/models/video/video-source.js'
|
||||
import { LocalVideoViewerWatchSectionModel } from '@server/models/view/local-video-viewer-watch-section.js'
|
||||
import { LocalVideoViewerModel } from '@server/models/view/local-video-viewer.js'
|
||||
import { WatchedWordsListModel } from '@server/models/watched-words/watched-words-list.js'
|
||||
import pg from 'pg'
|
||||
import { QueryTypes, Transaction } from 'sequelize'
|
||||
import { Sequelize as SequelizeTypescript } from 'sequelize-typescript'
|
||||
import { logger } from '../helpers/logger.js'
|
||||
import { AbuseMessageModel } from '../models/abuse/abuse-message.js'
|
||||
import { AbuseModel } from '../models/abuse/abuse.js'
|
||||
import { VideoAbuseModel } from '../models/abuse/video-abuse.js'
|
||||
import { VideoCommentAbuseModel } from '../models/abuse/video-comment-abuse.js'
|
||||
import { AccountBlocklistModel } from '../models/account/account-blocklist.js'
|
||||
import { AccountVideoRateModel } from '../models/account/account-video-rate.js'
|
||||
import { AccountModel } from '../models/account/account.js'
|
||||
import { ActorFollowModel } from '../models/actor/actor-follow.js'
|
||||
import { ActorImageModel } from '../models/actor/actor-image.js'
|
||||
import { ActorModel } from '../models/actor/actor.js'
|
||||
import { ApplicationModel } from '../models/application/application.js'
|
||||
import { OAuthClientModel } from '../models/oauth/oauth-client.js'
|
||||
import { OAuthTokenModel } from '../models/oauth/oauth-token.js'
|
||||
import { VideoRedundancyModel } from '../models/redundancy/video-redundancy.js'
|
||||
import { PluginModel } from '../models/server/plugin.js'
|
||||
import { ServerBlocklistModel } from '../models/server/server-blocklist.js'
|
||||
import { ServerModel } from '../models/server/server.js'
|
||||
import { UserNotificationSettingModel } from '../models/user/user-notification-setting.js'
|
||||
import { ScheduleVideoUpdateModel } from '../models/video/schedule-video-update.js'
|
||||
import { TagModel } from '../models/video/tag.js'
|
||||
import { ThumbnailModel } from '../models/video/thumbnail.js'
|
||||
import { VideoBlacklistModel } from '../models/video/video-blacklist.js'
|
||||
import { VideoCaptionModel } from '../models/video/video-caption.js'
|
||||
import { VideoChangeOwnershipModel } from '../models/video/video-change-ownership.js'
|
||||
import { VideoChannelModel } from '../models/video/video-channel.js'
|
||||
import { VideoCommentModel } from '../models/video/video-comment.js'
|
||||
import { VideoFileModel } from '../models/video/video-file.js'
|
||||
import { VideoImportModel } from '../models/video/video-import.js'
|
||||
import { VideoLiveModel } from '../models/video/video-live.js'
|
||||
import { VideoPlaylistElementModel } from '../models/video/video-playlist-element.js'
|
||||
import { VideoPlaylistModel } from '../models/video/video-playlist.js'
|
||||
import { VideoShareModel } from '../models/video/video-share.js'
|
||||
import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist.js'
|
||||
import { VideoTagModel } from '../models/video/video-tag.js'
|
||||
import { VideoModel } from '../models/video/video.js'
|
||||
import { VideoViewModel } from '../models/view/video-view.js'
|
||||
import { CONFIG } from './config.js'
|
||||
import { AccountAutomaticTagPolicyModel } from '@server/models/automatic-tag/account-automatic-tag-policy.js'
|
||||
|
||||
pg.defaults.parseInt8 = true // Avoid BIGINT to be converted to string
|
||||
|
||||
const dbname = CONFIG.DATABASE.DBNAME
|
||||
const username = CONFIG.DATABASE.USERNAME
|
||||
const password = CONFIG.DATABASE.PASSWORD
|
||||
const host = CONFIG.DATABASE.HOSTNAME
|
||||
const port = CONFIG.DATABASE.PORT
|
||||
const poolMax = CONFIG.DATABASE.POOL.MAX
|
||||
|
||||
let dialectOptions: any = {}
|
||||
|
||||
if (CONFIG.DATABASE.SSL) {
|
||||
dialectOptions = {
|
||||
ssl: {
|
||||
rejectUnauthorized: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const sequelizeTypescript = new SequelizeTypescript({
|
||||
database: dbname,
|
||||
dialect: 'postgres',
|
||||
dialectOptions,
|
||||
host,
|
||||
port,
|
||||
username,
|
||||
password,
|
||||
pool: {
|
||||
max: poolMax
|
||||
},
|
||||
benchmark: isTestOrDevInstance(),
|
||||
isolationLevel: Transaction.ISOLATION_LEVELS.SERIALIZABLE,
|
||||
logging: (message: string, benchmark: number) => {
|
||||
if (process.env.NODE_DB_LOG === 'false') return
|
||||
|
||||
let newMessage = 'Executed SQL request'
|
||||
if (isTestOrDevInstance() === true && benchmark !== undefined) {
|
||||
newMessage += ' in ' + benchmark + 'ms'
|
||||
}
|
||||
|
||||
logger.debug(newMessage, { sql: message, tags: [ 'sql' ] })
|
||||
}
|
||||
})
|
||||
|
||||
function checkDatabaseConnectionOrDie () {
|
||||
sequelizeTypescript.authenticate()
|
||||
.then(() => logger.debug('Connection to PostgreSQL has been established successfully.'))
|
||||
.catch(err => {
|
||||
|
||||
logger.error('Unable to connect to PostgreSQL database.', { err })
|
||||
process.exit(-1)
|
||||
})
|
||||
}
|
||||
|
||||
async function initDatabaseModels (silent: boolean) {
|
||||
sequelizeTypescript.addModels([
|
||||
ApplicationModel,
|
||||
ActorModel,
|
||||
ActorFollowModel,
|
||||
ActorImageModel,
|
||||
AccountModel,
|
||||
OAuthClientModel,
|
||||
OAuthTokenModel,
|
||||
ServerModel,
|
||||
TagModel,
|
||||
AccountVideoRateModel,
|
||||
UserModel,
|
||||
AbuseMessageModel,
|
||||
AbuseModel,
|
||||
VideoCommentAbuseModel,
|
||||
VideoAbuseModel,
|
||||
VideoModel,
|
||||
VideoChangeOwnershipModel,
|
||||
VideoChannelModel,
|
||||
VideoShareModel,
|
||||
VideoFileModel,
|
||||
VideoSourceModel,
|
||||
VideoChapterModel,
|
||||
VideoCaptionModel,
|
||||
VideoBlacklistModel,
|
||||
VideoTagModel,
|
||||
VideoCommentModel,
|
||||
ScheduleVideoUpdateModel,
|
||||
VideoImportModel,
|
||||
VideoViewModel,
|
||||
VideoRedundancyModel,
|
||||
UserVideoHistoryModel,
|
||||
VideoLiveModel,
|
||||
VideoLiveSessionModel,
|
||||
VideoLiveReplaySettingModel,
|
||||
AccountBlocklistModel,
|
||||
ServerBlocklistModel,
|
||||
UserNotificationModel,
|
||||
UserNotificationSettingModel,
|
||||
VideoStreamingPlaylistModel,
|
||||
VideoPlaylistModel,
|
||||
VideoPlaylistElementModel,
|
||||
LocalVideoViewerModel,
|
||||
LocalVideoViewerWatchSectionModel,
|
||||
ThumbnailModel,
|
||||
TrackerModel,
|
||||
VideoTrackerModel,
|
||||
PluginModel,
|
||||
ActorCustomPageModel,
|
||||
UserImportModel,
|
||||
VideoJobInfoModel,
|
||||
VideoChannelSyncModel,
|
||||
UserRegistrationModel,
|
||||
VideoPasswordModel,
|
||||
RunnerRegistrationTokenModel,
|
||||
RunnerModel,
|
||||
RunnerJobModel,
|
||||
StoryboardModel,
|
||||
UserExportModel,
|
||||
VideoAutomaticTagModel,
|
||||
CommentAutomaticTagModel,
|
||||
AutomaticTagModel,
|
||||
WatchedWordsListModel,
|
||||
AccountAutomaticTagPolicyModel
|
||||
])
|
||||
|
||||
// Check extensions exist in the database
|
||||
await checkPostgresExtensions()
|
||||
|
||||
// Create custom PostgreSQL functions
|
||||
await createFunctions()
|
||||
|
||||
if (!silent) logger.info('Database %s is ready.', dbname)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
checkDatabaseConnectionOrDie, initDatabaseModels, sequelizeTypescript
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function checkPostgresExtensions () {
|
||||
const promises = [
|
||||
checkPostgresExtension('pg_trgm'),
|
||||
checkPostgresExtension('unaccent')
|
||||
]
|
||||
|
||||
return Promise.all(promises)
|
||||
}
|
||||
|
||||
async function checkPostgresExtension (extension: string) {
|
||||
const query = `SELECT 1 FROM pg_available_extensions WHERE name = '${extension}' AND installed_version IS NOT NULL;`
|
||||
const options = {
|
||||
type: QueryTypes.SELECT as QueryTypes.SELECT,
|
||||
raw: true
|
||||
}
|
||||
|
||||
const res = await sequelizeTypescript.query<object>(query, options)
|
||||
|
||||
if (!res || res.length === 0) {
|
||||
// Try to create the extension ourselves
|
||||
try {
|
||||
await sequelizeTypescript.query(`CREATE EXTENSION ${extension};`, { raw: true })
|
||||
|
||||
} catch {
|
||||
const errorMessage = `You need to enable ${extension} extension in PostgreSQL. ` +
|
||||
`You can do so by running 'CREATE EXTENSION ${extension};' as a PostgreSQL super user in ${CONFIG.DATABASE.DBNAME} database.`
|
||||
throw new Error(errorMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createFunctions () {
|
||||
const query = `CREATE OR REPLACE FUNCTION immutable_unaccent(text)
|
||||
RETURNS text AS
|
||||
$func$
|
||||
SELECT public.unaccent('public.unaccent', $1::text)
|
||||
$func$ LANGUAGE sql IMMUTABLE;`
|
||||
|
||||
return sequelizeTypescript.query(query, { raw: true })
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
import { ensureDir, remove } from 'fs-extra/esm'
|
||||
import { readdir } from 'fs/promises'
|
||||
import passwordGenerator from 'password-generator'
|
||||
import { join } from 'path'
|
||||
import { UserRole } from '@peertube/peertube-models'
|
||||
import { isTestOrDevInstance } from '@peertube/peertube-node-utils'
|
||||
import { generateRunnerRegistrationToken } from '@server/helpers/token-generator.js'
|
||||
import { getNodeABIVersion } from '@server/helpers/version.js'
|
||||
import { RunnerRegistrationTokenModel } from '@server/models/runner/runner-registration-token.js'
|
||||
import { logger } from '../helpers/logger.js'
|
||||
import { buildUser, createApplicationActor, createUserAccountAndChannelAndPlaylist } from '../lib/user.js'
|
||||
import { ApplicationModel } from '../models/application/application.js'
|
||||
import { OAuthClientModel } from '../models/oauth/oauth-client.js'
|
||||
import { applicationExist, clientsExist, usersExist } from './checker-after-init.js'
|
||||
import { CONFIG } from './config.js'
|
||||
import { DIRECTORIES, FILES_CACHE, LAST_MIGRATION_VERSION } from './constants.js'
|
||||
import { sequelizeTypescript } from './database.js'
|
||||
|
||||
async function installApplication () {
|
||||
try {
|
||||
await Promise.all([
|
||||
// Database related
|
||||
sequelizeTypescript.sync()
|
||||
.then(() => {
|
||||
return Promise.all([
|
||||
createApplicationIfNotExist(),
|
||||
createOAuthClientIfNotExist(),
|
||||
createOAuthAdminIfNotExist(),
|
||||
createRunnerRegistrationTokenIfNotExist()
|
||||
])
|
||||
}),
|
||||
|
||||
// Directories
|
||||
removeCacheAndTmpDirectories()
|
||||
.then(() => createDirectoriesIfNotExist())
|
||||
])
|
||||
} catch (err) {
|
||||
logger.error('Cannot install application.', { err })
|
||||
process.exit(-1)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
installApplication
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function removeCacheAndTmpDirectories () {
|
||||
const cacheDirectories = Object.keys(FILES_CACHE)
|
||||
.map(k => FILES_CACHE[k].DIRECTORY)
|
||||
|
||||
const tasks: Promise<any>[] = []
|
||||
|
||||
// Cache directories
|
||||
for (const dir of cacheDirectories) {
|
||||
tasks.push(removeDirectoryOrContent(dir))
|
||||
}
|
||||
|
||||
tasks.push(removeDirectoryOrContent(CONFIG.STORAGE.TMP_DIR))
|
||||
|
||||
return Promise.all(tasks)
|
||||
}
|
||||
|
||||
async function removeDirectoryOrContent (dir: string) {
|
||||
try {
|
||||
await remove(dir)
|
||||
} catch (err) {
|
||||
logger.debug('Cannot remove directory %s. Removing content instead.', dir, { err })
|
||||
|
||||
const files = await readdir(dir)
|
||||
|
||||
for (const file of files) {
|
||||
await remove(join(dir, file))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createDirectoriesIfNotExist () {
|
||||
const storage = CONFIG.STORAGE
|
||||
const cacheDirectories = Object.keys(FILES_CACHE)
|
||||
.map(k => FILES_CACHE[k].DIRECTORY)
|
||||
|
||||
const tasks: Promise<void>[] = []
|
||||
for (const key of Object.keys(storage)) {
|
||||
const dir = storage[key]
|
||||
tasks.push(ensureDir(dir))
|
||||
}
|
||||
|
||||
// Cache directories
|
||||
for (const dir of cacheDirectories) {
|
||||
tasks.push(ensureDir(dir))
|
||||
}
|
||||
|
||||
tasks.push(ensureDir(DIRECTORIES.HLS_STREAMING_PLAYLIST.PRIVATE))
|
||||
tasks.push(ensureDir(DIRECTORIES.HLS_STREAMING_PLAYLIST.PUBLIC))
|
||||
tasks.push(ensureDir(DIRECTORIES.WEB_VIDEOS.PUBLIC))
|
||||
tasks.push(ensureDir(DIRECTORIES.WEB_VIDEOS.PRIVATE))
|
||||
|
||||
// Resumable upload directory
|
||||
tasks.push(ensureDir(DIRECTORIES.RESUMABLE_UPLOAD))
|
||||
|
||||
return Promise.all(tasks)
|
||||
}
|
||||
|
||||
async function createOAuthClientIfNotExist () {
|
||||
const exist = await clientsExist()
|
||||
// Nothing to do, clients already exist
|
||||
if (exist === true) return undefined
|
||||
|
||||
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 = new OAuthClientModel({
|
||||
clientId: id,
|
||||
clientSecret: secret,
|
||||
grants: [ 'password', 'refresh_token' ],
|
||||
redirectUris: null
|
||||
})
|
||||
|
||||
const createdClient = await client.save()
|
||||
logger.info('Client id: ' + createdClient.clientId)
|
||||
logger.info('Client secret: ' + createdClient.clientSecret)
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
async function createOAuthAdminIfNotExist () {
|
||||
const exist = await usersExist()
|
||||
// Nothing to do, users already exist
|
||||
if (exist === true) return undefined
|
||||
|
||||
logger.info('Creating the administrator.')
|
||||
|
||||
const username = 'root'
|
||||
const role = UserRole.ADMINISTRATOR
|
||||
const email = CONFIG.ADMIN.EMAIL
|
||||
let validatePassword = true
|
||||
let password = ''
|
||||
|
||||
// Do not generate a random password for test and dev environments
|
||||
if (isTestOrDevInstance()) {
|
||||
password = 'test'
|
||||
|
||||
if (process.env.NODE_APP_INSTANCE) {
|
||||
password += process.env.NODE_APP_INSTANCE
|
||||
}
|
||||
|
||||
// Our password is weak so do not validate it
|
||||
validatePassword = false
|
||||
} else if (process.env.PT_INITIAL_ROOT_PASSWORD) {
|
||||
password = process.env.PT_INITIAL_ROOT_PASSWORD
|
||||
} else {
|
||||
password = passwordGenerator(16, true)
|
||||
}
|
||||
|
||||
const user = buildUser({
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
role,
|
||||
emailVerified: true,
|
||||
videoQuota: -1,
|
||||
videoQuotaDaily: -1
|
||||
})
|
||||
|
||||
await createUserAccountAndChannelAndPlaylist({ userToCreate: user, channelNames: undefined, validateUser: validatePassword })
|
||||
logger.info('Username: ' + username)
|
||||
logger.info('User password: ' + password)
|
||||
}
|
||||
|
||||
async function createApplicationIfNotExist () {
|
||||
const exist = await applicationExist()
|
||||
// Nothing to do, application already exist
|
||||
if (exist === true) return undefined
|
||||
|
||||
logger.info('Creating application account.')
|
||||
|
||||
const application = await ApplicationModel.create({
|
||||
migrationVersion: LAST_MIGRATION_VERSION,
|
||||
nodeVersion: process.version,
|
||||
nodeABIVersion: getNodeABIVersion()
|
||||
})
|
||||
|
||||
return createApplicationActor(application.id)
|
||||
}
|
||||
|
||||
async function createRunnerRegistrationTokenIfNotExist () {
|
||||
const total = await RunnerRegistrationTokenModel.countTotal()
|
||||
if (total !== 0) return undefined
|
||||
|
||||
const token = new RunnerRegistrationTokenModel({
|
||||
registrationToken: generateRunnerRegistrationToken()
|
||||
})
|
||||
|
||||
await token.save()
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
|
||||
{
|
||||
const field = {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: true
|
||||
}
|
||||
await utils.queryInterface.addColumn('user', 'lastLoginDate', field)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
|
||||
// We made a mistake with the migration in 2.2.0-rc.1
|
||||
// Docker containers did not include this migration file
|
||||
// So we check the table definition and add the column if it does not exist
|
||||
const tableDefinition = await utils.queryInterface.describeTable('videoFile')
|
||||
|
||||
if (!tableDefinition['metadata']) {
|
||||
const metadata = {
|
||||
type: Sequelize.JSONB,
|
||||
allowNull: true
|
||||
}
|
||||
await utils.queryInterface.addColumn('videoFile', 'metadata', metadata)
|
||||
}
|
||||
|
||||
if (!tableDefinition['metadataUrl']) {
|
||||
const metadataUrl = {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true
|
||||
}
|
||||
await utils.queryInterface.addColumn('videoFile', 'metadataUrl', metadataUrl)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
await utils.queryInterface.addColumn('videoAbuse', 'predefinedReasons', {
|
||||
type: Sequelize.ARRAY(Sequelize.INTEGER),
|
||||
allowNull: true
|
||||
})
|
||||
|
||||
await utils.queryInterface.addColumn('videoAbuse', 'startAt', {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true
|
||||
})
|
||||
|
||||
await utils.queryInterface.addColumn('videoAbuse', 'endAt', {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true
|
||||
})
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
await utils.queryInterface.renameTable('videoAbuse', 'abuse')
|
||||
|
||||
await utils.sequelize.query(`
|
||||
ALTER TABLE "abuse"
|
||||
ADD COLUMN "flaggedAccountId" INTEGER REFERENCES "account" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||
`)
|
||||
|
||||
await utils.sequelize.query(`
|
||||
UPDATE "abuse" SET "videoId" = NULL
|
||||
WHERE "videoId" NOT IN (SELECT "id" FROM "video")
|
||||
`)
|
||||
|
||||
await utils.sequelize.query(`
|
||||
UPDATE "abuse" SET "flaggedAccountId" = "videoChannel"."accountId"
|
||||
FROM "video" INNER JOIN "videoChannel" ON "video"."channelId" = "videoChannel"."id"
|
||||
WHERE "abuse"."videoId" = "video"."id"
|
||||
`)
|
||||
|
||||
await utils.sequelize.query('DROP INDEX IF EXISTS video_abuse_video_id;')
|
||||
await utils.sequelize.query('DROP INDEX IF EXISTS video_abuse_reporter_account_id;')
|
||||
|
||||
await utils.sequelize.query(`
|
||||
CREATE TABLE IF NOT EXISTS "videoAbuse" (
|
||||
"id" serial,
|
||||
"startAt" integer DEFAULT NULL,
|
||||
"endAt" integer DEFAULT NULL,
|
||||
"deletedVideo" jsonb DEFAULT NULL,
|
||||
"abuseId" integer NOT NULL REFERENCES "abuse" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"videoId" integer REFERENCES "video" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
"createdAt" TIMESTAMP WITH time zone NOT NULL,
|
||||
"updatedAt" timestamp WITH time zone NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
`)
|
||||
|
||||
await utils.sequelize.query(`
|
||||
CREATE TABLE IF NOT EXISTS "commentAbuse" (
|
||||
"id" serial,
|
||||
"abuseId" integer NOT NULL REFERENCES "abuse" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"videoCommentId" integer REFERENCES "videoComment" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
"createdAt" timestamp WITH time zone NOT NULL,
|
||||
"updatedAt" timestamp WITH time zone NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
`)
|
||||
|
||||
await utils.sequelize.query(`
|
||||
INSERT INTO "videoAbuse" ("startAt", "endAt", "deletedVideo", "abuseId", "videoId", "createdAt", "updatedAt")
|
||||
SELECT "abuse"."startAt", "abuse"."endAt", "abuse"."deletedVideo", "abuse"."id", "abuse"."videoId",
|
||||
"abuse"."createdAt", "abuse"."updatedAt"
|
||||
FROM "abuse"
|
||||
`)
|
||||
|
||||
await utils.queryInterface.removeColumn('abuse', 'startAt')
|
||||
await utils.queryInterface.removeColumn('abuse', 'endAt')
|
||||
await utils.queryInterface.removeColumn('abuse', 'deletedVideo')
|
||||
await utils.queryInterface.removeColumn('abuse', 'videoId')
|
||||
|
||||
await utils.sequelize.query('DROP INDEX IF EXISTS user_notification_video_abuse_id')
|
||||
await utils.queryInterface.renameColumn('userNotification', 'videoAbuseId', 'abuseId')
|
||||
|
||||
await utils.sequelize.query(
|
||||
'ALTER INDEX IF EXISTS "videoAbuse_pkey" RENAME TO "abuse_pkey"'
|
||||
)
|
||||
|
||||
await utils.queryInterface.renameColumn('userNotificationSetting', 'videoAbuseAsModerator', 'abuseAsModerator')
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
await utils.sequelize.query(`
|
||||
CREATE TABLE IF NOT EXISTS "abuseMessage" (
|
||||
"id" serial,
|
||||
"message" text NOT NULL,
|
||||
"byModerator" boolean NOT NULL,
|
||||
"accountId" integer REFERENCES "account" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
"abuseId" integer NOT NULL REFERENCES "abuse" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"createdAt" timestamp WITH time zone NOT NULL,
|
||||
"updatedAt" timestamp WITH time zone NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
`)
|
||||
|
||||
const notificationSettingColumns = [ 'abuseStateChange', 'abuseNewMessage' ]
|
||||
|
||||
for (const column of notificationSettingColumns) {
|
||||
const data = {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}
|
||||
await utils.queryInterface.addColumn('userNotificationSetting', column, data)
|
||||
}
|
||||
|
||||
{
|
||||
const query = 'UPDATE "userNotificationSetting" SET "abuseStateChange" = 3, "abuseNewMessage" = 3'
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
|
||||
for (const column of notificationSettingColumns) {
|
||||
const data = {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: false
|
||||
}
|
||||
await utils.queryInterface.changeColumn('userNotificationSetting', column, data)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
import { WEBSERVER } from '../constants.js'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
{
|
||||
const field = {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true
|
||||
}
|
||||
await utils.queryInterface.changeColumn('videoPlaylistElement', 'url', field)
|
||||
}
|
||||
|
||||
{
|
||||
await utils.sequelize.query('DROP INDEX IF EXISTS video_playlist_element_video_playlist_id_video_id;')
|
||||
}
|
||||
|
||||
{
|
||||
const selectPlaylistUUID = 'SELECT "uuid" FROM "videoPlaylist" WHERE "id" = "videoPlaylistElement"."videoPlaylistId"'
|
||||
const url = `'${WEBSERVER.URL}' || '/video-playlists/' || (${selectPlaylistUUID}) || '/videos/' || "videoPlaylistElement"."id"`
|
||||
|
||||
const query = `
|
||||
UPDATE "videoPlaylistElement" SET "url" = ${url} WHERE id IN (
|
||||
SELECT "videoPlaylistElement"."id" FROM "videoPlaylistElement"
|
||||
INNER JOIN "videoPlaylist" ON "videoPlaylist".id = "videoPlaylistElement"."videoPlaylistId"
|
||||
INNER JOIN account ON account.id = "videoPlaylist"."ownerAccountId"
|
||||
INNER JOIN actor ON actor.id = account."actorId"
|
||||
WHERE actor."serverId" IS NULL
|
||||
)`
|
||||
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
{
|
||||
const query = `
|
||||
CREATE TABLE IF NOT EXISTS "videoLive" (
|
||||
"id" SERIAL ,
|
||||
"streamKey" VARCHAR(255),
|
||||
"videoId" INTEGER NOT NULL REFERENCES "video" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
"updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
`
|
||||
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('video', 'isLive', {
|
||||
type: Sequelize.BOOLEAN,
|
||||
defaultValue: false,
|
||||
allowNull: false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.STRING,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}
|
||||
|
||||
await utils.queryInterface.changeColumn('videoFile', 'infoHash', data)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.BOOLEAN,
|
||||
defaultValue: false,
|
||||
allowNull: false
|
||||
}
|
||||
|
||||
await utils.queryInterface.addColumn('videoLive', 'saveReplay', data)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
const query = `
|
||||
WITH t AS (
|
||||
SELECT actor.id FROM actor
|
||||
LEFT JOIN "videoChannel" ON "videoChannel"."actorId" = actor.id
|
||||
LEFT JOIN account ON account."actorId" = "actor"."id"
|
||||
WHERE "videoChannel".id IS NULL and "account".id IS NULL
|
||||
) DELETE FROM "actorFollow" WHERE "actorId" IN (SELECT t.id FROM t) OR "targetActorId" in (SELECT t.id FROM t)
|
||||
`
|
||||
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.STRING(2000),
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}
|
||||
|
||||
await utils.queryInterface.addColumn('actorFollow', 'url', data)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
import { buildUUID } from '@peertube/peertube-node-utils'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
const q = utils.queryInterface
|
||||
|
||||
{
|
||||
// Create uuid column for users
|
||||
const userFeedTokenUUID = {
|
||||
type: Sequelize.UUID,
|
||||
defaultValue: Sequelize.UUIDV4,
|
||||
allowNull: true
|
||||
}
|
||||
await q.addColumn('user', 'feedToken', userFeedTokenUUID)
|
||||
}
|
||||
|
||||
// Set UUID to previous users
|
||||
{
|
||||
const query = 'SELECT * FROM "user" WHERE "feedToken" IS NULL'
|
||||
const options = { type: Sequelize.QueryTypes.SELECT as Sequelize.QueryTypes.SELECT }
|
||||
const users = await utils.sequelize.query<any>(query, options)
|
||||
|
||||
for (const user of users) {
|
||||
const queryUpdate = `UPDATE "user" SET "feedToken" = '${buildUUID()}' WHERE id = ${user.id}`
|
||||
await utils.sequelize.query(queryUpdate)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const userFeedTokenUUID = {
|
||||
type: Sequelize.UUID,
|
||||
defaultValue: Sequelize.UUIDV4,
|
||||
allowNull: false
|
||||
}
|
||||
await q.changeColumn('user', 'feedToken', userFeedTokenUUID)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
{
|
||||
const query = `
|
||||
UPDATE "actorFollow" SET url = follower.url || '/follows/' || following.id
|
||||
FROM actor follower, actor following
|
||||
WHERE follower."serverId" IS NULL AND follower.id = "actorFollow"."actorId" AND following.id = "actorFollow"."targetActorId"
|
||||
`
|
||||
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.BOOLEAN,
|
||||
defaultValue: false,
|
||||
allowNull: false
|
||||
}
|
||||
|
||||
await utils.queryInterface.addColumn('videoLive', 'permanentLive', data)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
{
|
||||
const query = 'DELETE FROM "thumbnail" s1 ' +
|
||||
'USING (SELECT MIN(id) as id, "filename", "type" FROM "thumbnail" GROUP BY "filename", "type" HAVING COUNT(*) > 1) s2 ' +
|
||||
'WHERE s1."filename" = s2."filename" AND s1."type" = s2."type" AND s1.id <> s2.id'
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true,
|
||||
defaultValue: null
|
||||
}
|
||||
|
||||
await utils.queryInterface.addColumn('videoCaption', 'filename', data)
|
||||
}
|
||||
|
||||
{
|
||||
const query = `UPDATE "videoCaption" SET "filename" = s.uuid || '-' || s.language || '.vtt' ` +
|
||||
`FROM (` +
|
||||
` SELECT "videoCaption"."id", video.uuid, "videoCaption".language ` +
|
||||
` FROM "videoCaption" INNER JOIN video ON video.id = "videoCaption"."videoId"` +
|
||||
`) AS s ` +
|
||||
`WHERE "videoCaption".id = s.id`
|
||||
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: false,
|
||||
defaultValue: null
|
||||
}
|
||||
|
||||
await utils.queryInterface.changeColumn('videoCaption', 'filename', data)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
for (const column of [ 'filename', 'fileUrl', 'torrentFilename', 'torrentUrl' ]) {
|
||||
const data = {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true,
|
||||
defaultValue: null
|
||||
}
|
||||
|
||||
await utils.queryInterface.addColumn('videoFile', column, data)
|
||||
}
|
||||
|
||||
// Generate filenames for webtorrent files
|
||||
{
|
||||
const webtorrentQuery = `SELECT "videoFile".id, "video".uuid, "videoFile".resolution, "videoFile".extname ` +
|
||||
`FROM video INNER JOIN "videoFile" ON "videoFile"."videoId" = video.id`
|
||||
|
||||
const query = `UPDATE "videoFile" ` +
|
||||
`SET filename = t.uuid || '-' || t.resolution || t.extname, ` +
|
||||
`"torrentFilename" = t.uuid || '-' || t.resolution || '.torrent' ` +
|
||||
`FROM (${webtorrentQuery}) AS t WHERE t.id = "videoFile"."id"`
|
||||
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
|
||||
// Generate filenames for HLS files
|
||||
{
|
||||
const hlsQuery = `SELECT "videoFile".id, "video".uuid, "videoFile".resolution, "videoFile".extname ` +
|
||||
`FROM video ` +
|
||||
`INNER JOIN "videoStreamingPlaylist" ON "videoStreamingPlaylist"."videoId" = video.id ` +
|
||||
`INNER JOIN "videoFile" ON "videoFile"."videoStreamingPlaylistId" = "videoStreamingPlaylist".id`
|
||||
|
||||
const query = `UPDATE "videoFile" ` +
|
||||
`SET filename = t.uuid || '-' || t.resolution || '-fragmented' || t.extname, ` +
|
||||
`"torrentFilename" = t.uuid || '-' || t.resolution || '-hls.torrent' ` +
|
||||
`FROM (${hlsQuery}) AS t WHERE t.id = "videoFile"."id"`
|
||||
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
{
|
||||
const query = `CREATE TABLE IF NOT EXISTS "tracker" (
|
||||
"id" serial,
|
||||
"url" varchar(255) NOT NULL,
|
||||
"createdAt" timestamp WITH time zone NOT NULL,
|
||||
"updatedAt" timestamp WITH time zone NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
);`
|
||||
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
|
||||
{
|
||||
const query = `CREATE TABLE IF NOT EXISTS "videoTracker" (
|
||||
"videoId" integer REFERENCES "video" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"trackerId" integer REFERENCES "tracker" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"createdAt" timestamp WITH time zone NOT NULL,
|
||||
"updatedAt" timestamp WITH time zone NOT NULL,
|
||||
UNIQUE ("videoId", "trackerId"),
|
||||
PRIMARY KEY ("videoId", "trackerId")
|
||||
);`
|
||||
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
|
||||
await utils.sequelize.query(`CREATE UNIQUE INDEX "tracker_url" ON "tracker" ("url")`)
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
|
||||
// Torrent and file URLs
|
||||
{
|
||||
const fromQueryWebtorrent = `SELECT 'https://' || server.host AS "serverUrl", '/static/webseed/' AS "filePath", "videoFile".id ` +
|
||||
`FROM video ` +
|
||||
`INNER JOIN "videoChannel" ON "videoChannel".id = video."channelId" ` +
|
||||
`INNER JOIN actor ON actor.id = "videoChannel"."actorId" ` +
|
||||
`INNER JOIN server ON server.id = actor."serverId" ` +
|
||||
`INNER JOIN "videoFile" ON "videoFile"."videoId" = video.id ` +
|
||||
`WHERE video.remote IS TRUE`
|
||||
|
||||
const fromQueryHLS = `SELECT 'https://' || server.host AS "serverUrl", ` +
|
||||
`'/static/streaming-playlists/hls/' || video.uuid || '/' AS "filePath", "videoFile".id ` +
|
||||
`FROM video ` +
|
||||
`INNER JOIN "videoChannel" ON "videoChannel".id = video."channelId" ` +
|
||||
`INNER JOIN actor ON actor.id = "videoChannel"."actorId" ` +
|
||||
`INNER JOIN server ON server.id = actor."serverId" ` +
|
||||
`INNER JOIN "videoStreamingPlaylist" ON "videoStreamingPlaylist"."videoId" = video.id ` +
|
||||
`INNER JOIN "videoFile" ON "videoFile"."videoStreamingPlaylistId" = "videoStreamingPlaylist".id ` +
|
||||
`WHERE video.remote IS TRUE`
|
||||
|
||||
for (const fromQuery of [ fromQueryWebtorrent, fromQueryHLS ]) {
|
||||
const query = `UPDATE "videoFile" ` +
|
||||
`SET "torrentUrl" = t."serverUrl" || '/static/torrents/' || "videoFile"."torrentFilename", ` +
|
||||
`"fileUrl" = t."serverUrl" || t."filePath" || "videoFile"."filename" ` +
|
||||
`FROM (${fromQuery}) AS t WHERE t.id = "videoFile"."id" AND "videoFile"."fileUrl" IS NULL`
|
||||
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
}
|
||||
|
||||
// Caption URLs
|
||||
{
|
||||
const fromQuery = `SELECT 'https://' || server.host AS "serverUrl", "video".uuid, "videoCaption".id ` +
|
||||
`FROM video ` +
|
||||
`INNER JOIN "videoChannel" ON "videoChannel".id = video."channelId" ` +
|
||||
`INNER JOIN actor ON actor.id = "videoChannel"."actorId" ` +
|
||||
`INNER JOIN server ON server.id = actor."serverId" ` +
|
||||
`INNER JOIN "videoCaption" ON "videoCaption"."videoId" = video.id ` +
|
||||
`WHERE video.remote IS TRUE`
|
||||
|
||||
const query = `UPDATE "videoCaption" ` +
|
||||
`SET "fileUrl" = t."serverUrl" || '/lazy-static/video-captions/' || t.uuid || '-' || "videoCaption"."language" || '.vtt' ` +
|
||||
`FROM (${fromQuery}) AS t WHERE t.id = "videoCaption"."id" AND "videoCaption"."fileUrl" IS NULL`
|
||||
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
|
||||
// Thumbnail URLs
|
||||
{
|
||||
const fromQuery = `SELECT 'https://' || server.host AS "serverUrl", "video".uuid, "thumbnail".id ` +
|
||||
`FROM video ` +
|
||||
`INNER JOIN "videoChannel" ON "videoChannel".id = video."channelId" ` +
|
||||
`INNER JOIN actor ON actor.id = "videoChannel"."actorId" ` +
|
||||
`INNER JOIN server ON server.id = actor."serverId" ` +
|
||||
`INNER JOIN "thumbnail" ON "thumbnail"."videoId" = video.id ` +
|
||||
`WHERE video.remote IS TRUE`
|
||||
|
||||
// Thumbnails
|
||||
{
|
||||
const query = `UPDATE "thumbnail" ` +
|
||||
`SET "fileUrl" = t."serverUrl" || '/static/thumbnails/' || t.uuid || '.jpg' ` +
|
||||
`FROM (${fromQuery}) AS t WHERE t.id = "thumbnail"."id" AND "thumbnail"."fileUrl" IS NULL AND thumbnail.type = 1`
|
||||
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
|
||||
{
|
||||
// Previews
|
||||
const query = `UPDATE "thumbnail" ` +
|
||||
`SET "fileUrl" = t."serverUrl" || '/lazy-static/previews/' || t.uuid || '.jpg' ` +
|
||||
`FROM (${fromQuery}) AS t WHERE t.id = "thumbnail"."id" AND "thumbnail"."fileUrl" IS NULL AND thumbnail.type = 2`
|
||||
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
}
|
||||
|
||||
// Trackers
|
||||
{
|
||||
const trackerUrls = [
|
||||
`'https://' || server.host || '/tracker/announce'`,
|
||||
`'wss://' || server.host || '/tracker/socket'`
|
||||
]
|
||||
|
||||
for (const trackerUrl of trackerUrls) {
|
||||
{
|
||||
const query = `INSERT INTO "tracker" ("url", "createdAt", "updatedAt") ` +
|
||||
`SELECT ${trackerUrl} AS "url", NOW(), NOW() ` +
|
||||
`FROM video ` +
|
||||
`INNER JOIN "videoChannel" ON "videoChannel".id = video."channelId" ` +
|
||||
`INNER JOIN actor ON actor.id = "videoChannel"."actorId" ` +
|
||||
`INNER JOIN server ON server.id = actor."serverId" ` +
|
||||
`WHERE video.remote IS TRUE ` +
|
||||
`ON CONFLICT DO NOTHING`
|
||||
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
|
||||
{
|
||||
const query = `INSERT INTO "videoTracker" ("videoId", "trackerId", "createdAt", "updatedAt") ` +
|
||||
`SELECT video.id, (SELECT tracker.id FROM tracker WHERE url = ${trackerUrl}) AS "trackerId", NOW(), NOW()` +
|
||||
`FROM video ` +
|
||||
`INNER JOIN "videoChannel" ON "videoChannel".id = video."channelId" ` +
|
||||
`INNER JOIN actor ON actor.id = "videoChannel"."actorId" ` +
|
||||
`INNER JOIN server ON server.id = actor."serverId" ` +
|
||||
`WHERE video.remote IS TRUE`
|
||||
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
|
||||
{
|
||||
const query = 'DELETE FROM "videoFile" f1 ' +
|
||||
'USING (SELECT MIN(id) as id, "torrentFilename" FROM "videoFile" GROUP BY "torrentFilename" HAVING COUNT(*) > 1) f2 ' +
|
||||
'WHERE f1."torrentFilename" = f2."torrentFilename" AND f1.id <> f2.id'
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
|
||||
{
|
||||
const query = 'DELETE FROM "videoFile" f1 ' +
|
||||
'USING (SELECT MIN(id) as id, "filename" FROM "videoFile" GROUP BY "filename" HAVING COUNT(*) > 1) f2 ' +
|
||||
'WHERE f1."filename" = f2."filename" AND f1.id <> f2.id'
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
import { generateRSAKeyPairPromise } from '../../helpers/core-utils.js'
|
||||
import { PRIVATE_RSA_KEY_SIZE } from '../constants.js'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
|
||||
{
|
||||
const query = 'SELECT * FROM "actor" WHERE "serverId" IS NULL AND "publicKey" IS NULL'
|
||||
const options = { type: Sequelize.QueryTypes.SELECT as Sequelize.QueryTypes.SELECT }
|
||||
const actors = await utils.sequelize.query<any>(query, options)
|
||||
|
||||
for (const actor of actors) {
|
||||
const { privateKey, publicKey } = await generateRSAKeyPairPromise(PRIVATE_RSA_KEY_SIZE)
|
||||
|
||||
const queryUpdate = `UPDATE "actor" SET "publicKey" = '${publicKey}', "privateKey" = '${privateKey}' WHERE id = ${actor.id}`
|
||||
await utils.sequelize.query(queryUpdate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
|
||||
await utils.sequelize.query('DROP INDEX IF EXISTS video_views;')
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
|
||||
await utils.sequelize.query(
|
||||
'DELETE FROM "videoCaption" v1 USING (SELECT MIN(id) as id, "filename" FROM "videoCaption" ' +
|
||||
'GROUP BY "filename" HAVING COUNT(*) > 1) v2 WHERE v1."filename" = v2."filename" AND v1.id <> v2.id'
|
||||
)
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
{
|
||||
const notificationSettingColumns = [ 'newPeerTubeVersion', 'newPluginVersion' ]
|
||||
|
||||
for (const column of notificationSettingColumns) {
|
||||
const data = {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}
|
||||
await utils.queryInterface.addColumn('userNotificationSetting', column, data)
|
||||
}
|
||||
|
||||
{
|
||||
const query = 'UPDATE "userNotificationSetting" SET "newPeerTubeVersion" = 3, "newPluginVersion" = 1'
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
|
||||
for (const column of notificationSettingColumns) {
|
||||
const data = {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: false
|
||||
}
|
||||
await utils.queryInterface.changeColumn('userNotificationSetting', column, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.STRING,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}
|
||||
await utils.queryInterface.addColumn('application', 'latestPeerTubeVersion', data)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
|
||||
{
|
||||
await utils.sequelize.query(`
|
||||
ALTER TABLE "userNotification"
|
||||
ADD COLUMN "applicationId" INTEGER REFERENCES "application" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
ADD COLUMN "pluginId" INTEGER REFERENCES "plugin" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||
`)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
|
||||
{
|
||||
await utils.sequelize.query(`ALTER TABLE "avatar" RENAME to "actorImage"`)
|
||||
}
|
||||
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}
|
||||
await utils.queryInterface.addColumn('actorImage', 'type', data)
|
||||
}
|
||||
|
||||
{
|
||||
await utils.sequelize.query(`UPDATE "actorImage" SET "type" = 1`)
|
||||
}
|
||||
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: false
|
||||
}
|
||||
await utils.queryInterface.changeColumn('actorImage', 'type', data)
|
||||
}
|
||||
|
||||
{
|
||||
await utils.sequelize.query(
|
||||
`ALTER TABLE "actor" ADD COLUMN "bannerId" INTEGER REFERENCES "actorImage" ("id") ON DELETE SET NULL ON UPDATE CASCADE`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}
|
||||
await utils.queryInterface.addColumn('actorImage', 'height', data)
|
||||
}
|
||||
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}
|
||||
await utils.queryInterface.addColumn('actorImage', 'width', data)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
{
|
||||
await utils.sequelize.query(
|
||||
'DELETE FROM "actor" v1 USING (SELECT MIN(id) as id, "preferredUsername", "serverId" FROM "actor" ' +
|
||||
'GROUP BY "preferredUsername", "serverId" HAVING COUNT(*) > 1 AND "serverId" IS NOT NULL) v2 ' +
|
||||
'WHERE v1."preferredUsername" = v2."preferredUsername" AND v1."serverId" = v2."serverId" AND v1.id <> v2.id'
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
await utils.sequelize.query(
|
||||
'DELETE FROM "actor" v1 USING (SELECT MIN(id) as id, "url" FROM "actor" GROUP BY "url" HAVING COUNT(*) > 1) v2 ' +
|
||||
'WHERE v1."url" = v2."url" AND v1.id <> v2.id'
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
await utils.sequelize.query(
|
||||
'DELETE FROM "tag" v1 USING (SELECT MIN(id) as id, "name" FROM "tag" GROUP BY "name" HAVING COUNT(*) > 1) v2 ' +
|
||||
'WHERE v1."name" = v2."name" AND v1.id <> v2.id'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.DATE,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}
|
||||
await utils.queryInterface.addColumn('actor', 'remoteCreatedAt', data)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
{
|
||||
const query = `
|
||||
CREATE TABLE IF NOT EXISTS "actorCustomPage" (
|
||||
"id" serial,
|
||||
"content" TEXT,
|
||||
"type" varchar(255) NOT NULL,
|
||||
"actorId" integer NOT NULL REFERENCES "actor" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"createdAt" timestamp WITH time zone NOT NULL,
|
||||
"updatedAt" timestamp WITH time zone NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
`
|
||||
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
{
|
||||
for (const column of [ 'playlistUrl', 'segmentsSha256Url' ]) {
|
||||
const data = {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true,
|
||||
defaultValue: null
|
||||
}
|
||||
|
||||
await utils.queryInterface.changeColumn('videoStreamingPlaylist', column, data)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
await utils.sequelize.query(
|
||||
`UPDATE "videoStreamingPlaylist" SET "playlistUrl" = NULL, "segmentsSha256Url" = NULL ` +
|
||||
`WHERE "videoId" IN (SELECT id FROM video WHERE remote IS FALSE)`
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
for (const column of [ 'playlistFilename', 'segmentsSha256Filename' ]) {
|
||||
const data = {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true,
|
||||
defaultValue: null
|
||||
}
|
||||
|
||||
await utils.queryInterface.addColumn('videoStreamingPlaylist', column, data)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
await utils.sequelize.query(
|
||||
`UPDATE "videoStreamingPlaylist" SET "playlistFilename" = 'master.m3u8', "segmentsSha256Filename" = 'segments-sha256.json'`
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
for (const column of [ 'playlistFilename', 'segmentsSha256Filename' ]) {
|
||||
const data = {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: false,
|
||||
defaultValue: null
|
||||
}
|
||||
|
||||
await utils.queryInterface.changeColumn('videoStreamingPlaylist', column, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
import { FileStorage } from '@peertube/peertube-models'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
{
|
||||
const query = `
|
||||
CREATE TABLE IF NOT EXISTS "videoJobInfo" (
|
||||
"id" serial,
|
||||
"pendingMove" INTEGER NOT NULL,
|
||||
"pendingTranscode" INTEGER NOT NULL,
|
||||
"videoId" serial UNIQUE NOT NULL REFERENCES "video" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"createdAt" timestamp WITH time zone NOT NULL,
|
||||
"updatedAt" timestamp WITH time zone NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
`
|
||||
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('videoFile', 'storage', {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
defaultValue: FileStorage.FILE_SYSTEM
|
||||
})
|
||||
await utils.queryInterface.changeColumn('videoFile', 'storage', { type: Sequelize.INTEGER, allowNull: false, defaultValue: null })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('videoStreamingPlaylist', 'storage', {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
defaultValue: FileStorage.FILE_SYSTEM
|
||||
})
|
||||
await utils.queryInterface.changeColumn('videoStreamingPlaylist', 'storage', {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: null
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false
|
||||
}
|
||||
|
||||
await utils.queryInterface.addColumn('user', 'noAccountSetupWarningModal', data)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
for (const column of [ 'pendingMove', 'pendingTranscode' ]) {
|
||||
const data = {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
}
|
||||
|
||||
await utils.queryInterface.changeColumn('videoJobInfo', column, data)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
await utils.queryInterface.renameColumn('user', 'webTorrentEnabled', 'p2pEnabled')
|
||||
|
||||
await utils.sequelize.query('ALTER TABLE "user" ALTER COLUMN "p2pEnabled" DROP DEFAULT')
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
await utils.sequelize.query('ALTER TABLE "videoFile" ALTER COLUMN "storage" SET DEFAULT 0')
|
||||
await utils.sequelize.query('ALTER TABLE "videoStreamingPlaylist" ALTER COLUMN "storage" SET DEFAULT 0')
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
{
|
||||
await utils.queryInterface.addColumn('actorImage', 'actorId', {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'actor',
|
||||
key: 'id'
|
||||
},
|
||||
onDelete: 'CASCADE'
|
||||
}, { transaction: utils.transaction })
|
||||
|
||||
// Avatars
|
||||
{
|
||||
const query = `UPDATE "actorImage" SET "actorId" = (SELECT "id" FROM "actor" WHERE "actor"."avatarId" = "actorImage"."id") ` +
|
||||
`WHERE "type" = 1`
|
||||
await utils.sequelize.query(query, { type: Sequelize.QueryTypes.UPDATE, transaction: utils.transaction })
|
||||
}
|
||||
|
||||
// Banners
|
||||
{
|
||||
const query = `UPDATE "actorImage" SET "actorId" = (SELECT "id" FROM "actor" WHERE "actor"."bannerId" = "actorImage"."id") ` +
|
||||
`WHERE "type" = 2`
|
||||
await utils.sequelize.query(query, { type: Sequelize.QueryTypes.UPDATE, transaction: utils.transaction })
|
||||
}
|
||||
|
||||
// Remove orphans
|
||||
{
|
||||
const query = `DELETE FROM "actorImage" WHERE id NOT IN (` +
|
||||
`SELECT "bannerId" FROM actor WHERE "bannerId" IS NOT NULL ` +
|
||||
`UNION select "avatarId" FROM actor WHERE "avatarId" IS NOT NULL` +
|
||||
`);`
|
||||
|
||||
await utils.sequelize.query(query, { type: Sequelize.QueryTypes.DELETE, transaction: utils.transaction })
|
||||
}
|
||||
|
||||
await utils.queryInterface.changeColumn('actorImage', 'actorId', {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false
|
||||
}, { transaction: utils.transaction })
|
||||
|
||||
await utils.queryInterface.removeColumn('actor', 'avatarId', { transaction: utils.transaction })
|
||||
await utils.queryInterface.removeColumn('actor', 'bannerId', { transaction: utils.transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down () {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { LiveVideoLatencyMode } from '@peertube/peertube-models'
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
await utils.queryInterface.addColumn('videoLive', 'latencyMode', {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}, { transaction: utils.transaction })
|
||||
|
||||
{
|
||||
const query = `UPDATE "videoLive" SET "latencyMode" = ${LiveVideoLatencyMode.DEFAULT}`
|
||||
await utils.sequelize.query(query, { type: Sequelize.QueryTypes.UPDATE, transaction: utils.transaction })
|
||||
}
|
||||
|
||||
await utils.queryInterface.changeColumn('videoLive', 'latencyMode', {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: false
|
||||
}, { transaction: utils.transaction })
|
||||
}
|
||||
|
||||
function down () {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
const query = 'DELETE FROM "accountVideoRate" ' +
|
||||
'WHERE "accountVideoRate".id IN (' +
|
||||
'SELECT "accountVideoRate".id FROM "accountVideoRate" ' +
|
||||
'INNER JOIN account ON account.id = "accountVideoRate"."accountId" ' +
|
||||
'INNER JOIN actor ON actor.id = account."actorId" ' +
|
||||
'INNER JOIN video ON video.id = "accountVideoRate"."videoId" ' +
|
||||
'WHERE actor."serverId" IS NOT NULL AND video.remote IS TRUE' +
|
||||
')'
|
||||
|
||||
await utils.sequelize.query(query, { type: Sequelize.QueryTypes.BULKDELETE, transaction: utils.transaction })
|
||||
}
|
||||
|
||||
function down () {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
const { transaction } = utils
|
||||
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}
|
||||
await utils.queryInterface.addColumn('userNotificationSetting', 'myVideoStudioEditionFinished', data, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const query = 'UPDATE "userNotificationSetting" SET "myVideoStudioEditionFinished" = 1'
|
||||
await utils.sequelize.query(query, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: false
|
||||
}
|
||||
await utils.queryInterface.changeColumn('userNotificationSetting', 'myVideoStudioEditionFinished', data, { transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down () {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
const { transaction } = utils
|
||||
|
||||
{
|
||||
const query = `
|
||||
CREATE TABLE IF NOT EXISTS "localVideoViewer" (
|
||||
"id" serial,
|
||||
"startDate" timestamp with time zone NOT NULL,
|
||||
"endDate" timestamp with time zone NOT NULL,
|
||||
"watchTime" integer NOT NULL,
|
||||
"country" varchar(255),
|
||||
"uuid" uuid NOT NULL,
|
||||
"url" varchar(255) NOT NULL,
|
||||
"videoId" integer NOT NULL REFERENCES "video" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"createdAt" timestamp with time zone NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
`
|
||||
await utils.sequelize.query(query, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const query = `
|
||||
CREATE TABLE IF NOT EXISTS "localVideoViewerWatchSection" (
|
||||
"id" serial,
|
||||
"watchStart" integer NOT NULL,
|
||||
"watchEnd" integer NOT NULL,
|
||||
"localVideoViewerId" integer NOT NULL REFERENCES "localVideoViewer" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"createdAt" timestamp with time zone NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
`
|
||||
await utils.sequelize.query(query, { transaction })
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function down () {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
const { transaction } = utils
|
||||
|
||||
const query = `
|
||||
CREATE TABLE IF NOT EXISTS "videoLiveSession" (
|
||||
"id" serial,
|
||||
"startDate" timestamp with time zone NOT NULL,
|
||||
"endDate" timestamp with time zone,
|
||||
"error" integer,
|
||||
"replayVideoId" integer REFERENCES "video" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
"liveVideoId" integer REFERENCES "video" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
"createdAt" timestamp with time zone NOT NULL,
|
||||
"updatedAt" timestamp with time zone NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
`
|
||||
await utils.sequelize.query(query, { transaction })
|
||||
}
|
||||
|
||||
function down () {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
{
|
||||
const query = `
|
||||
CREATE TABLE IF NOT EXISTS "videoSource" (
|
||||
"id" SERIAL ,
|
||||
"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
"updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
"filename" VARCHAR(255) DEFAULT NULL,
|
||||
"videoId" INTEGER
|
||||
REFERENCES "video" ("id")
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
`
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
const { transaction } = utils
|
||||
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.BOOLEAN,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}
|
||||
await utils.queryInterface.addColumn('videoLiveSession', 'endingProcessed', data, { transaction })
|
||||
await utils.queryInterface.addColumn('videoLiveSession', 'saveReplay', data, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const query = `UPDATE "videoLiveSession" SET "saveReplay" = (
|
||||
SELECT "videoLive"."saveReplay" FROM "videoLive" WHERE "videoLive"."videoId" = "videoLiveSession"."liveVideoId"
|
||||
) WHERE "videoLiveSession"."liveVideoId" IS NOT NULL`
|
||||
await utils.sequelize.query(query, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const query = `UPDATE "videoLiveSession" SET "saveReplay" = FALSE WHERE "saveReplay" IS NULL`
|
||||
await utils.sequelize.query(query, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const query = `UPDATE "videoLiveSession" SET "endingProcessed" = TRUE`
|
||||
await utils.sequelize.query(query, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.BOOLEAN,
|
||||
defaultValue: null,
|
||||
allowNull: false
|
||||
}
|
||||
await utils.queryInterface.changeColumn('videoLiveSession', 'endingProcessed', data, { transaction })
|
||||
await utils.queryInterface.changeColumn('videoLiveSession', 'saveReplay', data, { transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
const { transaction } = utils
|
||||
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.STRING,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}
|
||||
await utils.queryInterface.addColumn('application', 'nodeVersion', data, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.STRING,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}
|
||||
await utils.queryInterface.addColumn('application', 'nodeABIVersion', data, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const query = `UPDATE "application" SET "nodeVersion" = '${process.version}'`
|
||||
await utils.sequelize.query(query, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const nodeABIVersion = parseInt(process.versions.modules)
|
||||
const query = `UPDATE "application" SET "nodeABIVersion" = ${nodeABIVersion}`
|
||||
await utils.sequelize.query(query, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.STRING,
|
||||
defaultValue: null,
|
||||
allowNull: false
|
||||
}
|
||||
await utils.queryInterface.changeColumn('application', 'nodeVersion', data, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.STRING,
|
||||
defaultValue: null,
|
||||
allowNull: false
|
||||
}
|
||||
await utils.queryInterface.changeColumn('application', 'nodeABIVersion', data, { transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
const query = `
|
||||
CREATE TABLE IF NOT EXISTS "videoChannelSync" (
|
||||
"id" SERIAL,
|
||||
"externalChannelUrl" VARCHAR(2000) NOT NULL DEFAULT NULL,
|
||||
"videoChannelId" INTEGER NOT NULL REFERENCES "videoChannel" ("id")
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE,
|
||||
"state" INTEGER NOT NULL DEFAULT 1,
|
||||
"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
"updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
"lastSyncAt" TIMESTAMP WITH TIME ZONE,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
`
|
||||
await utils.sequelize.query(query, { transaction: utils.transaction })
|
||||
}
|
||||
|
||||
async function down (utils: {
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
transaction: Sequelize.Transaction
|
||||
}) {
|
||||
await utils.queryInterface.dropTable('videoChannelSync', { transaction: utils.transaction })
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
await utils.queryInterface.addColumn('videoImport', 'videoChannelSyncId', {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'videoChannelSync',
|
||||
key: 'id'
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'SET NULL'
|
||||
}, { transaction: utils.transaction })
|
||||
}
|
||||
|
||||
async function down (utils: {
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
transaction: Sequelize.Transaction
|
||||
}) {
|
||||
await utils.queryInterface.dropTable('videoChannelSync', { transaction: utils.transaction })
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
try {
|
||||
await utils.sequelize.query('drop type "enum_actorFollow_state"')
|
||||
await utils.sequelize.query('alter type "enum_AccountFollows_state" rename to "enum_actorFollow_state";')
|
||||
} catch {
|
||||
// empty
|
||||
}
|
||||
|
||||
try {
|
||||
await utils.sequelize.query('drop type "enum_accountVideoRate_type"')
|
||||
await utils.sequelize.query('alter type "enum_AccountVideoRates_type" rename to "enum_accountVideoRate_type";')
|
||||
} catch {
|
||||
// empty
|
||||
}
|
||||
}
|
||||
|
||||
async function down (utils: {
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
transaction: Sequelize.Transaction
|
||||
}) {
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
const { transaction } = utils
|
||||
|
||||
const data = {
|
||||
type: Sequelize.STRING,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}
|
||||
await utils.queryInterface.addColumn('user', 'otpSecret', data, { transaction })
|
||||
|
||||
}
|
||||
|
||||
async function down (utils: {
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
transaction: Sequelize.Transaction
|
||||
}) {
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
{
|
||||
const query = `
|
||||
CREATE TABLE IF NOT EXISTS "userRegistration" (
|
||||
"id" serial,
|
||||
"state" integer NOT NULL,
|
||||
"registrationReason" text NOT NULL,
|
||||
"moderationResponse" text,
|
||||
"password" varchar(255),
|
||||
"username" varchar(255) NOT NULL,
|
||||
"email" varchar(400) NOT NULL,
|
||||
"emailVerified" boolean,
|
||||
"accountDisplayName" varchar(255),
|
||||
"channelHandle" varchar(255),
|
||||
"channelDisplayName" varchar(255),
|
||||
"userId" integer REFERENCES "user" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
"createdAt" timestamp with time zone NOT NULL,
|
||||
"updatedAt" timestamp with time zone NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
`
|
||||
await utils.sequelize.query(query, { transaction: utils.transaction })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('userNotification', 'userRegistrationId', {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'userRegistration',
|
||||
key: 'id'
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'SET NULL'
|
||||
}, { transaction: utils.transaction })
|
||||
}
|
||||
}
|
||||
|
||||
async function down (utils: {
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
transaction: Sequelize.Transaction
|
||||
}) {
|
||||
await utils.queryInterface.dropTable('videoChannelSync', { transaction: utils.transaction })
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
const { transaction } = utils
|
||||
|
||||
const query = 'DELETE FROM "localVideoViewer" t1 ' +
|
||||
'USING (SELECT MIN(id) as id, "url" FROM "localVideoViewer" GROUP BY "url" HAVING COUNT(*) > 1) t2 ' +
|
||||
'WHERE t1."url" = t2."url" AND t1.id <> t2.id'
|
||||
|
||||
await utils.sequelize.query(query, { transaction })
|
||||
}
|
||||
|
||||
async function down (utils: {
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
transaction: Sequelize.Transaction
|
||||
}) {
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
{
|
||||
const query = `
|
||||
CREATE TABLE IF NOT EXISTS "videoLiveReplaySetting" (
|
||||
"id" SERIAL ,
|
||||
"privacy" INTEGER NOT NULL,
|
||||
"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
"updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
`
|
||||
|
||||
await utils.sequelize.query(query, { transaction : utils.transaction })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('videoLive', 'replaySettingId', {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'videoLiveReplaySetting',
|
||||
key: 'id'
|
||||
},
|
||||
onDelete: 'SET NULL'
|
||||
}, { transaction: utils.transaction })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('videoLiveSession', 'replaySettingId', {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'videoLiveReplaySetting',
|
||||
key: 'id'
|
||||
},
|
||||
onDelete: 'SET NULL'
|
||||
}, { transaction: utils.transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const query = `
|
||||
SELECT live."id", v."privacy"
|
||||
FROM "videoLive" live
|
||||
INNER JOIN "video" v ON live."videoId" = v."id"
|
||||
WHERE live."saveReplay" = true
|
||||
`
|
||||
|
||||
const videoLives = await utils.sequelize.query<{ id: number, privacy: number }>(
|
||||
query,
|
||||
{ type: Sequelize.QueryTypes.SELECT, transaction: utils.transaction }
|
||||
)
|
||||
|
||||
for (const videoLive of videoLives) {
|
||||
const query = `
|
||||
WITH new_replay_setting AS (
|
||||
INSERT INTO "videoLiveReplaySetting" ("privacy", "createdAt", "updatedAt")
|
||||
VALUES (:privacy, NOW(), NOW())
|
||||
RETURNING id
|
||||
)
|
||||
UPDATE "videoLive" SET "replaySettingId" = (SELECT id FROM new_replay_setting)
|
||||
WHERE "id" = :id
|
||||
`
|
||||
|
||||
const options = {
|
||||
replacements: { privacy: videoLive.privacy, id: videoLive.id },
|
||||
type: Sequelize.QueryTypes.UPDATE,
|
||||
transaction: utils.transaction
|
||||
}
|
||||
|
||||
await utils.sequelize.query(query, options)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const query = `
|
||||
SELECT session."id", v."privacy"
|
||||
FROM "videoLiveSession" session
|
||||
INNER JOIN "video" v ON session."liveVideoId" = v."id"
|
||||
WHERE session."saveReplay" = true
|
||||
AND session."liveVideoId" IS NOT NULL;
|
||||
`
|
||||
|
||||
const videoLiveSessions = await utils.sequelize.query<{ id: number, privacy: number }>(
|
||||
query,
|
||||
{ type: Sequelize.QueryTypes.SELECT, transaction: utils.transaction }
|
||||
)
|
||||
|
||||
for (const videoLive of videoLiveSessions) {
|
||||
const query = `
|
||||
WITH new_replay_setting AS (
|
||||
INSERT INTO "videoLiveReplaySetting" ("privacy", "createdAt", "updatedAt")
|
||||
VALUES (:privacy, NOW(), NOW())
|
||||
RETURNING id
|
||||
)
|
||||
UPDATE "videoLiveSession" SET "replaySettingId" = (SELECT id FROM new_replay_setting)
|
||||
WHERE "id" = :id
|
||||
`
|
||||
|
||||
const options = {
|
||||
replacements: { privacy: videoLive.privacy, id: videoLive.id },
|
||||
type: Sequelize.QueryTypes.UPDATE,
|
||||
transaction: utils.transaction
|
||||
}
|
||||
|
||||
await utils.sequelize.query(query, options)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
{
|
||||
const query = `
|
||||
CREATE TABLE IF NOT EXISTS "runnerRegistrationToken"(
|
||||
"id" serial,
|
||||
"registrationToken" varchar(255) NOT NULL,
|
||||
"createdAt" timestamp with time zone NOT NULL,
|
||||
"updatedAt" timestamp with time zone NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
`
|
||||
|
||||
await utils.sequelize.query(query, { transaction : utils.transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const query = `
|
||||
CREATE TABLE IF NOT EXISTS "runner"(
|
||||
"id" serial,
|
||||
"runnerToken" varchar(255) NOT NULL,
|
||||
"name" varchar(255) NOT NULL,
|
||||
"description" varchar(1000),
|
||||
"lastContact" timestamp with time zone NOT NULL,
|
||||
"ip" varchar(255) NOT NULL,
|
||||
"runnerRegistrationTokenId" integer REFERENCES "runnerRegistrationToken"("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"createdAt" timestamp with time zone NOT NULL,
|
||||
"updatedAt" timestamp with time zone NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
`
|
||||
|
||||
await utils.sequelize.query(query, { transaction : utils.transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const query = `
|
||||
CREATE TABLE IF NOT EXISTS "runnerJob"(
|
||||
"id" serial,
|
||||
"uuid" uuid NOT NULL,
|
||||
"type" varchar(255) NOT NULL,
|
||||
"payload" jsonb NOT NULL,
|
||||
"privatePayload" jsonb NOT NULL,
|
||||
"state" integer NOT NULL,
|
||||
"failures" integer NOT NULL DEFAULT 0,
|
||||
"error" varchar(5000),
|
||||
"priority" integer NOT NULL,
|
||||
"processingJobToken" varchar(255),
|
||||
"progress" integer,
|
||||
"startedAt" timestamp with time zone,
|
||||
"finishedAt" timestamp with time zone,
|
||||
"dependsOnRunnerJobId" integer REFERENCES "runnerJob"("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"runnerId" integer REFERENCES "runner"("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
"createdAt" timestamp with time zone NOT NULL,
|
||||
"updatedAt" timestamp with time zone NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
|
||||
`
|
||||
|
||||
await utils.sequelize.query(query, { transaction : utils.transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
const { transaction } = utils
|
||||
|
||||
await utils.sequelize.query('drop index if exists "actor_preferred_username"', { transaction })
|
||||
await utils.sequelize.query('drop index if exists "actor_preferred_username_server_id"', { transaction })
|
||||
|
||||
await utils.sequelize.query(
|
||||
'DELETE FROM "actor" v1 USING (' +
|
||||
'SELECT MIN(id) as id, lower("preferredUsername") AS "lowerPreferredUsername", "serverId" ' +
|
||||
'FROM "actor" ' +
|
||||
'GROUP BY "lowerPreferredUsername", "serverId" HAVING COUNT(*) > 1 AND "serverId" IS NOT NULL' +
|
||||
') v2 ' +
|
||||
'WHERE lower(v1."preferredUsername") = v2."lowerPreferredUsername" AND v1."serverId" = v2."serverId" AND v1.id <> v2.id',
|
||||
{ transaction }
|
||||
)
|
||||
|
||||
await utils.sequelize.query(
|
||||
'DELETE FROM "actor" v1 USING (' +
|
||||
'SELECT MIN(id) as id, lower("preferredUsername") AS "lowerPreferredUsername", "serverId" ' +
|
||||
'FROM "actor" ' +
|
||||
'GROUP BY "lowerPreferredUsername", "serverId" HAVING COUNT(*) > 1 AND "serverId" IS NULL' +
|
||||
') v2 ' +
|
||||
'WHERE lower(v1."preferredUsername") = v2."lowerPreferredUsername" AND v1."serverId" IS NULL AND v1.id <> v2.id',
|
||||
{ transaction }
|
||||
)
|
||||
}
|
||||
|
||||
async function down (utils: {
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
transaction: Sequelize.Transaction
|
||||
}) {
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
|
||||
const data = {
|
||||
type: Sequelize.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false
|
||||
}
|
||||
|
||||
await utils.queryInterface.addColumn('user', 'emailPublic', data)
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
const { transaction } = utils
|
||||
|
||||
{
|
||||
await utils.sequelize.query('DELETE FROM "userNotification" WHERE type = 20 AND "userRegistrationId" IS NULL', { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.sequelize.query(
|
||||
'ALTER TABLE "userNotification" DROP CONSTRAINT "userNotification_userRegistrationId_fkey", ' +
|
||||
'ADD CONSTRAINT "userNotification_userRegistrationId_fkey" ' +
|
||||
'FOREIGN KEY ("userRegistrationId") REFERENCES "userRegistration" ("id") ON DELETE CASCADE ON UPDATE CASCADE',
|
||||
{ transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
{
|
||||
const query = `
|
||||
CREATE TABLE IF NOT EXISTS "videoPassword" (
|
||||
"id" SERIAL,
|
||||
"password" VARCHAR(255) NOT NULL,
|
||||
"videoId" INTEGER NOT NULL REFERENCES "video" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
"updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
`
|
||||
|
||||
await utils.sequelize.query(query, { transaction : utils.transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
const { transaction } = utils
|
||||
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.BOOLEAN,
|
||||
allowNull: true,
|
||||
defaultValue: true
|
||||
}
|
||||
|
||||
await utils.queryInterface.addColumn('thumbnail', 'onDisk', data, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
// Remote previews are not on the disk
|
||||
await utils.sequelize.query(
|
||||
'UPDATE "thumbnail" SET "onDisk" = FALSE ' +
|
||||
'WHERE "type" = 2 AND "videoId" NOT IN (SELECT "id" FROM "video" WHERE "remote" IS FALSE)',
|
||||
{ transaction }
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: null
|
||||
}
|
||||
|
||||
await utils.queryInterface.changeColumn('thumbnail', 'onDisk', data, { transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
const { transaction } = utils
|
||||
|
||||
const query = 'DELETE FROM "runner" r1 ' +
|
||||
'USING (SELECT MIN(id) as id, "name" FROM "runner" GROUP BY "name" HAVING COUNT(*) > 1) r2 ' +
|
||||
'WHERE r1."name" = r2."name" AND r1.id <> r2.id'
|
||||
|
||||
await utils.sequelize.query(query, { transaction })
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
const { transaction } = utils
|
||||
|
||||
{
|
||||
const query = 'DELETE FROM "videoSource" WHERE "videoId" IS NULL'
|
||||
await utils.sequelize.query(query, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const query = 'ALTER TABLE "videoSource" ALTER COLUMN "videoId" SET NOT NULL'
|
||||
await utils.sequelize.query(query, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: true,
|
||||
defaultValue: null
|
||||
}
|
||||
|
||||
await utils.queryInterface.addColumn('video', 'inputFileUpdatedAt', data, { transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
const { transaction } = utils
|
||||
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true
|
||||
}
|
||||
|
||||
await utils.queryInterface.addColumn('localVideoViewer', 'subdivisionName', data, { transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
const query = `
|
||||
CREATE TABLE IF NOT EXISTS "userExport" (
|
||||
"id" SERIAL,
|
||||
"filename" VARCHAR(255),
|
||||
"withVideoFiles" BOOLEAN NOT NULL,
|
||||
"state" INTEGER NOT NULL,
|
||||
"error" TEXT,
|
||||
"size" INTEGER,
|
||||
"storage" INTEGER NOT NULL,
|
||||
"userId" INTEGER NOT NULL REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
"updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
);`
|
||||
|
||||
await utils.sequelize.query(query, { transaction: utils.transaction })
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
const query = `
|
||||
CREATE TABLE IF NOT EXISTS "userImport" (
|
||||
"id" SERIAL,
|
||||
"filename" VARCHAR(255),
|
||||
"state" INTEGER NOT NULL,
|
||||
"error" TEXT,
|
||||
"resultSummary" JSONB,
|
||||
"userId" INTEGER NOT NULL REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
"updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
);;`
|
||||
|
||||
await utils.sequelize.query(query, { transaction: utils.transaction })
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.DATE,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}
|
||||
await utils.queryInterface.addColumn('userRegistration', 'processedAt', data)
|
||||
}
|
||||
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.DATE,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}
|
||||
await utils.queryInterface.addColumn('abuse', 'processedAt', data)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}
|
||||
await utils.queryInterface.addColumn('videoFile', 'width', data)
|
||||
}
|
||||
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}
|
||||
await utils.queryInterface.addColumn('videoFile', 'height', data)
|
||||
}
|
||||
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.FLOAT,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}
|
||||
await utils.queryInterface.addColumn('video', 'aspectRatio', data)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
const { transaction } = utils
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('videoSource', 'keptOriginalFilename', {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true
|
||||
}, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('videoSource', 'storage', {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true
|
||||
}, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('videoSource', 'resolution', {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true
|
||||
}, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('videoSource', 'width', {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true
|
||||
}, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('videoSource', 'height', {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true
|
||||
}, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('videoSource', 'fps', {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true
|
||||
}, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('videoSource', 'size', {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true
|
||||
}, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('videoSource', 'metadata', {
|
||||
type: Sequelize.JSONB,
|
||||
allowNull: true
|
||||
}, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('videoSource', 'fileUrl', {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true
|
||||
}, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.renameColumn('videoSource', 'filename', 'inputFilename', { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('userExport', 'fileUrl', {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true
|
||||
}, { transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
down, up
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
const { transaction } = utils
|
||||
|
||||
{
|
||||
await utils.queryInterface.changeColumn('videoSource', 'size', {
|
||||
type: Sequelize.BIGINT,
|
||||
allowNull: true
|
||||
}, { transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
down, up
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
const { transaction } = utils
|
||||
|
||||
{
|
||||
await utils.queryInterface.changeColumn('userExport', 'size', {
|
||||
type: Sequelize.BIGINT,
|
||||
allowNull: true
|
||||
}, { transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
down, up
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
const { transaction } = utils
|
||||
|
||||
{
|
||||
const query = `CREATE TABLE IF NOT EXISTS "automaticTag" ("id" SERIAL , "name" VARCHAR(255) NOT NULL, PRIMARY KEY ("id"));`
|
||||
|
||||
await utils.sequelize.query(query, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const query = `
|
||||
CREATE TABLE IF NOT EXISTS "videoAutomaticTag"(
|
||||
"videoId" integer REFERENCES "video"("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"automaticTagId" integer NOT NULL REFERENCES "automaticTag"("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"accountId" integer REFERENCES "account"("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"createdAt" timestamp with time zone NOT NULL,
|
||||
"updatedAt" timestamp with time zone NOT NULL,
|
||||
PRIMARY KEY ("videoId", "automaticTagId", "accountId")
|
||||
);`
|
||||
|
||||
await utils.sequelize.query(query, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const query = `
|
||||
CREATE TABLE IF NOT EXISTS "commentAutomaticTag"(
|
||||
"commentId" integer REFERENCES "videoComment"("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"automaticTagId" integer NOT NULL REFERENCES "automaticTag"("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"accountId" integer REFERENCES "account"("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"createdAt" timestamp with time zone NOT NULL,
|
||||
"updatedAt" timestamp with time zone NOT NULL,
|
||||
PRIMARY KEY ("commentId", "automaticTagId", "accountId")
|
||||
);`
|
||||
|
||||
await utils.sequelize.query(query, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const query = `
|
||||
CREATE TABLE IF NOT EXISTS "watchedWordsList"(
|
||||
"id" serial,
|
||||
"listName" varchar(255) NOT NULL,
|
||||
"words" varchar(255)[] NOT NULL,
|
||||
"accountId" integer NOT NULL REFERENCES "account"("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"createdAt" timestamp with time zone NOT NULL,
|
||||
"updatedAt" timestamp with time zone NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
);`
|
||||
|
||||
await utils.sequelize.query(query, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const query = `
|
||||
CREATE TABLE IF NOT EXISTS "accountAutomaticTagPolicy"(
|
||||
"id" serial,
|
||||
"policy" integer,
|
||||
"accountId" integer NOT NULL REFERENCES "account"("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"automaticTagId" integer NOT NULL REFERENCES "automaticTag"("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"createdAt" timestamp with time zone NOT NULL,
|
||||
"updatedAt" timestamp with time zone NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
);`
|
||||
|
||||
await utils.sequelize.query(query, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('video', 'commentsPolicy', {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: 1, // ENABLED
|
||||
allowNull: false
|
||||
}, { transaction })
|
||||
|
||||
const query = `UPDATE "video" SET "commentsPolicy" = 2 WHERE "commentsEnabled" IS FALSE` // Disabled
|
||||
await utils.sequelize.query(query, { transaction })
|
||||
|
||||
await utils.queryInterface.changeColumn('video', 'commentsPolicy', {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: false
|
||||
}, { transaction })
|
||||
|
||||
await utils.queryInterface.removeColumn('video', 'commentsEnabled', { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('videoComment', 'heldForReview', {
|
||||
type: Sequelize.BOOLEAN,
|
||||
defaultValue: false,
|
||||
allowNull: false
|
||||
}, { transaction })
|
||||
|
||||
await utils.queryInterface.changeColumn('videoComment', 'heldForReview', {
|
||||
type: Sequelize.BOOLEAN,
|
||||
defaultValue: null,
|
||||
allowNull: false
|
||||
}, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('videoComment', 'replyApproval', {
|
||||
type: Sequelize.STRING,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}, { transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
down, up
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
const { transaction } = utils
|
||||
|
||||
{
|
||||
await utils.queryInterface.changeColumn('videoStreamingPlaylist', 'segmentsSha256Filename', {
|
||||
type: Sequelize.STRING,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}, { transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
down, up
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
const { transaction } = utils
|
||||
|
||||
// Notification
|
||||
{
|
||||
await utils.queryInterface.addColumn('userNotification', 'videoCaptionId', {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'videoCaption',
|
||||
key: 'id'
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'CASCADE'
|
||||
}, { transaction })
|
||||
}
|
||||
|
||||
// Notification settings
|
||||
{
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}
|
||||
await utils.queryInterface.addColumn('userNotificationSetting', 'myVideoTranscriptionGenerated', data, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const query = 'UPDATE "userNotificationSetting" SET "myVideoTranscriptionGenerated" = 1'
|
||||
await utils.sequelize.query(query, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: false
|
||||
}
|
||||
await utils.queryInterface.changeColumn('userNotificationSetting', 'myVideoTranscriptionGenerated', data, { transaction })
|
||||
}
|
||||
}
|
||||
|
||||
// Video job info
|
||||
{
|
||||
await utils.queryInterface.addColumn('videoJobInfo', 'pendingTranscription', {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
}, { transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
down, up
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
const { transaction } = utils
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('videoCaption', 'automaticallyGenerated', {
|
||||
type: Sequelize.BOOLEAN,
|
||||
defaultValue: false,
|
||||
allowNull: false
|
||||
}, { transaction })
|
||||
|
||||
await utils.queryInterface.changeColumn('videoCaption', 'automaticallyGenerated', {
|
||||
type: Sequelize.BOOLEAN,
|
||||
defaultValue: null,
|
||||
allowNull: false
|
||||
}, { transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
down, up
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import { readdir } from 'fs/promises'
|
||||
import { join } from 'path'
|
||||
import { QueryTypes } from 'sequelize'
|
||||
import { currentDir } from '@peertube/peertube-node-utils'
|
||||
import { logger } from '../helpers/logger.js'
|
||||
import { LAST_MIGRATION_VERSION } from './constants.js'
|
||||
import { sequelizeTypescript } from './database.js'
|
||||
|
||||
async function migrate () {
|
||||
const tables = await sequelizeTypescript.getQueryInterface().showAllTables()
|
||||
|
||||
// No tables, we don't need to migrate anything
|
||||
// The installer will do that
|
||||
if (tables.length === 0) return
|
||||
|
||||
let actualVersion: number | null = null
|
||||
|
||||
const query = 'SELECT "migrationVersion" FROM "application"'
|
||||
const options = {
|
||||
type: QueryTypes.SELECT as QueryTypes.SELECT
|
||||
}
|
||||
|
||||
const rows = await sequelizeTypescript.query<{ migrationVersion: number }>(query, options)
|
||||
if (rows?.[0]?.migrationVersion) {
|
||||
actualVersion = rows[0].migrationVersion
|
||||
}
|
||||
|
||||
if (actualVersion === null) {
|
||||
await sequelizeTypescript.query('INSERT INTO "application" ("migrationVersion") VALUES (0)')
|
||||
actualVersion = 0
|
||||
}
|
||||
|
||||
// No need migrations, abort
|
||||
if (actualVersion >= LAST_MIGRATION_VERSION) return
|
||||
|
||||
// If there are a new migration scripts
|
||||
logger.info('Begin migrations.')
|
||||
|
||||
const migrationScripts = await getMigrationScripts()
|
||||
|
||||
for (const migrationScript of migrationScripts) {
|
||||
try {
|
||||
await executeMigration(actualVersion, migrationScript)
|
||||
} catch (err) {
|
||||
logger.error('Cannot execute migration %s.', migrationScript.version, { err })
|
||||
process.exit(-1)
|
||||
}
|
||||
}
|
||||
|
||||
logger.info('Migrations finished. New migration version schema: %s', LAST_MIGRATION_VERSION)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
migrate
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function getMigrationScripts () {
|
||||
const files = await readdir(join(currentDir(import.meta.url), 'migrations'))
|
||||
const filesToMigrate: {
|
||||
version: string
|
||||
script: string
|
||||
}[] = []
|
||||
|
||||
files
|
||||
.filter(file => file.endsWith('.js'))
|
||||
.forEach(file => {
|
||||
// Filename is something like 'version-blabla.js'
|
||||
const version = file.split('-')[0]
|
||||
filesToMigrate.push({
|
||||
version,
|
||||
script: file
|
||||
})
|
||||
})
|
||||
|
||||
return filesToMigrate
|
||||
}
|
||||
|
||||
async function executeMigration (actualVersion: number, entity: { version: string, script: string }) {
|
||||
const versionScript = parseInt(entity.version, 10)
|
||||
|
||||
// Do not execute old migration scripts
|
||||
if (versionScript <= actualVersion) return undefined
|
||||
|
||||
// Load the migration module and run it
|
||||
const migrationScriptName = entity.script
|
||||
logger.info('Executing %s migration script.', migrationScriptName)
|
||||
|
||||
const migrationScript = await import(join(currentDir(import.meta.url), 'migrations', migrationScriptName))
|
||||
|
||||
return sequelizeTypescript.transaction(async t => {
|
||||
const options = {
|
||||
transaction: t,
|
||||
queryInterface: sequelizeTypescript.getQueryInterface(),
|
||||
sequelize: sequelizeTypescript
|
||||
}
|
||||
|
||||
await migrationScript.up(options)
|
||||
|
||||
// Update the new migration version
|
||||
await sequelizeTypescript.query('UPDATE "application" SET "migrationVersion" = ' + versionScript, { transaction: t })
|
||||
})
|
||||
}
|
||||
新しい課題から参照
ユーザをブロックする