Init commit
This commit is contained in:
397
server/core/initializers/checker-after-init.ts
Normal file
397
server/core/initializers/checker-after-init.ts
Normal file
@@ -0,0 +1,397 @@
|
||||
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 { getBrowseVideosDefaultSortError, getBrowseVideosDefaultScopeError } from '../helpers/custom-validators/browse-videos.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()
|
||||
checkBrowseVideosConfig()
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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', 'warn', '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')
|
||||
}
|
||||
}
|
||||
|
||||
function checkBrowseVideosConfig () {
|
||||
const sortError = getBrowseVideosDefaultSortError(CONFIG.CLIENT.BROWSE_VIDEOS.DEFAULT_SORT, CONFIG.TRENDING.VIDEOS.ALGORITHMS.ENABLED)
|
||||
if (sortError) throw new Error(sortError)
|
||||
|
||||
const scopeError = getBrowseVideosDefaultScopeError(CONFIG.CLIENT.BROWSE_VIDEOS.DEFAULT_SCOPE)
|
||||
if (scopeError) throw new Error(scopeError)
|
||||
}
|
||||
346
server/core/initializers/checker-before-init.ts
Normal file
346
server/core/initializers/checker-before-init.ts
Normal file
@@ -0,0 +1,346 @@
|
||||
import { promisify0 } from '@peertube/peertube-core-utils'
|
||||
import config from 'config'
|
||||
import { parseSemVersion } from '../helpers/core-utils.js'
|
||||
import { logger } from '../helpers/logger.js'
|
||||
|
||||
// ONLY USE CORE MODULES IN THIS FILE!
|
||||
|
||||
// Check the config files
|
||||
export function checkMissedConfig () {
|
||||
const required = [
|
||||
'listen.port',
|
||||
'listen.hostname',
|
||||
'webserver.https',
|
||||
'webserver.hostname',
|
||||
'webserver.port',
|
||||
'secrets.peertube',
|
||||
'http_timeouts.request',
|
||||
'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',
|
||||
'storage.uploads',
|
||||
'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',
|
||||
'user.password_constraints.min_length',
|
||||
'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',
|
||||
'transcoding.fps.max',
|
||||
'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.videos.http.force_ipv4',
|
||||
'import.videos.http.proxies',
|
||||
'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.prefer_author_display_name',
|
||||
'client.browse_videos.default_sort',
|
||||
'client.browse_videos.default_scope',
|
||||
'client.menu.login.redirect_on_single_external_auth',
|
||||
'client.header.hide_instance_name',
|
||||
'defaults.publish.download_enabled',
|
||||
'defaults.publish.comments_policy',
|
||||
'defaults.publish.privacy',
|
||||
'defaults.publish.licence',
|
||||
'defaults.player.auto_play',
|
||||
'defaults.player.theme',
|
||||
'instance.name',
|
||||
'instance.short_description',
|
||||
'instance.default_language',
|
||||
'instance.description',
|
||||
'instance.terms',
|
||||
'instance.default_client_route',
|
||||
'instance.is_nsfw',
|
||||
'instance.default_nsfw_policy',
|
||||
'instance.robots',
|
||||
'instance.securitytxt',
|
||||
'instance.server_country',
|
||||
'instance.support.text',
|
||||
'instance.social.external_link',
|
||||
'instance.social.mastodon_link',
|
||||
'instance.social.bluesky_link',
|
||||
'instance.social.x_link',
|
||||
'services.twitter.username',
|
||||
'followers.instance.enabled',
|
||||
'followers.instance.manual_approval',
|
||||
'followers.channels.enabled',
|
||||
'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.force_path_style',
|
||||
'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',
|
||||
'object_storage.captions.bucket_name',
|
||||
'object_storage.captions.prefix',
|
||||
'object_storage.captions.base_url',
|
||||
'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.enabled',
|
||||
'federation.prevent_ssrf',
|
||||
'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.auto_start_ffmpeg_streaming',
|
||||
'live.enable_source_start',
|
||||
'live.enable_sink_start',
|
||||
'live.ffmpeg.path',
|
||||
'live.ffmpeg.hls_time',
|
||||
'live.ffmpeg.hls_list_size',
|
||||
'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.fps.max',
|
||||
'live.transcoding.remote_runners.enabled',
|
||||
'storyboards.enabled',
|
||||
'webrtc.stun_servers',
|
||||
'nsfw_flags_settings.enabled',
|
||||
'download_generate_video.max_parallel_downloads',
|
||||
'video_comments.accept_remote_comments'
|
||||
]
|
||||
|
||||
const requiredAlternatives = [
|
||||
[ // set
|
||||
[ 'redis.hostname', 'redis.port' ], // alternative
|
||||
[ 'redis.socket' ],
|
||||
[ 'redis.sentinel.master_name', 'redis.sentinel.sentinels[0].host', '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)
|
||||
export 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(`Codec ${codec} not found in FFmpeg.`)
|
||||
}
|
||||
|
||||
if (codecs[codec].canEncode !== true) {
|
||||
throw new Error(`Unavailable encode codec ${codec} in FFmpeg`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function checkNodeVersion () {
|
||||
const v = process.version
|
||||
const { major, minor } = parseSemVersion(v)
|
||||
|
||||
logger.debug(`Checking NodeJS version ${v}`)
|
||||
|
||||
if (major < 20) {
|
||||
throw new Error(`Your NodeJS version ${v} is not supported. Please upgrade.`)
|
||||
}
|
||||
|
||||
if (major === 20 && minor < 19) {
|
||||
throw new Error(`NodeJS v20.19 and above is required`)
|
||||
}
|
||||
|
||||
if (major === 22 && minor < 12) {
|
||||
throw new Error(`NodeJS v22.12 and above is required`)
|
||||
}
|
||||
}
|
||||
1296
server/core/initializers/config.ts
Normal file
1296
server/core/initializers/config.ts
Normal file
File diff suppressed because it is too large
Load Diff
1579
server/core/initializers/constants.ts
Normal file
1579
server/core/initializers/constants.ts
Normal file
File diff suppressed because it is too large
Load Diff
260
server/core/initializers/database.ts
Normal file
260
server/core/initializers/database.ts
Normal file
@@ -0,0 +1,260 @@
|
||||
import { isTestOrDevInstance } from '@peertube/peertube-node-utils'
|
||||
import { ActorCustomPageModel } from '@server/models/account/actor-custom-page.js'
|
||||
import { ActorReservedModel } from '@server/models/actor/actor-reserved.js'
|
||||
import { UploadImageModel } from '@server/models/application/upload-image.js'
|
||||
import { AccountAutomaticTagPolicyModel } from '@server/models/automatic-tag/account-automatic-tag-policy.js'
|
||||
import { AutomaticTagModel } from '@server/models/automatic-tag/automatic-tag.js'
|
||||
import { CommentAutomaticTagModel } from '@server/models/automatic-tag/comment-automatic-tag.js'
|
||||
import { VideoAutomaticTagModel } from '@server/models/automatic-tag/video-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 { PlayerSettingModel } from '@server/models/video/player-setting.js'
|
||||
import { StoryboardModel } from '@server/models/video/storyboard.js'
|
||||
import { VideoChannelActivityModel } from '@server/models/video/video-channel-activity.js'
|
||||
import { VideoChannelCollaboratorModel } from '@server/models/video/video-channel-collaborator.js'
|
||||
import { VideoChannelSyncModel } from '@server/models/video/video-channel-sync.js'
|
||||
import { VideoChapterModel } from '@server/models/video/video-chapter.js'
|
||||
import { EventModel } from '@server/models/video/event.js'
|
||||
import { EventRegistrationModel } from '@server/models/video/event-registration.js'
|
||||
import { LiveVideoModel } from '@server/models/video/live-video.js'
|
||||
import { VideoJobInfoModel } from '@server/models/video/video-job-info.js'
|
||||
import { VideoLiveReplaySettingModel } from '@server/models/video/video-live-replay-setting.js'
|
||||
import { VideoLiveScheduleModel } from '@server/models/video/video-live-schedule.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'
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export 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' ] })
|
||||
}
|
||||
})
|
||||
|
||||
export 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)
|
||||
})
|
||||
}
|
||||
|
||||
export 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,
|
||||
VideoChannelActivityModel,
|
||||
VideoChapterModel,
|
||||
VideoCaptionModel,
|
||||
VideoBlacklistModel,
|
||||
EventModel,
|
||||
EventRegistrationModel,
|
||||
LiveVideoModel,
|
||||
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,
|
||||
UploadImageModel,
|
||||
VideoLiveScheduleModel,
|
||||
PlayerSettingModel,
|
||||
VideoChannelCollaboratorModel,
|
||||
ActorReservedModel
|
||||
])
|
||||
|
||||
// Check extensions exist in the database
|
||||
await checkPostgresExtensions()
|
||||
|
||||
await createFunctions()
|
||||
|
||||
if (!silent) logger.info('Database %s is ready.', dbname)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Private
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
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 (err) {
|
||||
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, { cause: err })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 })
|
||||
}
|
||||
202
server/core/initializers/installer.ts
Normal file
202
server/core/initializers/installer.ts
Normal file
@@ -0,0 +1,202 @@
|
||||
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'
|
||||
import { initPNPM } from '@server/lib/plugins/package-manager.js'
|
||||
|
||||
async function installApplication () {
|
||||
try {
|
||||
await Promise.all([
|
||||
// Database related
|
||||
sequelizeTypescript.sync()
|
||||
.then(() => {
|
||||
return Promise.all([
|
||||
createApplicationIfNotExist(),
|
||||
createOAuthClientIfNotExist(),
|
||||
createOAuthAdminIfNotExist(),
|
||||
createRunnerRegistrationTokenIfNotExist(),
|
||||
initPNPM()
|
||||
])
|
||||
}),
|
||||
|
||||
// Directories
|
||||
removeCacheAndTmpDirectories()
|
||||
.then(() => createDirectoriesIfNotExist())
|
||||
])
|
||||
} catch (err) {
|
||||
logger.error('Cannot install application.', { err })
|
||||
process.exit(-1)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
installApplication
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function removeCacheAndTmpDirectories () {
|
||||
const cacheDirectories = Object.keys(FILES_CACHE)
|
||||
.map(k => FILES_CACHE[k].DIRECTORY)
|
||||
|
||||
const tasks: Promise<any>[] = []
|
||||
|
||||
// Cache directories
|
||||
for (const dir of cacheDirectories) {
|
||||
tasks.push(removeDirectoryOrContent(dir))
|
||||
}
|
||||
|
||||
tasks.push(removeDirectoryOrContent(CONFIG.STORAGE.TMP_DIR))
|
||||
|
||||
return Promise.all(tasks)
|
||||
}
|
||||
|
||||
async function removeDirectoryOrContent (dir: string) {
|
||||
try {
|
||||
await remove(dir)
|
||||
} catch (err) {
|
||||
logger.debug('Cannot remove directory %s. Removing content instead.', dir, { err })
|
||||
|
||||
const files = await readdir(dir)
|
||||
|
||||
for (const file of files) {
|
||||
await remove(join(dir, file))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createDirectoriesIfNotExist () {
|
||||
const storage = CONFIG.STORAGE
|
||||
const cacheDirectories = Object.keys(FILES_CACHE)
|
||||
.map(k => FILES_CACHE[k].DIRECTORY)
|
||||
|
||||
const tasks: Promise<void>[] = []
|
||||
for (const key of Object.keys(storage)) {
|
||||
const dir = storage[key]
|
||||
tasks.push(ensureDir(dir))
|
||||
}
|
||||
|
||||
// Cache directories
|
||||
for (const dir of cacheDirectories) {
|
||||
tasks.push(ensureDir(dir))
|
||||
}
|
||||
|
||||
tasks.push(ensureDir(DIRECTORIES.HLS_STREAMING_PLAYLIST.PRIVATE))
|
||||
tasks.push(ensureDir(DIRECTORIES.HLS_STREAMING_PLAYLIST.PUBLIC))
|
||||
tasks.push(ensureDir(DIRECTORIES.WEB_VIDEOS.PUBLIC))
|
||||
tasks.push(ensureDir(DIRECTORIES.WEB_VIDEOS.PRIVATE))
|
||||
|
||||
// Resumable upload directory
|
||||
tasks.push(ensureDir(DIRECTORIES.RESUMABLE_UPLOAD))
|
||||
|
||||
return Promise.all(tasks)
|
||||
}
|
||||
|
||||
async function createOAuthClientIfNotExist () {
|
||||
const exist = await clientsExist()
|
||||
// Nothing to do, clients already exist
|
||||
if (exist === true) return undefined
|
||||
|
||||
logger.info('Creating a default OAuth Client.')
|
||||
|
||||
const id = passwordGenerator(32, false, /[a-z0-9]/)
|
||||
const secret = passwordGenerator(32, false, /[a-zA-Z0-9]/)
|
||||
const client = new OAuthClientModel({
|
||||
clientId: id,
|
||||
clientSecret: secret,
|
||||
grants: [ 'password', 'refresh_token' ],
|
||||
redirectUris: null
|
||||
})
|
||||
|
||||
const createdClient = await client.save()
|
||||
logger.info('Client id: ' + createdClient.clientId)
|
||||
logger.info('Client secret: ' + createdClient.clientSecret)
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
async function createOAuthAdminIfNotExist () {
|
||||
const exist = await usersExist()
|
||||
// Nothing to do, users already exist
|
||||
if (exist === true) return undefined
|
||||
|
||||
logger.info('Creating the administrator.')
|
||||
|
||||
const username = 'root'
|
||||
const role = UserRole.ADMINISTRATOR
|
||||
const email = CONFIG.ADMIN.EMAIL
|
||||
let validatePassword = true
|
||||
let password = ''
|
||||
|
||||
// Do not generate a random password for test and dev environments
|
||||
if (isTestOrDevInstance()) {
|
||||
password = 'test'
|
||||
|
||||
if (process.env.NODE_APP_INSTANCE) {
|
||||
password += process.env.NODE_APP_INSTANCE
|
||||
}
|
||||
|
||||
// Our password is weak so do not validate it
|
||||
validatePassword = false
|
||||
} else if (process.env.PT_INITIAL_ROOT_PASSWORD) {
|
||||
password = process.env.PT_INITIAL_ROOT_PASSWORD
|
||||
} else {
|
||||
password = passwordGenerator(16, true)
|
||||
}
|
||||
|
||||
const user = buildUser({
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
role,
|
||||
emailVerified: true,
|
||||
videoQuota: -1,
|
||||
videoQuotaDaily: -1
|
||||
})
|
||||
|
||||
await createUserAccountAndChannelAndPlaylist({ userToCreate: user, channelNames: undefined, validateUser: validatePassword })
|
||||
logger.info('Username: ' + username)
|
||||
logger.info('User password: ' + password)
|
||||
}
|
||||
|
||||
async function createApplicationIfNotExist () {
|
||||
const exist = await applicationExist()
|
||||
// Nothing to do, application already exist
|
||||
if (exist === true) return undefined
|
||||
|
||||
logger.info('Creating application account.')
|
||||
|
||||
const application = await ApplicationModel.create({
|
||||
migrationVersion: LAST_MIGRATION_VERSION,
|
||||
nodeVersion: process.version,
|
||||
nodeABIVersion: getNodeABIVersion()
|
||||
})
|
||||
|
||||
return createApplicationActor(application.id)
|
||||
}
|
||||
|
||||
async function createRunnerRegistrationTokenIfNotExist () {
|
||||
const total = await RunnerRegistrationTokenModel.countTotal()
|
||||
if (total !== 0) return undefined
|
||||
|
||||
const token = new RunnerRegistrationTokenModel({
|
||||
registrationToken: generateRunnerRegistrationToken()
|
||||
})
|
||||
|
||||
await token.save()
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
|
||||
{
|
||||
const field = {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: true
|
||||
}
|
||||
await utils.queryInterface.addColumn('user', 'lastLoginDate', field)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
|
||||
// We made a mistake with the migration in 2.2.0-rc.1
|
||||
// Docker containers did not include this migration file
|
||||
// So we check the table definition and add the column if it does not exist
|
||||
const tableDefinition = await utils.queryInterface.describeTable('videoFile')
|
||||
|
||||
if (!tableDefinition['metadata']) {
|
||||
const metadata = {
|
||||
type: Sequelize.JSONB,
|
||||
allowNull: true
|
||||
}
|
||||
await utils.queryInterface.addColumn('videoFile', 'metadata', metadata)
|
||||
}
|
||||
|
||||
if (!tableDefinition['metadataUrl']) {
|
||||
const metadataUrl = {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true
|
||||
}
|
||||
await utils.queryInterface.addColumn('videoFile', 'metadataUrl', metadataUrl)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
await utils.queryInterface.addColumn('videoAbuse', 'predefinedReasons', {
|
||||
type: Sequelize.ARRAY(Sequelize.INTEGER),
|
||||
allowNull: true
|
||||
})
|
||||
|
||||
await utils.queryInterface.addColumn('videoAbuse', 'startAt', {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true
|
||||
})
|
||||
|
||||
await utils.queryInterface.addColumn('videoAbuse', 'endAt', {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true
|
||||
})
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
83
server/core/initializers/migrations/0520-abuses-split.ts
Normal file
83
server/core/initializers/migrations/0520-abuses-split.ts
Normal file
@@ -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
server/core/initializers/migrations/0525-abuse-messages.ts
Normal file
54
server/core/initializers/migrations/0525-abuse-messages.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
await utils.sequelize.query(`
|
||||
CREATE TABLE IF NOT EXISTS "abuseMessage" (
|
||||
"id" serial,
|
||||
"message" text NOT NULL,
|
||||
"byModerator" boolean NOT NULL,
|
||||
"accountId" integer REFERENCES "account" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
"abuseId" integer NOT NULL REFERENCES "abuse" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"createdAt" timestamp WITH time zone NOT NULL,
|
||||
"updatedAt" timestamp WITH time zone NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
`)
|
||||
|
||||
const notificationSettingColumns = [ 'abuseStateChange', 'abuseNewMessage' ]
|
||||
|
||||
for (const column of notificationSettingColumns) {
|
||||
const data = {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}
|
||||
await utils.queryInterface.addColumn('userNotificationSetting', column, data)
|
||||
}
|
||||
|
||||
{
|
||||
const query = 'UPDATE "userNotificationSetting" SET "abuseStateChange" = 3, "abuseNewMessage" = 3'
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
|
||||
for (const column of notificationSettingColumns) {
|
||||
const data = {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: false
|
||||
}
|
||||
await utils.queryInterface.changeColumn('userNotificationSetting', column, data)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
import { WEBSERVER } from '../constants.js'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
{
|
||||
const field = {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true
|
||||
}
|
||||
await utils.queryInterface.changeColumn('videoPlaylistElement', 'url', field)
|
||||
}
|
||||
|
||||
{
|
||||
await utils.sequelize.query('DROP INDEX IF EXISTS video_playlist_element_video_playlist_id_video_id;')
|
||||
}
|
||||
|
||||
{
|
||||
const selectPlaylistUUID = 'SELECT "uuid" FROM "videoPlaylist" WHERE "id" = "videoPlaylistElement"."videoPlaylistId"'
|
||||
const url = `'${WEBSERVER.URL}' || '/video-playlists/' || (${selectPlaylistUUID}) || '/videos/' || "videoPlaylistElement"."id"`
|
||||
|
||||
const query = `
|
||||
UPDATE "videoPlaylistElement" SET "url" = ${url} WHERE id IN (
|
||||
SELECT "videoPlaylistElement"."id" FROM "videoPlaylistElement"
|
||||
INNER JOIN "videoPlaylist" ON "videoPlaylist".id = "videoPlaylistElement"."videoPlaylistId"
|
||||
INNER JOIN account ON account.id = "videoPlaylist"."ownerAccountId"
|
||||
INNER JOIN actor ON actor.id = account."actorId"
|
||||
WHERE actor."serverId" IS NULL
|
||||
)`
|
||||
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
39
server/core/initializers/migrations/0535-video-live.ts
Normal file
39
server/core/initializers/migrations/0535-video-live.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
{
|
||||
const query = `
|
||||
CREATE TABLE IF NOT EXISTS "videoLive" (
|
||||
"id" SERIAL ,
|
||||
"streamKey" VARCHAR(255),
|
||||
"videoId" INTEGER NOT NULL REFERENCES "video" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
"updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
`
|
||||
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('video', 'isLive', {
|
||||
type: Sequelize.BOOLEAN,
|
||||
defaultValue: false,
|
||||
allowNull: false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.STRING,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}
|
||||
|
||||
await utils.queryInterface.changeColumn('videoFile', 'infoHash', data)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.BOOLEAN,
|
||||
defaultValue: false,
|
||||
allowNull: false
|
||||
}
|
||||
|
||||
await utils.queryInterface.addColumn('videoLive', 'saveReplay', data)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
const query = `
|
||||
WITH t AS (
|
||||
SELECT actor.id FROM actor
|
||||
LEFT JOIN "videoChannel" ON "videoChannel"."actorId" = actor.id
|
||||
LEFT JOIN account ON account."actorId" = "actor"."id"
|
||||
WHERE "videoChannel".id IS NULL and "account".id IS NULL
|
||||
) DELETE FROM "actorFollow" WHERE "actorId" IN (SELECT t.id FROM t) OR "targetActorId" in (SELECT t.id FROM t)
|
||||
`
|
||||
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
26
server/core/initializers/migrations/0555-actor-follow-url.ts
Normal file
26
server/core/initializers/migrations/0555-actor-follow-url.ts
Normal file
@@ -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
server/core/initializers/migrations/0560-user-feed-token.ts
Normal file
51
server/core/initializers/migrations/0560-user-feed-token.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
import { buildUUID } from '@peertube/peertube-node-utils'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
const q = utils.queryInterface
|
||||
|
||||
{
|
||||
// Create uuid column for users
|
||||
const userFeedTokenUUID = {
|
||||
type: Sequelize.UUID,
|
||||
defaultValue: Sequelize.UUIDV4,
|
||||
allowNull: true
|
||||
}
|
||||
await q.addColumn('user', 'feedToken', userFeedTokenUUID)
|
||||
}
|
||||
|
||||
// Set UUID to previous users
|
||||
{
|
||||
const query = 'SELECT * FROM "user" WHERE "feedToken" IS NULL'
|
||||
const options = { type: Sequelize.QueryTypes.SELECT as Sequelize.QueryTypes.SELECT }
|
||||
const users = await utils.sequelize.query<any>(query, options)
|
||||
|
||||
for (const user of users) {
|
||||
const queryUpdate = `UPDATE "user" SET "feedToken" = '${buildUUID()}' WHERE id = ${user.id}`
|
||||
await utils.sequelize.query(queryUpdate)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const userFeedTokenUUID = {
|
||||
type: Sequelize.UUID,
|
||||
defaultValue: Sequelize.UUIDV4,
|
||||
allowNull: false
|
||||
}
|
||||
await q.changeColumn('user', 'feedToken', userFeedTokenUUID)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
{
|
||||
const query = `
|
||||
UPDATE "actorFollow" SET url = follower.url || '/follows/' || following.id
|
||||
FROM actor follower, actor following
|
||||
WHERE follower."serverId" IS NULL AND follower.id = "actorFollow"."actorId" AND following.id = "actorFollow"."targetActorId"
|
||||
`
|
||||
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
27
server/core/initializers/migrations/0570-permanent-live.ts
Normal file
27
server/core/initializers/migrations/0570-permanent-live.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.BOOLEAN,
|
||||
defaultValue: false,
|
||||
allowNull: false
|
||||
}
|
||||
|
||||
await utils.queryInterface.addColumn('videoLive', 'permanentLive', data)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
{
|
||||
const query = 'DELETE FROM "thumbnail" s1 ' +
|
||||
'USING (SELECT MIN(id) as id, "filename", "type" FROM "thumbnail" GROUP BY "filename", "type" HAVING COUNT(*) > 1) s2 ' +
|
||||
'WHERE s1."filename" = s2."filename" AND s1."type" = s2."type" AND s1.id <> s2.id'
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
48
server/core/initializers/migrations/0580-caption-filename.ts
Normal file
48
server/core/initializers/migrations/0580-caption-filename.ts
Normal file
@@ -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
server/core/initializers/migrations/0585-video-file-names.ts
Normal file
55
server/core/initializers/migrations/0585-video-file-names.ts
Normal file
@@ -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
server/core/initializers/migrations/0590-trackers.ts
Normal file
44
server/core/initializers/migrations/0590-trackers.ts
Normal file
@@ -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
server/core/initializers/migrations/0595-remote-url.ts
Normal file
130
server/core/initializers/migrations/0595-remote-url.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
|
||||
// Torrent and file URLs
|
||||
{
|
||||
const fromQueryWebtorrent = `SELECT 'https://' || server.host AS "serverUrl", '/static/webseed/' AS "filePath", "videoFile".id ` +
|
||||
`FROM video ` +
|
||||
`INNER JOIN "videoChannel" ON "videoChannel".id = video."channelId" ` +
|
||||
`INNER JOIN actor ON actor.id = "videoChannel"."actorId" ` +
|
||||
`INNER JOIN server ON server.id = actor."serverId" ` +
|
||||
`INNER JOIN "videoFile" ON "videoFile"."videoId" = video.id ` +
|
||||
`WHERE video.remote IS TRUE`
|
||||
|
||||
const fromQueryHLS = `SELECT 'https://' || server.host AS "serverUrl", ` +
|
||||
`'/static/streaming-playlists/hls/' || video.uuid || '/' AS "filePath", "videoFile".id ` +
|
||||
`FROM video ` +
|
||||
`INNER JOIN "videoChannel" ON "videoChannel".id = video."channelId" ` +
|
||||
`INNER JOIN actor ON actor.id = "videoChannel"."actorId" ` +
|
||||
`INNER JOIN server ON server.id = actor."serverId" ` +
|
||||
`INNER JOIN "videoStreamingPlaylist" ON "videoStreamingPlaylist"."videoId" = video.id ` +
|
||||
`INNER JOIN "videoFile" ON "videoFile"."videoStreamingPlaylistId" = "videoStreamingPlaylist".id ` +
|
||||
`WHERE video.remote IS TRUE`
|
||||
|
||||
for (const fromQuery of [ fromQueryWebtorrent, fromQueryHLS ]) {
|
||||
const query = `UPDATE "videoFile" ` +
|
||||
`SET "torrentUrl" = t."serverUrl" || '/static/torrents/' || "videoFile"."torrentFilename", ` +
|
||||
`"fileUrl" = t."serverUrl" || t."filePath" || "videoFile"."filename" ` +
|
||||
`FROM (${fromQuery}) AS t WHERE t.id = "videoFile"."id" AND "videoFile"."fileUrl" IS NULL`
|
||||
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
}
|
||||
|
||||
// Caption URLs
|
||||
{
|
||||
const fromQuery = `SELECT 'https://' || server.host AS "serverUrl", "video".uuid, "videoCaption".id ` +
|
||||
`FROM video ` +
|
||||
`INNER JOIN "videoChannel" ON "videoChannel".id = video."channelId" ` +
|
||||
`INNER JOIN actor ON actor.id = "videoChannel"."actorId" ` +
|
||||
`INNER JOIN server ON server.id = actor."serverId" ` +
|
||||
`INNER JOIN "videoCaption" ON "videoCaption"."videoId" = video.id ` +
|
||||
`WHERE video.remote IS TRUE`
|
||||
|
||||
const query = `UPDATE "videoCaption" ` +
|
||||
`SET "fileUrl" = t."serverUrl" || '/lazy-static/video-captions/' || t.uuid || '-' || "videoCaption"."language" || '.vtt' ` +
|
||||
`FROM (${fromQuery}) AS t WHERE t.id = "videoCaption"."id" AND "videoCaption"."fileUrl" IS NULL`
|
||||
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
|
||||
// Thumbnail URLs
|
||||
{
|
||||
const fromQuery = `SELECT 'https://' || server.host AS "serverUrl", "video".uuid, "thumbnail".id ` +
|
||||
`FROM video ` +
|
||||
`INNER JOIN "videoChannel" ON "videoChannel".id = video."channelId" ` +
|
||||
`INNER JOIN actor ON actor.id = "videoChannel"."actorId" ` +
|
||||
`INNER JOIN server ON server.id = actor."serverId" ` +
|
||||
`INNER JOIN "thumbnail" ON "thumbnail"."videoId" = video.id ` +
|
||||
`WHERE video.remote IS TRUE`
|
||||
|
||||
// Thumbnails
|
||||
{
|
||||
const query = `UPDATE "thumbnail" ` +
|
||||
`SET "fileUrl" = t."serverUrl" || '/static/thumbnails/' || t.uuid || '.jpg' ` +
|
||||
`FROM (${fromQuery}) AS t WHERE t.id = "thumbnail"."id" AND "thumbnail"."fileUrl" IS NULL AND thumbnail.type = 1`
|
||||
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
|
||||
{
|
||||
// Previews
|
||||
const query = `UPDATE "thumbnail" ` +
|
||||
`SET "fileUrl" = t."serverUrl" || '/lazy-static/previews/' || t.uuid || '.jpg' ` +
|
||||
`FROM (${fromQuery}) AS t WHERE t.id = "thumbnail"."id" AND "thumbnail"."fileUrl" IS NULL AND thumbnail.type = 2`
|
||||
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
}
|
||||
|
||||
// Trackers
|
||||
{
|
||||
const trackerUrls = [
|
||||
`'https://' || server.host || '/tracker/announce'`,
|
||||
`'wss://' || server.host || '/tracker/socket'`
|
||||
]
|
||||
|
||||
for (const trackerUrl of trackerUrls) {
|
||||
{
|
||||
const query = `INSERT INTO "tracker" ("url", "createdAt", "updatedAt") ` +
|
||||
`SELECT ${trackerUrl} AS "url", NOW(), NOW() ` +
|
||||
`FROM video ` +
|
||||
`INNER JOIN "videoChannel" ON "videoChannel".id = video."channelId" ` +
|
||||
`INNER JOIN actor ON actor.id = "videoChannel"."actorId" ` +
|
||||
`INNER JOIN server ON server.id = actor."serverId" ` +
|
||||
`WHERE video.remote IS TRUE ` +
|
||||
`ON CONFLICT DO NOTHING`
|
||||
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
|
||||
{
|
||||
const query = `INSERT INTO "videoTracker" ("videoId", "trackerId", "createdAt", "updatedAt") ` +
|
||||
`SELECT video.id, (SELECT tracker.id FROM tracker WHERE url = ${trackerUrl}) AS "trackerId", NOW(), NOW()` +
|
||||
`FROM video ` +
|
||||
`INNER JOIN "videoChannel" ON "videoChannel".id = video."channelId" ` +
|
||||
`INNER JOIN actor ON actor.id = "videoChannel"."actorId" ` +
|
||||
`INNER JOIN server ON server.id = actor."serverId" ` +
|
||||
`WHERE video.remote IS TRUE`
|
||||
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
|
||||
{
|
||||
const query = 'DELETE FROM "videoFile" f1 ' +
|
||||
'USING (SELECT MIN(id) as id, "torrentFilename" FROM "videoFile" GROUP BY "torrentFilename" HAVING COUNT(*) > 1) f2 ' +
|
||||
'WHERE f1."torrentFilename" = f2."torrentFilename" AND f1.id <> f2.id'
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
|
||||
{
|
||||
const query = 'DELETE FROM "videoFile" f1 ' +
|
||||
'USING (SELECT MIN(id) as id, "filename" FROM "videoFile" GROUP BY "filename" HAVING COUNT(*) > 1) f2 ' +
|
||||
'WHERE f1."filename" = f2."filename" AND f1.id <> f2.id'
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
import { generateRSAKeyPairPromise } from '../../helpers/core-utils.js'
|
||||
import { PRIVATE_RSA_KEY_SIZE } from '../constants.js'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
|
||||
{
|
||||
const query = 'SELECT * FROM "actor" WHERE "serverId" IS NULL AND "publicKey" IS NULL'
|
||||
const options = { type: Sequelize.QueryTypes.SELECT as Sequelize.QueryTypes.SELECT }
|
||||
const actors = await utils.sequelize.query<any>(query, options)
|
||||
|
||||
for (const actor of actors) {
|
||||
const { privateKey, publicKey } = await generateRSAKeyPairPromise(PRIVATE_RSA_KEY_SIZE)
|
||||
|
||||
const queryUpdate = `UPDATE "actor" SET "publicKey" = '${publicKey}', "privateKey" = '${privateKey}' WHERE id = ${actor.id}`
|
||||
await utils.sequelize.query(queryUpdate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
20
server/core/initializers/migrations/0610-views-index copy.ts
Normal file
20
server/core/initializers/migrations/0610-views-index copy.ts
Normal file
@@ -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
server/core/initializers/migrations/0612-captions-unique.ts
Normal file
23
server/core/initializers/migrations/0612-captions-unique.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
|
||||
await utils.sequelize.query(
|
||||
'DELETE FROM "videoCaption" v1 USING (SELECT MIN(id) as id, "filename" FROM "videoCaption" ' +
|
||||
'GROUP BY "filename" HAVING COUNT(*) > 1) v2 WHERE v1."filename" = v2."filename" AND v1.id <> v2.id'
|
||||
)
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
{
|
||||
const notificationSettingColumns = [ 'newPeerTubeVersion', 'newPluginVersion' ]
|
||||
|
||||
for (const column of notificationSettingColumns) {
|
||||
const data = {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}
|
||||
await utils.queryInterface.addColumn('userNotificationSetting', column, data)
|
||||
}
|
||||
|
||||
{
|
||||
const query = 'UPDATE "userNotificationSetting" SET "newPeerTubeVersion" = 3, "newPluginVersion" = 1'
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
|
||||
for (const column of notificationSettingColumns) {
|
||||
const data = {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: false
|
||||
}
|
||||
await utils.queryInterface.changeColumn('userNotificationSetting', column, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.STRING,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}
|
||||
await utils.queryInterface.addColumn('application', 'latestPeerTubeVersion', data)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
|
||||
{
|
||||
await utils.sequelize.query(`
|
||||
ALTER TABLE "userNotification"
|
||||
ADD COLUMN "applicationId" INTEGER REFERENCES "application" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
ADD COLUMN "pluginId" INTEGER REFERENCES "plugin" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||
`)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
50
server/core/initializers/migrations/0630-banner.ts
Normal file
50
server/core/initializers/migrations/0630-banner.ts
Normal file
@@ -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
server/core/initializers/migrations/0635-actor-image-size.ts
Normal file
35
server/core/initializers/migrations/0635-actor-image-size.ts
Normal file
@@ -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
server/core/initializers/migrations/0640-unique-keys.ts
Normal file
39
server/core/initializers/migrations/0640-unique-keys.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
{
|
||||
await utils.sequelize.query(
|
||||
'DELETE FROM "actor" v1 USING (SELECT MIN(id) as id, "preferredUsername", "serverId" FROM "actor" ' +
|
||||
'GROUP BY "preferredUsername", "serverId" HAVING COUNT(*) > 1 AND "serverId" IS NOT NULL) v2 ' +
|
||||
'WHERE v1."preferredUsername" = v2."preferredUsername" AND v1."serverId" = v2."serverId" AND v1.id <> v2.id'
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
await utils.sequelize.query(
|
||||
'DELETE FROM "actor" v1 USING (SELECT MIN(id) as id, "url" FROM "actor" GROUP BY "url" HAVING COUNT(*) > 1) v2 ' +
|
||||
'WHERE v1."url" = v2."url" AND v1.id <> v2.id'
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
await utils.sequelize.query(
|
||||
'DELETE FROM "tag" v1 USING (SELECT MIN(id) as id, "name" FROM "tag" GROUP BY "name" HAVING COUNT(*) > 1) v2 ' +
|
||||
'WHERE v1."name" = v2."name" AND v1.id <> v2.id'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.DATE,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}
|
||||
await utils.queryInterface.addColumn('actor', 'remoteCreatedAt', data)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
{
|
||||
const query = `
|
||||
CREATE TABLE IF NOT EXISTS "actorCustomPage" (
|
||||
"id" serial,
|
||||
"content" TEXT,
|
||||
"type" varchar(255) NOT NULL,
|
||||
"actorId" integer NOT NULL REFERENCES "actor" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"createdAt" timestamp WITH time zone NOT NULL,
|
||||
"updatedAt" timestamp WITH time zone NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
`
|
||||
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
{
|
||||
for (const column of [ 'playlistUrl', 'segmentsSha256Url' ]) {
|
||||
const data = {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true,
|
||||
defaultValue: null
|
||||
}
|
||||
|
||||
await utils.queryInterface.changeColumn('videoStreamingPlaylist', column, data)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
await utils.sequelize.query(
|
||||
`UPDATE "videoStreamingPlaylist" SET "playlistUrl" = NULL, "segmentsSha256Url" = NULL ` +
|
||||
`WHERE "videoId" IN (SELECT id FROM video WHERE remote IS FALSE)`
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
for (const column of [ 'playlistFilename', 'segmentsSha256Filename' ]) {
|
||||
const data = {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true,
|
||||
defaultValue: null
|
||||
}
|
||||
|
||||
await utils.queryInterface.addColumn('videoStreamingPlaylist', column, data)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
await utils.sequelize.query(
|
||||
`UPDATE "videoStreamingPlaylist" SET "playlistFilename" = 'master.m3u8', "segmentsSha256Filename" = 'segments-sha256.json'`
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
for (const column of [ 'playlistFilename', 'segmentsSha256Filename' ]) {
|
||||
const data = {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: false,
|
||||
defaultValue: null
|
||||
}
|
||||
|
||||
await utils.queryInterface.changeColumn('videoStreamingPlaylist', column, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
56
server/core/initializers/migrations/0660-object-storage.ts
Normal file
56
server/core/initializers/migrations/0660-object-storage.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
import { FileStorage } from '@peertube/peertube-models'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
{
|
||||
const query = `
|
||||
CREATE TABLE IF NOT EXISTS "videoJobInfo" (
|
||||
"id" serial,
|
||||
"pendingMove" INTEGER NOT NULL,
|
||||
"pendingTranscode" INTEGER NOT NULL,
|
||||
"videoId" serial UNIQUE NOT NULL REFERENCES "video" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"createdAt" timestamp WITH time zone NOT NULL,
|
||||
"updatedAt" timestamp WITH time zone NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
`
|
||||
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('videoFile', 'storage', {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
defaultValue: FileStorage.FILE_SYSTEM
|
||||
})
|
||||
await utils.queryInterface.changeColumn('videoFile', 'storage', { type: Sequelize.INTEGER, allowNull: false, defaultValue: null })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('videoStreamingPlaylist', 'storage', {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
defaultValue: FileStorage.FILE_SYSTEM
|
||||
})
|
||||
await utils.queryInterface.changeColumn('videoStreamingPlaylist', 'storage', {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: null
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false
|
||||
}
|
||||
|
||||
await utils.queryInterface.addColumn('user', 'noAccountSetupWarningModal', data)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
for (const column of [ 'pendingMove', 'pendingTranscode' ]) {
|
||||
const data = {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
}
|
||||
|
||||
await utils.queryInterface.changeColumn('videoJobInfo', column, data)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
21
server/core/initializers/migrations/0675-p2p-enabled.ts
Normal file
21
server/core/initializers/migrations/0675-p2p-enabled.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
await utils.queryInterface.renameColumn('user', 'webTorrentEnabled', 'p2pEnabled')
|
||||
|
||||
await utils.sequelize.query('ALTER TABLE "user" ALTER COLUMN "p2pEnabled" DROP DEFAULT')
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
await utils.sequelize.query('ALTER TABLE "videoFile" ALTER COLUMN "storage" SET DEFAULT 0')
|
||||
await utils.sequelize.query('ALTER TABLE "videoStreamingPlaylist" ALTER COLUMN "storage" SET DEFAULT 0')
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
{
|
||||
await utils.queryInterface.addColumn('actorImage', 'actorId', {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'actor',
|
||||
key: 'id'
|
||||
},
|
||||
onDelete: 'CASCADE'
|
||||
}, { transaction: utils.transaction })
|
||||
|
||||
// Avatars
|
||||
{
|
||||
const query = `UPDATE "actorImage" SET "actorId" = (SELECT "id" FROM "actor" WHERE "actor"."avatarId" = "actorImage"."id") ` +
|
||||
`WHERE "type" = 1`
|
||||
await utils.sequelize.query(query, { type: Sequelize.QueryTypes.UPDATE, transaction: utils.transaction })
|
||||
}
|
||||
|
||||
// Banners
|
||||
{
|
||||
const query = `UPDATE "actorImage" SET "actorId" = (SELECT "id" FROM "actor" WHERE "actor"."bannerId" = "actorImage"."id") ` +
|
||||
`WHERE "type" = 2`
|
||||
await utils.sequelize.query(query, { type: Sequelize.QueryTypes.UPDATE, transaction: utils.transaction })
|
||||
}
|
||||
|
||||
// Remove orphans
|
||||
{
|
||||
const query = `DELETE FROM "actorImage" WHERE id NOT IN (` +
|
||||
`SELECT "bannerId" FROM actor WHERE "bannerId" IS NOT NULL ` +
|
||||
`UNION select "avatarId" FROM actor WHERE "avatarId" IS NOT NULL` +
|
||||
`);`
|
||||
|
||||
await utils.sequelize.query(query, { type: Sequelize.QueryTypes.DELETE, transaction: utils.transaction })
|
||||
}
|
||||
|
||||
await utils.queryInterface.changeColumn('actorImage', 'actorId', {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false
|
||||
}, { transaction: utils.transaction })
|
||||
|
||||
await utils.queryInterface.removeColumn('actor', 'avatarId', { transaction: utils.transaction })
|
||||
await utils.queryInterface.removeColumn('actor', 'bannerId', { transaction: utils.transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down () {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { LiveVideoLatencyMode } from '@peertube/peertube-models'
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
await utils.queryInterface.addColumn('videoLive', 'latencyMode', {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}, { transaction: utils.transaction })
|
||||
|
||||
{
|
||||
const query = `UPDATE "videoLive" SET "latencyMode" = ${LiveVideoLatencyMode.DEFAULT}`
|
||||
await utils.sequelize.query(query, { type: Sequelize.QueryTypes.UPDATE, transaction: utils.transaction })
|
||||
}
|
||||
|
||||
await utils.queryInterface.changeColumn('videoLive', 'latencyMode', {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: false
|
||||
}, { transaction: utils.transaction })
|
||||
}
|
||||
|
||||
function down () {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
const query = 'DELETE FROM "accountVideoRate" ' +
|
||||
'WHERE "accountVideoRate".id IN (' +
|
||||
'SELECT "accountVideoRate".id FROM "accountVideoRate" ' +
|
||||
'INNER JOIN account ON account.id = "accountVideoRate"."accountId" ' +
|
||||
'INNER JOIN actor ON actor.id = account."actorId" ' +
|
||||
'INNER JOIN video ON video.id = "accountVideoRate"."videoId" ' +
|
||||
'WHERE actor."serverId" IS NOT NULL AND video.remote IS TRUE' +
|
||||
')'
|
||||
|
||||
await utils.sequelize.query(query, { type: Sequelize.QueryTypes.BULKDELETE, transaction: utils.transaction })
|
||||
}
|
||||
|
||||
function down () {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
const { transaction } = utils
|
||||
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}
|
||||
await utils.queryInterface.addColumn('userNotificationSetting', 'myVideoStudioEditionFinished', data, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const query = 'UPDATE "userNotificationSetting" SET "myVideoStudioEditionFinished" = 1'
|
||||
await utils.sequelize.query(query, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: false
|
||||
}
|
||||
await utils.queryInterface.changeColumn('userNotificationSetting', 'myVideoStudioEditionFinished', data, { transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down () {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
const { transaction } = utils
|
||||
|
||||
{
|
||||
const query = `
|
||||
CREATE TABLE IF NOT EXISTS "localVideoViewer" (
|
||||
"id" serial,
|
||||
"startDate" timestamp with time zone NOT NULL,
|
||||
"endDate" timestamp with time zone NOT NULL,
|
||||
"watchTime" integer NOT NULL,
|
||||
"country" varchar(255),
|
||||
"uuid" uuid NOT NULL,
|
||||
"url" varchar(255) NOT NULL,
|
||||
"videoId" integer NOT NULL REFERENCES "video" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"createdAt" timestamp with time zone NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
`
|
||||
await utils.sequelize.query(query, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const query = `
|
||||
CREATE TABLE IF NOT EXISTS "localVideoViewerWatchSection" (
|
||||
"id" serial,
|
||||
"watchStart" integer NOT NULL,
|
||||
"watchEnd" integer NOT NULL,
|
||||
"localVideoViewerId" integer NOT NULL REFERENCES "localVideoViewer" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"createdAt" timestamp with time zone NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
`
|
||||
await utils.sequelize.query(query, { transaction })
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function down () {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
34
server/core/initializers/migrations/0710-live-sessions.ts
Normal file
34
server/core/initializers/migrations/0710-live-sessions.ts
Normal file
@@ -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
server/core/initializers/migrations/0715-video-source.ts
Normal file
34
server/core/initializers/migrations/0715-video-source.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
{
|
||||
const query = `
|
||||
CREATE TABLE IF NOT EXISTS "videoSource" (
|
||||
"id" SERIAL ,
|
||||
"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
"updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
"filename" VARCHAR(255) DEFAULT NULL,
|
||||
"videoId" INTEGER
|
||||
REFERENCES "video" ("id")
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
`
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
const { transaction } = utils
|
||||
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.BOOLEAN,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}
|
||||
await utils.queryInterface.addColumn('videoLiveSession', 'endingProcessed', data, { transaction })
|
||||
await utils.queryInterface.addColumn('videoLiveSession', 'saveReplay', data, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const query = `UPDATE "videoLiveSession" SET "saveReplay" = (
|
||||
SELECT "videoLive"."saveReplay" FROM "videoLive" WHERE "videoLive"."videoId" = "videoLiveSession"."liveVideoId"
|
||||
) WHERE "videoLiveSession"."liveVideoId" IS NOT NULL`
|
||||
await utils.sequelize.query(query, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const query = `UPDATE "videoLiveSession" SET "saveReplay" = FALSE WHERE "saveReplay" IS NULL`
|
||||
await utils.sequelize.query(query, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const query = `UPDATE "videoLiveSession" SET "endingProcessed" = TRUE`
|
||||
await utils.sequelize.query(query, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.BOOLEAN,
|
||||
defaultValue: null,
|
||||
allowNull: false
|
||||
}
|
||||
await utils.queryInterface.changeColumn('videoLiveSession', 'endingProcessed', data, { transaction })
|
||||
await utils.queryInterface.changeColumn('videoLiveSession', 'saveReplay', data, { transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
66
server/core/initializers/migrations/0725-node-version.ts
Normal file
66
server/core/initializers/migrations/0725-node-version.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
const { transaction } = utils
|
||||
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.STRING,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}
|
||||
await utils.queryInterface.addColumn('application', 'nodeVersion', data, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.STRING,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}
|
||||
await utils.queryInterface.addColumn('application', 'nodeABIVersion', data, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const query = `UPDATE "application" SET "nodeVersion" = '${process.version}'`
|
||||
await utils.sequelize.query(query, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const nodeABIVersion = parseInt(process.versions.modules)
|
||||
const query = `UPDATE "application" SET "nodeABIVersion" = ${nodeABIVersion}`
|
||||
await utils.sequelize.query(query, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.STRING,
|
||||
defaultValue: null,
|
||||
allowNull: false
|
||||
}
|
||||
await utils.queryInterface.changeColumn('application', 'nodeVersion', data, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.STRING,
|
||||
defaultValue: null,
|
||||
allowNull: false
|
||||
}
|
||||
await utils.queryInterface.changeColumn('application', 'nodeABIVersion', data, { transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
const query = `
|
||||
CREATE TABLE IF NOT EXISTS "videoChannelSync" (
|
||||
"id" SERIAL,
|
||||
"externalChannelUrl" VARCHAR(2000) NOT NULL DEFAULT NULL,
|
||||
"videoChannelId" INTEGER NOT NULL REFERENCES "videoChannel" ("id")
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE,
|
||||
"state" INTEGER NOT NULL DEFAULT 1,
|
||||
"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
"updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
"lastSyncAt" TIMESTAMP WITH TIME ZONE,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
`
|
||||
await utils.sequelize.query(query, { transaction: utils.transaction })
|
||||
}
|
||||
|
||||
async function down (utils: {
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
transaction: Sequelize.Transaction
|
||||
}) {
|
||||
await utils.queryInterface.dropTable('videoChannelSync', { transaction: utils.transaction })
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
await utils.queryInterface.addColumn('videoImport', 'videoChannelSyncId', {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'videoChannelSync',
|
||||
key: 'id'
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'SET NULL'
|
||||
}, { transaction: utils.transaction })
|
||||
}
|
||||
|
||||
async function down (utils: {
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
transaction: Sequelize.Transaction
|
||||
}) {
|
||||
await utils.queryInterface.dropTable('videoChannelSync', { transaction: utils.transaction })
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
33
server/core/initializers/migrations/0740-fix-old-enums.ts
Normal file
33
server/core/initializers/migrations/0740-fix-old-enums.ts
Normal file
@@ -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
server/core/initializers/migrations/0745-user-otp.ts
Normal file
29
server/core/initializers/migrations/0745-user-otp.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
const { transaction } = utils
|
||||
|
||||
const data = {
|
||||
type: Sequelize.STRING,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}
|
||||
await utils.queryInterface.addColumn('user', 'otpSecret', data, { transaction })
|
||||
|
||||
}
|
||||
|
||||
async function down (utils: {
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
transaction: Sequelize.Transaction
|
||||
}) {
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
{
|
||||
const query = `
|
||||
CREATE TABLE IF NOT EXISTS "userRegistration" (
|
||||
"id" serial,
|
||||
"state" integer NOT NULL,
|
||||
"registrationReason" text NOT NULL,
|
||||
"moderationResponse" text,
|
||||
"password" varchar(255),
|
||||
"username" varchar(255) NOT NULL,
|
||||
"email" varchar(400) NOT NULL,
|
||||
"emailVerified" boolean,
|
||||
"accountDisplayName" varchar(255),
|
||||
"channelHandle" varchar(255),
|
||||
"channelDisplayName" varchar(255),
|
||||
"userId" integer REFERENCES "user" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
"createdAt" timestamp with time zone NOT NULL,
|
||||
"updatedAt" timestamp with time zone NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
`
|
||||
await utils.sequelize.query(query, { transaction: utils.transaction })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('userNotification', 'userRegistrationId', {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'userRegistration',
|
||||
key: 'id'
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'SET NULL'
|
||||
}, { transaction: utils.transaction })
|
||||
}
|
||||
}
|
||||
|
||||
async function down (utils: {
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
transaction: Sequelize.Transaction
|
||||
}) {
|
||||
await utils.queryInterface.dropTable('videoChannelSync', { transaction: utils.transaction })
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
const { transaction } = utils
|
||||
|
||||
const query = 'DELETE FROM "localVideoViewer" t1 ' +
|
||||
'USING (SELECT MIN(id) as id, "url" FROM "localVideoViewer" GROUP BY "url" HAVING COUNT(*) > 1) t2 ' +
|
||||
'WHERE t1."url" = t2."url" AND t1.id <> t2.id'
|
||||
|
||||
await utils.sequelize.query(query, { transaction })
|
||||
}
|
||||
|
||||
async function down (utils: {
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
transaction: Sequelize.Transaction
|
||||
}) {
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
{
|
||||
const query = `
|
||||
CREATE TABLE IF NOT EXISTS "videoLiveReplaySetting" (
|
||||
"id" SERIAL ,
|
||||
"privacy" INTEGER NOT NULL,
|
||||
"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
"updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
`
|
||||
|
||||
await utils.sequelize.query(query, { transaction : utils.transaction })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('videoLive', 'replaySettingId', {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'videoLiveReplaySetting',
|
||||
key: 'id'
|
||||
},
|
||||
onDelete: 'SET NULL'
|
||||
}, { transaction: utils.transaction })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('videoLiveSession', 'replaySettingId', {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'videoLiveReplaySetting',
|
||||
key: 'id'
|
||||
},
|
||||
onDelete: 'SET NULL'
|
||||
}, { transaction: utils.transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const query = `
|
||||
SELECT live."id", v."privacy"
|
||||
FROM "videoLive" live
|
||||
INNER JOIN "video" v ON live."videoId" = v."id"
|
||||
WHERE live."saveReplay" = true
|
||||
`
|
||||
|
||||
const videoLives = await utils.sequelize.query<{ id: number, privacy: number }>(
|
||||
query,
|
||||
{ type: Sequelize.QueryTypes.SELECT, transaction: utils.transaction }
|
||||
)
|
||||
|
||||
for (const videoLive of videoLives) {
|
||||
const query = `
|
||||
WITH new_replay_setting AS (
|
||||
INSERT INTO "videoLiveReplaySetting" ("privacy", "createdAt", "updatedAt")
|
||||
VALUES (:privacy, NOW(), NOW())
|
||||
RETURNING id
|
||||
)
|
||||
UPDATE "videoLive" SET "replaySettingId" = (SELECT id FROM new_replay_setting)
|
||||
WHERE "id" = :id
|
||||
`
|
||||
|
||||
const options = {
|
||||
replacements: { privacy: videoLive.privacy, id: videoLive.id },
|
||||
type: Sequelize.QueryTypes.UPDATE,
|
||||
transaction: utils.transaction
|
||||
}
|
||||
|
||||
await utils.sequelize.query(query, options)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const query = `
|
||||
SELECT session."id", v."privacy"
|
||||
FROM "videoLiveSession" session
|
||||
INNER JOIN "video" v ON session."liveVideoId" = v."id"
|
||||
WHERE session."saveReplay" = true
|
||||
AND session."liveVideoId" IS NOT NULL;
|
||||
`
|
||||
|
||||
const videoLiveSessions = await utils.sequelize.query<{ id: number, privacy: number }>(
|
||||
query,
|
||||
{ type: Sequelize.QueryTypes.SELECT, transaction: utils.transaction }
|
||||
)
|
||||
|
||||
for (const videoLive of videoLiveSessions) {
|
||||
const query = `
|
||||
WITH new_replay_setting AS (
|
||||
INSERT INTO "videoLiveReplaySetting" ("privacy", "createdAt", "updatedAt")
|
||||
VALUES (:privacy, NOW(), NOW())
|
||||
RETURNING id
|
||||
)
|
||||
UPDATE "videoLiveSession" SET "replaySettingId" = (SELECT id FROM new_replay_setting)
|
||||
WHERE "id" = :id
|
||||
`
|
||||
|
||||
const options = {
|
||||
replacements: { privacy: videoLive.privacy, id: videoLive.id },
|
||||
type: Sequelize.QueryTypes.UPDATE,
|
||||
transaction: utils.transaction
|
||||
}
|
||||
|
||||
await utils.sequelize.query(query, options)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
{
|
||||
const query = `
|
||||
CREATE TABLE IF NOT EXISTS "runnerRegistrationToken"(
|
||||
"id" serial,
|
||||
"registrationToken" varchar(255) NOT NULL,
|
||||
"createdAt" timestamp with time zone NOT NULL,
|
||||
"updatedAt" timestamp with time zone NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
`
|
||||
|
||||
await utils.sequelize.query(query, { transaction : utils.transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const query = `
|
||||
CREATE TABLE IF NOT EXISTS "runner"(
|
||||
"id" serial,
|
||||
"runnerToken" varchar(255) NOT NULL,
|
||||
"name" varchar(255) NOT NULL,
|
||||
"description" varchar(1000),
|
||||
"lastContact" timestamp with time zone NOT NULL,
|
||||
"ip" varchar(255) NOT NULL,
|
||||
"runnerRegistrationTokenId" integer REFERENCES "runnerRegistrationToken"("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"createdAt" timestamp with time zone NOT NULL,
|
||||
"updatedAt" timestamp with time zone NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
`
|
||||
|
||||
await utils.sequelize.query(query, { transaction : utils.transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const query = `
|
||||
CREATE TABLE IF NOT EXISTS "runnerJob"(
|
||||
"id" serial,
|
||||
"uuid" uuid NOT NULL,
|
||||
"type" varchar(255) NOT NULL,
|
||||
"payload" jsonb NOT NULL,
|
||||
"privatePayload" jsonb NOT NULL,
|
||||
"state" integer NOT NULL,
|
||||
"failures" integer NOT NULL DEFAULT 0,
|
||||
"error" varchar(5000),
|
||||
"priority" integer NOT NULL,
|
||||
"processingJobToken" varchar(255),
|
||||
"progress" integer,
|
||||
"startedAt" timestamp with time zone,
|
||||
"finishedAt" timestamp with time zone,
|
||||
"dependsOnRunnerJobId" integer REFERENCES "runnerJob"("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"runnerId" integer REFERENCES "runner"("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
"createdAt" timestamp with time zone NOT NULL,
|
||||
"updatedAt" timestamp with time zone NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
|
||||
`
|
||||
|
||||
await utils.sequelize.query(query, { transaction : utils.transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
const { transaction } = utils
|
||||
|
||||
await utils.sequelize.query('drop index if exists "actor_preferred_username"', { transaction })
|
||||
await utils.sequelize.query('drop index if exists "actor_preferred_username_server_id"', { transaction })
|
||||
|
||||
await utils.sequelize.query(
|
||||
'DELETE FROM "actor" v1 USING (' +
|
||||
'SELECT MIN(id) as id, lower("preferredUsername") AS "lowerPreferredUsername", "serverId" ' +
|
||||
'FROM "actor" ' +
|
||||
'GROUP BY "lowerPreferredUsername", "serverId" HAVING COUNT(*) > 1 AND "serverId" IS NOT NULL' +
|
||||
') v2 ' +
|
||||
'WHERE lower(v1."preferredUsername") = v2."lowerPreferredUsername" AND v1."serverId" = v2."serverId" AND v1.id <> v2.id',
|
||||
{ transaction }
|
||||
)
|
||||
|
||||
await utils.sequelize.query(
|
||||
'DELETE FROM "actor" v1 USING (' +
|
||||
'SELECT MIN(id) as id, lower("preferredUsername") AS "lowerPreferredUsername", "serverId" ' +
|
||||
'FROM "actor" ' +
|
||||
'GROUP BY "lowerPreferredUsername", "serverId" HAVING COUNT(*) > 1 AND "serverId" IS NULL' +
|
||||
') v2 ' +
|
||||
'WHERE lower(v1."preferredUsername") = v2."lowerPreferredUsername" AND v1."serverId" IS NULL AND v1.id <> v2.id',
|
||||
{ transaction }
|
||||
)
|
||||
}
|
||||
|
||||
async function down (utils: {
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
transaction: Sequelize.Transaction
|
||||
}) {
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
|
||||
const data = {
|
||||
type: Sequelize.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false
|
||||
}
|
||||
|
||||
await utils.queryInterface.addColumn('user', 'emailPublic', data)
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
const { transaction } = utils
|
||||
|
||||
{
|
||||
await utils.sequelize.query('DELETE FROM "userNotification" WHERE type = 20 AND "userRegistrationId" IS NULL', { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.sequelize.query(
|
||||
'ALTER TABLE "userNotification" DROP CONSTRAINT "userNotification_userRegistrationId_fkey", ' +
|
||||
'ADD CONSTRAINT "userNotification_userRegistrationId_fkey" ' +
|
||||
'FOREIGN KEY ("userRegistrationId") REFERENCES "userRegistration" ("id") ON DELETE CASCADE ON UPDATE CASCADE',
|
||||
{ transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
{
|
||||
const query = `
|
||||
CREATE TABLE IF NOT EXISTS "videoPassword" (
|
||||
"id" SERIAL,
|
||||
"password" VARCHAR(255) NOT NULL,
|
||||
"videoId" INTEGER NOT NULL REFERENCES "video" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
"updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
`
|
||||
|
||||
await utils.sequelize.query(query, { transaction : utils.transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
47
server/core/initializers/migrations/0790-thumbnail-disk.ts
Normal file
47
server/core/initializers/migrations/0790-thumbnail-disk.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
const { transaction } = utils
|
||||
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.BOOLEAN,
|
||||
allowNull: true,
|
||||
defaultValue: true
|
||||
}
|
||||
|
||||
await utils.queryInterface.addColumn('thumbnail', 'onDisk', data, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
// Remote previews are not on the disk
|
||||
await utils.sequelize.query(
|
||||
'UPDATE "thumbnail" SET "onDisk" = FALSE ' +
|
||||
'WHERE "type" = 2 AND "videoId" NOT IN (SELECT "id" FROM "video" WHERE "remote" IS FALSE)',
|
||||
{ transaction }
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: null
|
||||
}
|
||||
|
||||
await utils.queryInterface.changeColumn('thumbnail', 'onDisk', data, { transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
const { transaction } = utils
|
||||
|
||||
const query = 'DELETE FROM "runner" r1 ' +
|
||||
'USING (SELECT MIN(id) as id, "name" FROM "runner" GROUP BY "name" HAVING COUNT(*) > 1) r2 ' +
|
||||
'WHERE r1."name" = r2."name" AND r1.id <> r2.id'
|
||||
|
||||
await utils.sequelize.query(query, { transaction })
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
const { transaction } = utils
|
||||
|
||||
{
|
||||
const query = 'DELETE FROM "videoSource" WHERE "videoId" IS NULL'
|
||||
await utils.sequelize.query(query, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const query = 'ALTER TABLE "videoSource" ALTER COLUMN "videoId" SET NOT NULL'
|
||||
await utils.sequelize.query(query, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.DATE,
|
||||
allowNull: true,
|
||||
defaultValue: null
|
||||
}
|
||||
|
||||
await utils.queryInterface.addColumn('video', 'inputFileUpdatedAt', data, { transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
const { transaction } = utils
|
||||
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true
|
||||
}
|
||||
|
||||
await utils.queryInterface.addColumn('localVideoViewer', 'subdivisionName', data, { transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
33
server/core/initializers/migrations/0810-user-export.ts
Normal file
33
server/core/initializers/migrations/0810-user-export.ts
Normal file
@@ -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
server/core/initializers/migrations/0815-user-import.ts
Normal file
31
server/core/initializers/migrations/0815-user-import.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
const query = `
|
||||
CREATE TABLE IF NOT EXISTS "userImport" (
|
||||
"id" SERIAL,
|
||||
"filename" VARCHAR(255),
|
||||
"state" INTEGER NOT NULL,
|
||||
"error" TEXT,
|
||||
"resultSummary" JSONB,
|
||||
"userId" INTEGER NOT NULL REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
"updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
);;`
|
||||
|
||||
await utils.sequelize.query(query, { transaction: utils.transaction })
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.DATE,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}
|
||||
await utils.queryInterface.addColumn('userRegistration', 'processedAt', data)
|
||||
}
|
||||
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.DATE,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}
|
||||
await utils.queryInterface.addColumn('abuse', 'processedAt', data)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
43
server/core/initializers/migrations/0825-video-ratio.ts
Normal file
43
server/core/initializers/migrations/0825-video-ratio.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}
|
||||
await utils.queryInterface.addColumn('videoFile', 'width', data)
|
||||
}
|
||||
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}
|
||||
await utils.queryInterface.addColumn('videoFile', 'height', data)
|
||||
}
|
||||
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.FLOAT,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}
|
||||
await utils.queryInterface.addColumn('video', 'aspectRatio', data)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
const { transaction } = utils
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('videoSource', 'keptOriginalFilename', {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true
|
||||
}, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('videoSource', 'storage', {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true
|
||||
}, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('videoSource', 'resolution', {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true
|
||||
}, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('videoSource', 'width', {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true
|
||||
}, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('videoSource', 'height', {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true
|
||||
}, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('videoSource', 'fps', {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true
|
||||
}, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('videoSource', 'size', {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true
|
||||
}, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('videoSource', 'metadata', {
|
||||
type: Sequelize.JSONB,
|
||||
allowNull: true
|
||||
}, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('videoSource', 'fileUrl', {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true
|
||||
}, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.renameColumn('videoSource', 'filename', 'inputFilename', { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('userExport', 'fileUrl', {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true
|
||||
}, { transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
down, up
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
const { transaction } = utils
|
||||
|
||||
{
|
||||
await utils.queryInterface.changeColumn('videoSource', 'size', {
|
||||
type: Sequelize.BIGINT,
|
||||
allowNull: true
|
||||
}, { transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
down, up
|
||||
}
|
||||
24
server/core/initializers/migrations/0840-user-export-size.ts
Normal file
24
server/core/initializers/migrations/0840-user-export-size.ts
Normal file
@@ -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
server/core/initializers/migrations/0845-auto-tags.ts
Normal file
122
server/core/initializers/migrations/0845-auto-tags.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
const { transaction } = utils
|
||||
|
||||
{
|
||||
const query = `CREATE TABLE IF NOT EXISTS "automaticTag" ("id" SERIAL , "name" VARCHAR(255) NOT NULL, PRIMARY KEY ("id"));`
|
||||
|
||||
await utils.sequelize.query(query, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const query = `
|
||||
CREATE TABLE IF NOT EXISTS "videoAutomaticTag"(
|
||||
"videoId" integer REFERENCES "video"("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"automaticTagId" integer NOT NULL REFERENCES "automaticTag"("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"accountId" integer REFERENCES "account"("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"createdAt" timestamp with time zone NOT NULL,
|
||||
"updatedAt" timestamp with time zone NOT NULL,
|
||||
PRIMARY KEY ("videoId", "automaticTagId", "accountId")
|
||||
);`
|
||||
|
||||
await utils.sequelize.query(query, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const query = `
|
||||
CREATE TABLE IF NOT EXISTS "commentAutomaticTag"(
|
||||
"commentId" integer REFERENCES "videoComment"("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"automaticTagId" integer NOT NULL REFERENCES "automaticTag"("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"accountId" integer REFERENCES "account"("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"createdAt" timestamp with time zone NOT NULL,
|
||||
"updatedAt" timestamp with time zone NOT NULL,
|
||||
PRIMARY KEY ("commentId", "automaticTagId", "accountId")
|
||||
);`
|
||||
|
||||
await utils.sequelize.query(query, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const query = `
|
||||
CREATE TABLE IF NOT EXISTS "watchedWordsList"(
|
||||
"id" serial,
|
||||
"listName" varchar(255) NOT NULL,
|
||||
"words" varchar(255)[] NOT NULL,
|
||||
"accountId" integer NOT NULL REFERENCES "account"("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"createdAt" timestamp with time zone NOT NULL,
|
||||
"updatedAt" timestamp with time zone NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
);`
|
||||
|
||||
await utils.sequelize.query(query, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const query = `
|
||||
CREATE TABLE IF NOT EXISTS "accountAutomaticTagPolicy"(
|
||||
"id" serial,
|
||||
"policy" integer,
|
||||
"accountId" integer NOT NULL REFERENCES "account"("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"automaticTagId" integer NOT NULL REFERENCES "automaticTag"("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"createdAt" timestamp with time zone NOT NULL,
|
||||
"updatedAt" timestamp with time zone NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
);`
|
||||
|
||||
await utils.sequelize.query(query, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('video', 'commentsPolicy', {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: 1, // ENABLED
|
||||
allowNull: false
|
||||
}, { transaction })
|
||||
|
||||
const query = `UPDATE "video" SET "commentsPolicy" = 2 WHERE "commentsEnabled" IS FALSE` // Disabled
|
||||
await utils.sequelize.query(query, { transaction })
|
||||
|
||||
await utils.queryInterface.changeColumn('video', 'commentsPolicy', {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: false
|
||||
}, { transaction })
|
||||
|
||||
await utils.queryInterface.removeColumn('video', 'commentsEnabled', { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('videoComment', 'heldForReview', {
|
||||
type: Sequelize.BOOLEAN,
|
||||
defaultValue: false,
|
||||
allowNull: false
|
||||
}, { transaction })
|
||||
|
||||
await utils.queryInterface.changeColumn('videoComment', 'heldForReview', {
|
||||
type: Sequelize.BOOLEAN,
|
||||
defaultValue: null,
|
||||
allowNull: false
|
||||
}, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('videoComment', 'replyApproval', {
|
||||
type: Sequelize.STRING,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}, { transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
down, up
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
const { transaction } = utils
|
||||
|
||||
{
|
||||
await utils.queryInterface.changeColumn('videoStreamingPlaylist', 'segmentsSha256Filename', {
|
||||
type: Sequelize.STRING,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}, { transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
down, up
|
||||
}
|
||||
67
server/core/initializers/migrations/0855-transcription.ts
Normal file
67
server/core/initializers/migrations/0855-transcription.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
const { transaction } = utils
|
||||
|
||||
// Notification
|
||||
{
|
||||
await utils.queryInterface.addColumn('userNotification', 'videoCaptionId', {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: 'videoCaption',
|
||||
key: 'id'
|
||||
},
|
||||
onUpdate: 'CASCADE',
|
||||
onDelete: 'CASCADE'
|
||||
}, { transaction })
|
||||
}
|
||||
|
||||
// Notification settings
|
||||
{
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}
|
||||
await utils.queryInterface.addColumn('userNotificationSetting', 'myVideoTranscriptionGenerated', data, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const query = 'UPDATE "userNotificationSetting" SET "myVideoTranscriptionGenerated" = 1'
|
||||
await utils.sequelize.query(query, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: false
|
||||
}
|
||||
await utils.queryInterface.changeColumn('userNotificationSetting', 'myVideoTranscriptionGenerated', data, { transaction })
|
||||
}
|
||||
}
|
||||
|
||||
// Video job info
|
||||
{
|
||||
await utils.queryInterface.addColumn('videoJobInfo', 'pendingTranscription', {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
}, { transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
down, up
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
const { transaction } = utils
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('videoCaption', 'automaticallyGenerated', {
|
||||
type: Sequelize.BOOLEAN,
|
||||
defaultValue: false,
|
||||
allowNull: false
|
||||
}, { transaction })
|
||||
|
||||
await utils.queryInterface.changeColumn('videoCaption', 'automaticallyGenerated', {
|
||||
type: Sequelize.BOOLEAN,
|
||||
defaultValue: null,
|
||||
allowNull: false
|
||||
}, { transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
down, up
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
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('DROP INDEX IF EXISTS "video_file_video_id"')
|
||||
await utils.sequelize.query('DROP INDEX IF EXISTS "video_file_video_streaming_playlist_id"')
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('videoFile', 'formatFlags', {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: 2, // fragmented
|
||||
allowNull: false
|
||||
}, { transaction })
|
||||
|
||||
// Web videos will be updated in the migration script because the query can be slow
|
||||
|
||||
await utils.queryInterface.changeColumn('videoFile', 'formatFlags', {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: false
|
||||
}, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('videoFile', 'streams', {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: 3, // audio + video
|
||||
allowNull: false
|
||||
}, { transaction })
|
||||
|
||||
// Case where there is only an audio stream
|
||||
const query = 'UPDATE "videoFile" SET "streams" = 2 WHERE "resolution" = 0'
|
||||
await utils.sequelize.query(query, { transaction })
|
||||
|
||||
await utils.queryInterface.changeColumn('videoFile', 'streams', {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: false
|
||||
}, { transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
down, up
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { logger } from '@server/helpers/logger.js'
|
||||
import { VideoFileModel } from '@server/models/video/video-file.js'
|
||||
import { remove } from 'fs-extra'
|
||||
import { join } from 'path'
|
||||
import * as Sequelize from 'sequelize'
|
||||
import { QueryTypes } from 'sequelize'
|
||||
import { CONFIG } from '../config.js'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
const { transaction } = utils
|
||||
|
||||
{
|
||||
const query = 'SELECT "videoFileId" FROM "videoRedundancy" WHERE "strategy" IS NOT NULL AND "videoFileId" IS NOT NULL'
|
||||
|
||||
const rows = await utils.sequelize.query<{ videoFileId: number }>(query, {
|
||||
transaction,
|
||||
type: QueryTypes.SELECT as QueryTypes.SELECT
|
||||
})
|
||||
|
||||
for (const { videoFileId } of rows) {
|
||||
try {
|
||||
const videoFile = await VideoFileModel.loadWithVideo(videoFileId, transaction)
|
||||
const filePath = join(CONFIG.STORAGE.REDUNDANCY_DIR, videoFile.filename)
|
||||
|
||||
await remove(filePath)
|
||||
} catch (err) {
|
||||
logger.error(`Cannot delete redundancy file (videoFileId: ${videoFileId}).`, { err })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const query = 'DELETE FROM "videoRedundancy" WHERE "videoFileId" IS NOT NULL'
|
||||
|
||||
await utils.sequelize.query(query, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.sequelize.query('DROP INDEX IF EXISTS video_redundancy_video_file_id', { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.removeColumn('videoRedundancy', 'videoFileId', { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: false
|
||||
}
|
||||
|
||||
await utils.queryInterface.changeColumn('videoRedundancy', 'videoStreamingPlaylistId', data, { transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
down, up
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { FileStorage } from '@peertube/peertube-models'
|
||||
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', 'storage', {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true,
|
||||
defaultValue: FileStorage.FILE_SYSTEM
|
||||
}, { transaction })
|
||||
|
||||
await utils.queryInterface.changeColumn('videoCaption', 'storage', {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: null
|
||||
}, { transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
down, up
|
||||
}
|
||||
@@ -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 { transaction } = utils
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('video', 'comments', {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: 0,
|
||||
allowNull: false
|
||||
}, { transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
down,
|
||||
up
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
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('localVideoViewer', 'client', {
|
||||
type: Sequelize.STRING,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('localVideoViewer', 'device', {
|
||||
type: Sequelize.STRING,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('localVideoViewer', 'operatingSystem', {
|
||||
type: Sequelize.STRING,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}, { transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
down,
|
||||
up
|
||||
}
|
||||
34
server/core/initializers/migrations/0890-hls-caption.ts
Normal file
34
server/core/initializers/migrations/0890-hls-caption.ts
Normal file
@@ -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 { transaction } = utils
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('videoCaption', 'm3u8Filename', {
|
||||
type: Sequelize.STRING,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('videoCaption', 'm3u8Url', {
|
||||
type: Sequelize.STRING,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}, { transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
down,
|
||||
up
|
||||
}
|
||||
58
server/core/initializers/migrations/0895-nsfw-flags.ts
Normal file
58
server/core/initializers/migrations/0895-nsfw-flags.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
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('video', 'nsfwSummary', {
|
||||
type: Sequelize.STRING,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}, { transaction })
|
||||
|
||||
await utils.queryInterface.addColumn('video', 'nsfwFlags', {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: 0,
|
||||
allowNull: false
|
||||
}, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('user', 'nsfwFlagsDisplayed', {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: 0,
|
||||
allowNull: false
|
||||
}, { transaction })
|
||||
|
||||
await utils.queryInterface.addColumn('user', 'nsfwFlagsHidden', {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: 0,
|
||||
allowNull: false
|
||||
}, { transaction })
|
||||
|
||||
await utils.queryInterface.addColumn('user', 'nsfwFlagsWarned', {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: 0,
|
||||
allowNull: false
|
||||
}, { transaction })
|
||||
|
||||
await utils.queryInterface.addColumn('user', 'nsfwFlagsBlurred', {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: 0,
|
||||
allowNull: false
|
||||
}, { transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
down,
|
||||
up
|
||||
}
|
||||
31
server/core/initializers/migrations/0900-uploads.ts
Normal file
31
server/core/initializers/migrations/0900-uploads.ts
Normal file
@@ -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 "uploadImage"(
|
||||
"id" serial,
|
||||
"filename" varchar(255) NOT NULL,
|
||||
"height" integer DEFAULT NULL,
|
||||
"width" integer DEFAULT NULL,
|
||||
"fileUrl" varchar(255),
|
||||
"type" integer 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, { transaction: utils.transaction })
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
const { transaction } = utils
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('videoPlaylist', 'videoChannelPosition', {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}, { transaction })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.sequelize.query(
|
||||
`UPDATE "videoPlaylist" SET "videoChannelPosition" = tmp.position FROM (` +
|
||||
`SELECT tmp.*, ROW_NUMBER () ` +
|
||||
`OVER (PARTITION BY "videoChannelId" ORDER BY "updatedAt" DESC) AS "position" from "videoPlaylist" "tmp" ` +
|
||||
`WHERE "tmp"."videoChannelPosition" IS NULL AND "tmp"."videoChannelId" IS NOT NULL` +
|
||||
`) tmp ` +
|
||||
`WHERE "videoPlaylist"."id" = tmp.id`,
|
||||
{ transaction }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
26
server/core/initializers/migrations/0910-user-language.ts
Normal file
26
server/core/initializers/migrations/0910-user-language.ts
Normal file
@@ -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 { transaction } = utils
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('user', 'language', {
|
||||
type: Sequelize.STRING,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}, { transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
down,
|
||||
up
|
||||
}
|
||||
26
server/core/initializers/migrations/0915-runner-version.ts
Normal file
26
server/core/initializers/migrations/0915-runner-version.ts
Normal file
@@ -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 { transaction } = utils
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('runner', 'version', {
|
||||
type: Sequelize.STRING,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}, { transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
down,
|
||||
up
|
||||
}
|
||||
46
server/core/initializers/migrations/0920-token-sessions.ts
Normal file
46
server/core/initializers/migrations/0920-token-sessions.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
const { transaction } = utils
|
||||
|
||||
const stringColumns = [
|
||||
'loginDevice',
|
||||
'loginIP',
|
||||
'lastActivityDevice',
|
||||
'lastActivityIP'
|
||||
]
|
||||
|
||||
for (const c of stringColumns) {
|
||||
await utils.queryInterface.addColumn('oAuthToken', c, {
|
||||
type: Sequelize.STRING,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}, { transaction })
|
||||
}
|
||||
|
||||
const dateColumns = [
|
||||
'loginDate',
|
||||
'lastActivityDate'
|
||||
]
|
||||
|
||||
for (const c of dateColumns) {
|
||||
await utils.queryInterface.addColumn('oAuthToken', c, {
|
||||
type: Sequelize.DATE,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}, { transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
down,
|
||||
up
|
||||
}
|
||||
24
server/core/initializers/migrations/0925-live-schedule-at.ts
Normal file
24
server/core/initializers/migrations/0925-live-schedule-at.ts
Normal file
@@ -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 =
|
||||
// eslint-disable-next-line max-len
|
||||
`CREATE TABLE IF NOT EXISTS "videoLiveSchedule" ("id" SERIAL , "startAt" TIMESTAMP WITH TIME ZONE NOT NULL, "liveVideoId" INTEGER REFERENCES "videoLive" ("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 })
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
28
server/core/initializers/migrations/0930-player-settings.ts
Normal file
28
server/core/initializers/migrations/0930-player-settings.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
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 "playerSetting" (
|
||||
"id" SERIAL,
|
||||
"theme" VARCHAR(255) NOT NULL DEFAULT 'instance-default',
|
||||
"videoId" INTEGER REFERENCES "video" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"channelId" INTEGER REFERENCES "videoChannel" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
"updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
);`
|
||||
|
||||
await utils.sequelize.query(query, { transaction: utils.transaction })
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
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 "videoChannelCollaborator" (
|
||||
"id" SERIAL,
|
||||
"state" INTEGER NOT NULL,
|
||||
"accountId" INTEGER REFERENCES "account" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"channelId" INTEGER NOT NULL REFERENCES "videoChannel" ("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 metadata = {
|
||||
type: Sequelize.JSONB,
|
||||
allowNull: true
|
||||
}
|
||||
await utils.queryInterface.addColumn('userNotification', 'data', metadata, { transaction: utils.transaction })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.sequelize.query(
|
||||
`ALTER TABLE "userNotification" ` +
|
||||
`ADD COLUMN "channelCollaboratorId" INTEGER REFERENCES "videoChannelCollaborator" ("id") ON DELETE CASCADE ON UPDATE CASCADE`,
|
||||
{ transaction: utils.transaction }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
{
|
||||
await utils.sequelize.query(
|
||||
`ALTER TABLE "application" ALTER COLUMN "nodeABIVersion" TYPE VARCHAR(255)`,
|
||||
{ transaction: utils.transaction }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
down,
|
||||
up
|
||||
}
|
||||
48
server/core/initializers/migrations/0945-channel-activity.ts
Normal file
48
server/core/initializers/migrations/0945-channel-activity.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
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 "videoChannelActivity" (
|
||||
"id" SERIAL,
|
||||
"action" INTEGER NOT NULL,
|
||||
"targetType" INTEGER NOT NULL,
|
||||
"data" JSONB NOT NULL,
|
||||
"details" JSONB,
|
||||
"videoChannelId" INTEGER NOT NULL REFERENCES "videoChannel" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
"accountId" INTEGER REFERENCES "account" ("id") ON DELETE
|
||||
SET
|
||||
NULL ON UPDATE CASCADE,
|
||||
"videoId" INTEGER REFERENCES "video" ("id") ON DELETE
|
||||
SET
|
||||
NULL ON UPDATE CASCADE,
|
||||
"videoPlaylistId" INTEGER REFERENCES "videoPlaylist" ("id") ON DELETE
|
||||
SET
|
||||
NULL ON UPDATE CASCADE,
|
||||
"videoChannelSyncId" INTEGER REFERENCES "videoChannelSync" ("id") ON DELETE
|
||||
SET
|
||||
NULL ON UPDATE CASCADE,
|
||||
"videoImportId" INTEGER REFERENCES "videoImport" ("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")
|
||||
);`,
|
||||
{ transaction: utils.transaction }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
down,
|
||||
up
|
||||
}
|
||||
31
server/core/initializers/migrations/0950-retry-import.ts
Normal file
31
server/core/initializers/migrations/0950-retry-import.ts
Normal file
@@ -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('videoImport', 'attempts', {
|
||||
type: Sequelize.DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
}, { transaction: utils.transaction })
|
||||
}
|
||||
|
||||
{
|
||||
await utils.queryInterface.addColumn('videoImport', 'payload', {
|
||||
type: Sequelize.DataTypes.JSONB,
|
||||
allowNull: true
|
||||
}, { transaction: utils.transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
down,
|
||||
up
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
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 "actorReserved" (
|
||||
"id" SERIAL,
|
||||
"preferredUsername" 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 = `ALTER TABLE "actor" ADD COLUMN IF NOT EXISTS "accountId" INTEGER ` +
|
||||
`REFERENCES "account" ("id") ON DELETE CASCADE ON UPDATE CASCADE`
|
||||
await utils.sequelize.query(query, { transaction: utils.transaction })
|
||||
}
|
||||
|
||||
{
|
||||
const query = `ALTER TABLE "actor" ADD COLUMN IF NOT EXISTS "videoChannelId" INTEGER ` +
|
||||
`REFERENCES "videoChannel" ("id") ON DELETE CASCADE ON UPDATE CASCADE`
|
||||
await utils.sequelize.query(query, { transaction: utils.transaction })
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
{
|
||||
await utils.sequelize.query(
|
||||
`
|
||||
INSERT INTO "actorReserved" ("preferredUsername", "createdAt", "updatedAt")
|
||||
SELECT "preferredUsername", NOW(), NOW()
|
||||
FROM "actor"
|
||||
WHERE "accountId" IS NULL AND "videoChannelId" IS NULL AND "serverId" IS NULL
|
||||
`,
|
||||
{ transaction: utils.transaction }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
down,
|
||||
up
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
import { QueryTypes } from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
const batchSize = 1000
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Do not use transaction on purpose, to speed up chunk updates
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Fill "actor"."accountId"
|
||||
while (true) {
|
||||
const query = `
|
||||
WITH batch AS (
|
||||
SELECT a."id" AS actor_id, ac."id" AS account_id
|
||||
FROM "actor" a INNER JOIN "account" ac ON ac."actorId" = a."id"
|
||||
WHERE a."accountId" IS DISTINCT FROM ac."id" ORDER BY a."id"
|
||||
LIMIT ${batchSize}
|
||||
) UPDATE "actor" SET "accountId" = batch.account_id FROM batch WHERE "actor"."id" = batch.actor_id`
|
||||
|
||||
const [ , rowsUpdated ] = await utils.sequelize.query(query, {
|
||||
type: QueryTypes.UPDATE,
|
||||
replacements: { limit: batchSize }
|
||||
})
|
||||
|
||||
if (rowsUpdated < batchSize) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Fill "actor"."videoChannelId"
|
||||
while (true) {
|
||||
const query = `
|
||||
WITH batch AS (
|
||||
SELECT a."id" AS actor_id, vc."id" AS channel_id
|
||||
FROM "actor" a INNER JOIN "videoChannel" vc ON vc."actorId" = a."id"
|
||||
WHERE a."videoChannelId" IS DISTINCT FROM vc."id" ORDER BY a."id"
|
||||
LIMIT ${batchSize}
|
||||
) UPDATE "actor" SET "videoChannelId" = batch.channel_id FROM batch WHERE "actor"."id" = batch.actor_id`
|
||||
|
||||
const [ , rowsUpdated ] = await utils.sequelize.query(query, {
|
||||
type: QueryTypes.UPDATE,
|
||||
replacements: { limit: batchSize }
|
||||
})
|
||||
|
||||
if (rowsUpdated < batchSize) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
{
|
||||
await utils.sequelize.query(`ALTER TABLE "account" DROP COLUMN "actorId"`, { transaction: utils.transaction })
|
||||
await utils.sequelize.query(`ALTER TABLE "videoChannel" DROP COLUMN "actorId"`, { transaction: utils.transaction })
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
down,
|
||||
up
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
}): Promise<void> {
|
||||
await utils.queryInterface.addColumn('user', 'newFeaturesInfoRead', {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: true
|
||||
}, { transaction: utils.transaction })
|
||||
|
||||
await utils.sequelize.query('UPDATE "user" SET "newFeaturesInfoRead" = 0', { transaction: utils.transaction })
|
||||
|
||||
await utils.queryInterface.changeColumn('user', 'newFeaturesInfoRead', {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false
|
||||
}, { transaction: utils.transaction })
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
down,
|
||||
up
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user