はじまりの大地

このコミットが含まれているのは:
2024-07-15 09:14:04 +09:00
コミット 6632905f32
3501個のファイルの変更1439465行の追加0行の削除
+380
ファイルの表示
@@ -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')
}
}
+164
ファイルの表示
@@ -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
}
+770
ファイルの表示
@@ -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()
}
ファイル差分が大きすぎるため省略します 差分を読込み
+248
ファイルの表示
@@ -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 })
}
+200
ファイルの表示
@@ -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()
}
+26
ファイルの表示
@@ -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
}
+38
ファイルの表示
@@ -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
}
+31
ファイルの表示
@@ -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
}
+83
ファイルの表示
@@ -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
}
+54
ファイルの表示
@@ -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
}
+46
ファイルの表示
@@ -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
}
+39
ファイルの表示
@@ -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
}
+26
ファイルの表示
@@ -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
}
+26
ファイルの表示
@@ -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
}
+27
ファイルの表示
@@ -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
}
+26
ファイルの表示
@@ -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
}
+51
ファイルの表示
@@ -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
}
+27
ファイルの表示
@@ -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
}
+27
ファイルの表示
@@ -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
}
+24
ファイルの表示
@@ -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
}
+48
ファイルの表示
@@ -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
}
+55
ファイルの表示
@@ -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
}
+44
ファイルの表示
@@ -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
}
+130
ファイルの表示
@@ -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
}
+33
ファイルの表示
@@ -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
}
+33
ファイルの表示
@@ -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
}
+20
ファイルの表示
@@ -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
}
+23
ファイルの表示
@@ -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
}
+27
ファイルの表示
@@ -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
}
+26
ファイルの表示
@@ -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
}
+50
ファイルの表示
@@ -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
}
+35
ファイルの表示
@@ -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
}
+39
ファイルの表示
@@ -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
}
+26
ファイルの表示
@@ -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
}
+33
ファイルの表示
@@ -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
}
+66
ファイルの表示
@@ -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
}
+56
ファイルの表示
@@ -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
}
+27
ファイルの表示
@@ -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
}
+27
ファイルの表示
@@ -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
}
+21
ファイルの表示
@@ -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
}
+20
ファイルの表示
@@ -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
}
+62
ファイルの表示
@@ -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
}
+35
ファイルの表示
@@ -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
}
+28
ファイルの表示
@@ -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
}
+42
ファイルの表示
@@ -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
}
+52
ファイルの表示
@@ -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
}
+34
ファイルの表示
@@ -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
}
+34
ファイルの表示
@@ -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
}
+56
ファイルの表示
@@ -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
}
+66
ファイルの表示
@@ -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
}
+36
ファイルの表示
@@ -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
}
+33
ファイルの表示
@@ -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
}
+29
ファイルの表示
@@ -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
}
+57
ファイルの表示
@@ -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
}
+27
ファイルの表示
@@ -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
}
+125
ファイルの表示
@@ -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
}
+78
ファイルの表示
@@ -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
}
+44
ファイルの表示
@@ -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
}
+25
ファイルの表示
@@ -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
}
+30
ファイルの表示
@@ -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
}
+31
ファイルの表示
@@ -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
}
+47
ファイルの表示
@@ -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
}
+24
ファイルの表示
@@ -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
}
+38
ファイルの表示
@@ -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
}
+27
ファイルの表示
@@ -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
}
+33
ファイルの表示
@@ -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
}
+31
ファイルの表示
@@ -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
}
+34
ファイルの表示
@@ -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
}
+43
ファイルの表示
@@ -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
}
+91
ファイルの表示
@@ -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
}
+24
ファイルの表示
@@ -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
}
+24
ファイルの表示
@@ -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
}
+122
ファイルの表示
@@ -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
}
+25
ファイルの表示
@@ -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
}
+67
ファイルの表示
@@ -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
}
+31
ファイルの表示
@@ -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
}
+106
ファイルの表示
@@ -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 })
})
}