Upgrade sequelize

pull/1787/head
Chocobozzz 2019-04-23 09:50:57 +02:00
parent 1735c82572
commit 3acc508440
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
48 changed files with 457 additions and 466 deletions

2
.gitignore vendored
View File

@ -16,8 +16,8 @@
/config/production.yaml
/config/local*
/ffmpeg/
/ffmpeg-4/
/ffmpeg-3/
/ffmpeg-4/
/thumbnails/
/torrents/
/videos/

View File

@ -142,8 +142,8 @@
"reflect-metadata": "^0.1.12",
"request": "^2.81.0",
"scripty": "^1.5.0",
"sequelize": "5.6.1",
"sequelize-typescript": "^1.0.0-beta.1",
"sequelize": "5.7.4",
"sequelize-typescript": "1.0.0-beta.2",
"sharp": "^0.22.0",
"sitemap": "^2.1.0",
"socket.io": "^2.2.0",
@ -212,7 +212,7 @@
"ts-node": "8.0.3",
"tslint": "^5.7.0",
"tslint-config-standard": "^8.0.1",
"typescript": "^3.1.6",
"typescript": "^3.4.3",
"xliff": "^4.0.0"
},
"scripty": {

View File

@ -41,7 +41,7 @@ import { VideoPlaylistReorder } from '../../../shared/models/videos/playlist/vid
import { JobQueue } from '../../lib/job-queue'
import { CONFIG } from '../../initializers/config'
import { sequelizeTypescript } from '../../initializers/database'
import { createPlaylistThumbnailFromExisting } from '../../lib/thumbnail'
import { createPlaylistMiniatureFromExisting } from '../../lib/thumbnail'
const reqThumbnailFile = createReqFiles([ 'thumbnailfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { thumbnailfile: CONFIG.STORAGE.TMP_DIR })
@ -174,16 +174,13 @@ async function addVideoPlaylist (req: express.Request, res: express.Response) {
const thumbnailField = req.files['thumbnailfile']
const thumbnailModel = thumbnailField
? await createPlaylistThumbnailFromExisting(thumbnailField[0].path, videoPlaylist)
? await createPlaylistMiniatureFromExisting(thumbnailField[0].path, videoPlaylist)
: undefined
const videoPlaylistCreated: VideoPlaylistModel = await sequelizeTypescript.transaction(async t => {
const videoPlaylistCreated = await videoPlaylist.save({ transaction: t })
if (thumbnailModel) {
thumbnailModel.videoPlaylistId = videoPlaylistCreated.id
videoPlaylistCreated.setThumbnail(await thumbnailModel.save({ transaction: t }))
}
if (thumbnailModel) await videoPlaylistCreated.setAndSaveThumbnail(thumbnailModel, t)
// We need more attributes for the federation
videoPlaylistCreated.OwnerAccount = await AccountModel.load(user.Account.id, t)
@ -210,7 +207,7 @@ async function updateVideoPlaylist (req: express.Request, res: express.Response)
const thumbnailField = req.files['thumbnailfile']
const thumbnailModel = thumbnailField
? await createPlaylistThumbnailFromExisting(thumbnailField[0].path, videoPlaylistInstance)
? await createPlaylistMiniatureFromExisting(thumbnailField[0].path, videoPlaylistInstance)
: undefined
try {
@ -239,10 +236,7 @@ async function updateVideoPlaylist (req: express.Request, res: express.Response)
const playlistUpdated = await videoPlaylistInstance.save(sequelizeOptions)
if (thumbnailModel) {
thumbnailModel.videoPlaylistId = playlistUpdated.id
playlistUpdated.setThumbnail(await thumbnailModel.save({ transaction: t }))
}
if (thumbnailModel) await playlistUpdated.setAndSaveThumbnail(thumbnailModel, t)
const isNewPlaylist = wasPrivatePlaylist && playlistUpdated.privacy !== VideoPlaylistPrivacy.PRIVATE
@ -313,8 +307,8 @@ async function addVideoInPlaylist (req: express.Request, res: express.Response)
if (playlistElement.position === 1 && videoPlaylist.hasThumbnail() === false) {
logger.info('Generating default thumbnail to playlist %s.', videoPlaylist.url)
const inputPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnail().filename)
const thumbnailModel = await createPlaylistThumbnailFromExisting(inputPath, videoPlaylist, true)
const inputPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getMiniature().filename)
const thumbnailModel = await createPlaylistMiniatureFromExisting(inputPath, videoPlaylist, true)
thumbnailModel.videoPlaylistId = videoPlaylist.id

View File

@ -23,7 +23,7 @@ import { move, readFile } from 'fs-extra'
import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist'
import { CONFIG } from '../../../initializers/config'
import { sequelizeTypescript } from '../../../initializers/database'
import { createVideoThumbnailFromExisting } from '../../../lib/thumbnail'
import { createVideoMiniatureFromExisting } from '../../../lib/thumbnail'
import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type'
import { ThumbnailModel } from '../../../models/video/thumbnail'
@ -204,7 +204,7 @@ async function processThumbnail (req: express.Request, video: VideoModel) {
if (thumbnailField) {
const thumbnailPhysicalFile = thumbnailField[ 0 ]
return createVideoThumbnailFromExisting(thumbnailPhysicalFile.path, video, ThumbnailType.THUMBNAIL)
return createVideoMiniatureFromExisting(thumbnailPhysicalFile.path, video, ThumbnailType.MINIATURE)
}
return undefined
@ -215,7 +215,7 @@ async function processPreview (req: express.Request, video: VideoModel) {
if (previewField) {
const previewPhysicalFile = previewField[0]
return createVideoThumbnailFromExisting(previewPhysicalFile.path, video, ThumbnailType.PREVIEW)
return createVideoMiniatureFromExisting(previewPhysicalFile.path, video, ThumbnailType.PREVIEW)
}
return undefined
@ -238,14 +238,8 @@ function insertIntoDB (parameters: {
const videoCreated = await video.save(sequelizeOptions)
videoCreated.VideoChannel = videoChannel
if (thumbnailModel) {
thumbnailModel.videoId = videoCreated.id
videoCreated.addThumbnail(await thumbnailModel.save({ transaction: t }))
}
if (previewModel) {
previewModel.videoId = videoCreated.id
videoCreated.addThumbnail(await previewModel.save({ transaction: t }))
}
if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t)
if (previewModel) await videoCreated.addAndSaveThumbnail(previewModel, t)
await autoBlacklistVideoIfNeeded(video, videoChannel.Account.User, t)

View File

@ -52,7 +52,7 @@ import { Notifier } from '../../../lib/notifier'
import { sendView } from '../../../lib/activitypub/send/send-view'
import { CONFIG } from '../../../initializers/config'
import { sequelizeTypescript } from '../../../initializers/database'
import { createVideoThumbnailFromExisting, generateVideoThumbnail } from '../../../lib/thumbnail'
import { createVideoMiniatureFromExisting, generateVideoMiniature } from '../../../lib/thumbnail'
import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type'
const auditLogger = auditLoggerFactory('videos')
@ -214,14 +214,14 @@ async function addVideo (req: express.Request, res: express.Response) {
// Process thumbnail or create it from the video
const thumbnailField = req.files['thumbnailfile']
const thumbnailModel = thumbnailField
? await createVideoThumbnailFromExisting(thumbnailField[0].path, video, ThumbnailType.THUMBNAIL)
: await generateVideoThumbnail(video, videoFile, ThumbnailType.THUMBNAIL)
? await createVideoMiniatureFromExisting(thumbnailField[0].path, video, ThumbnailType.MINIATURE)
: await generateVideoMiniature(video, videoFile, ThumbnailType.MINIATURE)
// Process preview or create it from the video
const previewField = req.files['previewfile']
const previewModel = previewField
? await createVideoThumbnailFromExisting(previewField[0].path, video, ThumbnailType.PREVIEW)
: await generateVideoThumbnail(video, videoFile, ThumbnailType.PREVIEW)
? await createVideoMiniatureFromExisting(previewField[0].path, video, ThumbnailType.PREVIEW)
: await generateVideoMiniature(video, videoFile, ThumbnailType.PREVIEW)
// Create the torrent file
await video.createTorrentAndSetInfoHash(videoFile)
@ -231,11 +231,8 @@ async function addVideo (req: express.Request, res: express.Response) {
const videoCreated = await video.save(sequelizeOptions)
thumbnailModel.videoId = videoCreated.id
previewModel.videoId = videoCreated.id
videoCreated.addThumbnail(await thumbnailModel.save({ transaction: t }))
videoCreated.addThumbnail(await previewModel.save({ transaction: t }))
await videoCreated.addAndSaveThumbnail(thumbnailModel, t)
await videoCreated.addAndSaveThumbnail(previewModel, t)
// Do not forget to add video channel information to the created video
videoCreated.VideoChannel = res.locals.videoChannel
@ -308,11 +305,11 @@ async function updateVideo (req: express.Request, res: express.Response) {
// Process thumbnail or create it from the video
const thumbnailModel = req.files && req.files['thumbnailfile']
? await createVideoThumbnailFromExisting(req.files['thumbnailfile'][0].path, videoInstance, ThumbnailType.THUMBNAIL)
? await createVideoMiniatureFromExisting(req.files['thumbnailfile'][0].path, videoInstance, ThumbnailType.MINIATURE)
: undefined
const previewModel = req.files && req.files['previewfile']
? await createVideoThumbnailFromExisting(req.files['previewfile'][0].path, videoInstance, ThumbnailType.PREVIEW)
? await createVideoMiniatureFromExisting(req.files['previewfile'][0].path, videoInstance, ThumbnailType.PREVIEW)
: undefined
try {
@ -346,14 +343,8 @@ async function updateVideo (req: express.Request, res: express.Response) {
const videoInstanceUpdated = await videoInstance.save(sequelizeOptions)
if (thumbnailModel) {
thumbnailModel.videoId = videoInstanceUpdated.id
videoInstanceUpdated.addThumbnail(await thumbnailModel.save({ transaction: t }))
}
if (previewModel) {
previewModel.videoId = videoInstanceUpdated.id
videoInstanceUpdated.addThumbnail(await previewModel.save({ transaction: t }))
}
if (thumbnailModel) await videoInstanceUpdated.addAndSaveThumbnail(thumbnailModel, t)
if (previewModel) await videoInstanceUpdated.addAndSaveThumbnail(previewModel, t)
// Video tags update?
if (videoInfoToUpdate.tags !== undefined) {

View File

@ -85,7 +85,7 @@ async function getSitemapLocalVideoUrls () {
// Sitemap description should be < 2000 characters
description: truncate(v.description || v.name, { length: 2000, omission: '...' }),
player_loc: WEBSERVER.URL + '/videos/embed/' + v.uuid,
thumbnail_loc: WEBSERVER.URL + v.getThumbnailStaticPath()
thumbnail_loc: WEBSERVER.URL + v.getMiniatureStaticPath()
}
]
}))

View File

@ -137,7 +137,7 @@ async function generateVideoFeed (req: express.Request, res: express.Response) {
torrent: torrents,
thumbnail: [
{
url: WEBSERVER.URL + video.getThumbnailStaticPath(),
url: WEBSERVER.URL + video.getMiniatureStaticPath(),
height: THUMBNAILS_SIZE.height,
width: THUMBNAILS_SIZE.width
}

View File

@ -165,20 +165,20 @@ export {
// ---------------------------------------------------------------------------
async function getPreview (req: express.Request, res: express.Response) {
const path = await VideosPreviewCache.Instance.getFilePath(req.params.uuid)
if (!path) return res.sendStatus(404)
const result = await VideosPreviewCache.Instance.getFilePath(req.params.uuid)
if (!result) return res.sendStatus(404)
return res.sendFile(path, { maxAge: STATIC_MAX_AGE })
return res.sendFile(result.path, { maxAge: STATIC_MAX_AGE })
}
async function getVideoCaption (req: express.Request, res: express.Response) {
const path = await VideosCaptionCache.Instance.getFilePath({
const result = await VideosCaptionCache.Instance.getFilePath({
videoId: req.params.videoId,
language: req.params.captionLanguage
})
if (!path) return res.sendStatus(404)
if (!result) return res.sendStatus(404)
return res.sendFile(path, { maxAge: STATIC_MAX_AGE })
return res.sendFile(result.path, { maxAge: STATIC_MAX_AGE })
}
async function generateNodeinfo (req: express.Request, res: express.Response, next: express.NextFunction) {

View File

@ -140,15 +140,15 @@ async function checkPostgresExtensions () {
}
async function checkPostgresExtension (extension: string) {
const query = `SELECT true AS enabled FROM pg_available_extensions WHERE name = '${extension}' AND installed_version IS NOT NULL;`
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<{ enabled: boolean }>(query, options)
const res = await sequelizeTypescript.query<object>(query, options)
if (!res || res.length === 0 || res[ 0 ][ 'enabled' ] !== true) {
if (!res || res.length === 0) {
// Try to create the extension ourselves
try {
await sequelizeTypescript.query(`CREATE EXTENSION ${extension};`, { raw: true })

View File

@ -16,7 +16,8 @@ import { VideoPlaylistElementModel } from '../../models/video/video-playlist-ele
import { VideoModel } from '../../models/video/video'
import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model'
import { sequelizeTypescript } from '../../initializers/database'
import { createPlaylistThumbnailFromUrl } from '../thumbnail'
import { createPlaylistMiniatureFromUrl } from '../thumbnail'
import { FilteredModelAttributes } from '../../typings/sequelize'
function playlistObjectToDBAttributes (playlistObject: PlaylistObject, byAccount: AccountModel, to: string[]) {
const privacy = to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 ? VideoPlaylistPrivacy.PUBLIC : VideoPlaylistPrivacy.UNLISTED
@ -86,8 +87,7 @@ async function createOrUpdateVideoPlaylist (playlistObject: PlaylistObject, byAc
}
}
// FIXME: sequelize typings
const [ playlist ] = (await VideoPlaylistModel.upsert<VideoPlaylistModel>(playlistAttributes, { returning: true }) as any)
const [ playlist ] = await VideoPlaylistModel.upsert<VideoPlaylistModel>(playlistAttributes, { returning: true })
let accItems: string[] = []
await crawlCollectionPage<string>(playlistObject.id, items => {
@ -100,10 +100,8 @@ async function createOrUpdateVideoPlaylist (playlistObject: PlaylistObject, byAc
if (playlistObject.icon) {
try {
const thumbnailModel = await createPlaylistThumbnailFromUrl(playlistObject.icon.url, refreshedPlaylist)
thumbnailModel.videoPlaylistId = refreshedPlaylist.id
refreshedPlaylist.setThumbnail(await thumbnailModel.save())
const thumbnailModel = await createPlaylistMiniatureFromUrl(playlistObject.icon.url, refreshedPlaylist)
await refreshedPlaylist.setAndSaveThumbnail(thumbnailModel, undefined)
} catch (err) {
logger.warn('Cannot generate thumbnail of %s.', playlistObject.id, { err })
}
@ -156,7 +154,7 @@ export {
// ---------------------------------------------------------------------------
async function resetVideoPlaylistElements (elementUrls: string[], playlist: VideoPlaylistModel) {
const elementsToCreate: object[] = [] // FIXME: sequelize typings
const elementsToCreate: FilteredModelAttributes<VideoPlaylistElementModel>[] = []
await Bluebird.map(elementUrls, async elementUrl => {
try {

View File

@ -73,8 +73,7 @@ async function addVideoComment (videoInstance: VideoModel, commentUrl: string) {
const entry = await videoCommentActivityObjectToDBAttributes(videoInstance, actor, body)
if (!entry) return { created: false }
// FIXME: sequelize typings
const [ comment, created ] = (await VideoCommentModel.upsert<VideoCommentModel>(entry, { returning: true }) as any)
const [ comment, created ] = await VideoCommentModel.upsert<VideoCommentModel>(entry, { returning: true })
comment.Account = actor.Account
comment.Video = videoInstance

View File

@ -49,10 +49,11 @@ import { AccountVideoRateModel } from '../../models/account/account-video-rate'
import { VideoShareModel } from '../../models/video/video-share'
import { VideoCommentModel } from '../../models/video/video-comment'
import { sequelizeTypescript } from '../../initializers/database'
import { createPlaceholderThumbnail, createVideoThumbnailFromUrl } from '../thumbnail'
import { createPlaceholderThumbnail, createVideoMiniatureFromUrl } from '../thumbnail'
import { ThumbnailModel } from '../../models/video/thumbnail'
import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type'
import { join } from 'path'
import { FilteredModelAttributes } from '../../typings/sequelize'
async function federateVideoIfNeeded (video: VideoModel, isNewVideo: boolean, transaction?: sequelize.Transaction) {
// If the video is not private and is published, we federate it
@ -247,7 +248,7 @@ async function updateVideoFromAP (options: {
let thumbnailModel: ThumbnailModel
try {
thumbnailModel = await createVideoThumbnailFromUrl(options.videoObject.icon.url, options.video, ThumbnailType.THUMBNAIL)
thumbnailModel = await createVideoMiniatureFromUrl(options.videoObject.icon.url, options.video, ThumbnailType.MINIATURE)
} catch (err) {
logger.warn('Cannot generate thumbnail of %s.', options.videoObject.id, { err })
}
@ -288,16 +289,12 @@ async function updateVideoFromAP (options: {
await options.video.save(sequelizeOptions)
if (thumbnailModel) {
thumbnailModel.videoId = options.video.id
options.video.addThumbnail(await thumbnailModel.save({ transaction: t }))
}
if (thumbnailModel) if (thumbnailModel) await options.video.addAndSaveThumbnail(thumbnailModel, t)
// FIXME: use icon URL instead
const previewUrl = buildRemoteBaseUrl(options.video, join(STATIC_PATHS.PREVIEWS, options.video.getPreview().filename))
const previewModel = createPlaceholderThumbnail(previewUrl, options.video, ThumbnailType.PREVIEW, PREVIEWS_SIZE)
options.video.addThumbnail(await previewModel.save({ transaction: t }))
await options.video.addAndSaveThumbnail(previewModel, t)
{
const videoFileAttributes = videoFileActivityUrlToDBAttributes(options.video, options.videoObject)
@ -311,7 +308,7 @@ async function updateVideoFromAP (options: {
// Update or add other one
const upsertTasks = videoFileAttributes.map(a => {
return (VideoFileModel.upsert<VideoFileModel>(a, { returning: true, transaction: t }) as any) // FIXME: sequelize typings
return VideoFileModel.upsert<VideoFileModel>(a, { returning: true, transaction: t })
.then(([ file ]) => file)
})
@ -334,8 +331,7 @@ async function updateVideoFromAP (options: {
// Update or add other one
const upsertTasks = streamingPlaylistAttributes.map(a => {
// FIXME: sequelize typings
return (VideoStreamingPlaylistModel.upsert<VideoStreamingPlaylistModel>(a, { returning: true, transaction: t }) as any)
return VideoStreamingPlaylistModel.upsert<VideoStreamingPlaylistModel>(a, { returning: true, transaction: t })
.then(([ streamingPlaylist ]) => streamingPlaylist)
})
@ -464,7 +460,7 @@ async function createVideo (videoObject: VideoTorrentObject, channelActor: Actor
const videoData = await videoActivityObjectToDBAttributes(channelActor.VideoChannel, videoObject, videoObject.to)
const video = VideoModel.build(videoData)
const promiseThumbnail = createVideoThumbnailFromUrl(videoObject.icon.url, video, ThumbnailType.THUMBNAIL)
const promiseThumbnail = createVideoMiniatureFromUrl(videoObject.icon.url, video, ThumbnailType.MINIATURE)
let thumbnailModel: ThumbnailModel
if (waitThumbnail === true) {
@ -477,18 +473,12 @@ async function createVideo (videoObject: VideoTorrentObject, channelActor: Actor
const videoCreated = await video.save(sequelizeOptions)
videoCreated.VideoChannel = channelActor.VideoChannel
if (thumbnailModel) {
thumbnailModel.videoId = videoCreated.id
videoCreated.addThumbnail(await thumbnailModel.save({ transaction: t }))
}
if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t)
// FIXME: use icon URL instead
const previewUrl = buildRemoteBaseUrl(videoCreated, join(STATIC_PATHS.PREVIEWS, video.generatePreviewName()))
const previewModel = createPlaceholderThumbnail(previewUrl, video, ThumbnailType.PREVIEW, PREVIEWS_SIZE)
previewModel.videoId = videoCreated.id
videoCreated.addThumbnail(await previewModel.save({ transaction: t }))
if (thumbnailModel) await videoCreated.addAndSaveThumbnail(previewModel, t)
// Process files
const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoCreated, videoObject)
@ -594,7 +584,7 @@ function videoFileActivityUrlToDBAttributes (video: VideoModel, videoObject: Vid
throw new Error('Cannot find video files for ' + video.url)
}
const attributes: object[] = [] // FIXME: add typings
const attributes: FilteredModelAttributes<VideoFileModel>[] = []
for (const fileUrl of fileUrls) {
// Fetch associated magnet uri
const magnet = videoObject.url.find(u => {
@ -629,7 +619,7 @@ function streamingPlaylistActivityUrlToDBAttributes (video: VideoModel, videoObj
const playlistUrls = videoObject.url.filter(u => isAPStreamingPlaylistUrlObject(u)) as ActivityPlaylistUrlObject[]
if (playlistUrls.length === 0) return []
const attributes: object[] = [] // FIXME: add typings
const attributes: FilteredModelAttributes<VideoStreamingPlaylistModel>[] = []
for (const playlistUrlObject of playlistUrls) {
const segmentsSha256UrlObject = playlistUrlObject.tag
.find(t => {

View File

@ -4,24 +4,28 @@ import { VideoModel } from '../../models/video/video'
import { fetchRemoteVideoStaticFile } from '../activitypub'
import * as memoizee from 'memoizee'
type GetFilePathResult = { isOwned: boolean, path: string } | undefined
export abstract class AbstractVideoStaticFileCache <T> {
getFilePath: (params: T) => Promise<string>
getFilePath: (params: T) => Promise<GetFilePathResult>
abstract getFilePathImpl (params: T): Promise<string>
abstract getFilePathImpl (params: T): Promise<GetFilePathResult>
// Load and save the remote file, then return the local path from filesystem
protected abstract loadRemoteFile (key: string): Promise<string>
protected abstract loadRemoteFile (key: string): Promise<GetFilePathResult>
init (max: number, maxAge: number) {
this.getFilePath = memoizee(this.getFilePathImpl, {
maxAge,
max,
promise: true,
dispose: (value: string) => {
remove(value)
.then(() => logger.debug('%s evicted from %s', value, this.constructor.name))
.catch(err => logger.error('Cannot remove %s from cache %s.', value, this.constructor.name, { err }))
dispose: (result: GetFilePathResult) => {
if (result.isOwned !== true) {
remove(result.path)
.then(() => logger.debug('%s removed from %s', result.path, this.constructor.name))
.catch(err => logger.error('Cannot remove %s from cache %s.', result.path, this.constructor.name, { err }))
}
}
})
}

View File

@ -4,6 +4,7 @@ import { VideoModel } from '../../models/video/video'
import { VideoCaptionModel } from '../../models/video/video-caption'
import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache'
import { CONFIG } from '../../initializers/config'
import { logger } from '../../helpers/logger'
type GetPathParam = { videoId: string, language: string }
@ -24,13 +25,15 @@ class VideosCaptionCache extends AbstractVideoStaticFileCache <GetPathParam> {
const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(params.videoId, params.language)
if (!videoCaption) return undefined
if (videoCaption.isOwned()) return join(CONFIG.STORAGE.CAPTIONS_DIR, videoCaption.getCaptionName())
if (videoCaption.isOwned()) return { isOwned: true, path: join(CONFIG.STORAGE.CAPTIONS_DIR, videoCaption.getCaptionName()) }
const key = params.videoId + VideosCaptionCache.KEY_DELIMITER + params.language
return this.loadRemoteFile(key)
}
protected async loadRemoteFile (key: string) {
logger.debug('Loading remote caption file %s.', key)
const [ videoId, language ] = key.split(VideosCaptionCache.KEY_DELIMITER)
const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(videoId, language)
@ -46,7 +49,9 @@ class VideosCaptionCache extends AbstractVideoStaticFileCache <GetPathParam> {
const remoteStaticPath = videoCaption.getCaptionStaticPath()
const destPath = join(FILES_CACHE.VIDEO_CAPTIONS.DIRECTORY, videoCaption.getCaptionName())
return this.saveRemoteVideoFileAndReturnPath(video, remoteStaticPath, destPath)
const path = await this.saveRemoteVideoFileAndReturnPath(video, remoteStaticPath, destPath)
return { isOwned: false, path }
}
}

View File

@ -20,7 +20,7 @@ class VideosPreviewCache extends AbstractVideoStaticFileCache <string> {
const video = await VideoModel.loadByUUIDWithFile(videoUUID)
if (!video) return undefined
if (video.isOwned()) return join(CONFIG.STORAGE.PREVIEWS_DIR, video.getPreview().filename)
if (video.isOwned()) return { isOwned: true, path: join(CONFIG.STORAGE.PREVIEWS_DIR, video.getPreview().filename) }
return this.loadRemoteFile(videoUUID)
}
@ -35,7 +35,9 @@ class VideosPreviewCache extends AbstractVideoStaticFileCache <string> {
const remoteStaticPath = join(STATIC_PATHS.PREVIEWS, video.getPreview().filename)
const destPath = join(FILES_CACHE.PREVIEWS.DIRECTORY, video.getPreview().filename)
return this.saveRemoteVideoFileAndReturnPath(video, remoteStaticPath, destPath)
const path = await this.saveRemoteVideoFileAndReturnPath(video, remoteStaticPath, destPath)
return { isOwned: false, path }
}
}

View File

@ -18,7 +18,7 @@ import { Notifier } from '../../notifier'
import { CONFIG } from '../../../initializers/config'
import { sequelizeTypescript } from '../../../initializers/database'
import { ThumbnailModel } from '../../../models/video/thumbnail'
import { createVideoThumbnailFromUrl, generateVideoThumbnail } from '../../thumbnail'
import { createVideoMiniatureFromUrl, generateVideoMiniature } from '../../thumbnail'
import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type'
type VideoImportYoutubeDLPayload = {
@ -150,17 +150,17 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide
// Process thumbnail
let thumbnailModel: ThumbnailModel
if (options.downloadThumbnail && options.thumbnailUrl) {
thumbnailModel = await createVideoThumbnailFromUrl(options.thumbnailUrl, videoImport.Video, ThumbnailType.THUMBNAIL)
thumbnailModel = await createVideoMiniatureFromUrl(options.thumbnailUrl, videoImport.Video, ThumbnailType.MINIATURE)
} else if (options.generateThumbnail || options.downloadThumbnail) {
thumbnailModel = await generateVideoThumbnail(videoImport.Video, videoFile, ThumbnailType.THUMBNAIL)
thumbnailModel = await generateVideoMiniature(videoImport.Video, videoFile, ThumbnailType.MINIATURE)
}
// Process preview
let previewModel: ThumbnailModel
if (options.downloadPreview && options.thumbnailUrl) {
previewModel = await createVideoThumbnailFromUrl(options.thumbnailUrl, videoImport.Video, ThumbnailType.PREVIEW)
previewModel = await createVideoMiniatureFromUrl(options.thumbnailUrl, videoImport.Video, ThumbnailType.PREVIEW)
} else if (options.generatePreview || options.downloadPreview) {
previewModel = await generateVideoThumbnail(videoImport.Video, videoFile, ThumbnailType.PREVIEW)
previewModel = await generateVideoMiniature(videoImport.Video, videoFile, ThumbnailType.PREVIEW)
}
// Create torrent
@ -180,14 +180,8 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide
video.state = CONFIG.TRANSCODING.ENABLED ? VideoState.TO_TRANSCODE : VideoState.PUBLISHED
await video.save({ transaction: t })
if (thumbnailModel) {
thumbnailModel.videoId = video.id
video.addThumbnail(await thumbnailModel.save({ transaction: t }))
}
if (previewModel) {
previewModel.videoId = video.id
video.addThumbnail(await previewModel.save({ transaction: t }))
}
if (thumbnailModel) await video.addAndSaveThumbnail(thumbnailModel, t)
if (previewModel) await video.addAndSaveThumbnail(previewModel, t)
// Now we can federate the video (reload from database, we need more attributes)
const videoForFederation = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t)

View File

@ -39,6 +39,8 @@ function clearCacheByToken (token: string) {
function getAccessToken (bearerToken: string) {
logger.debug('Getting access token (bearerToken: ' + bearerToken + ').')
if (!bearerToken) return Bluebird.resolve(undefined)
if (accessTokenCache[bearerToken] !== undefined) return Bluebird.resolve(accessTokenCache[bearerToken])
return OAuthTokenModel.getByTokenAndPopulateUser(bearerToken)

View File

@ -12,37 +12,37 @@ import { VideoPlaylistModel } from '../models/video/video-playlist'
type ImageSize = { height: number, width: number }
function createPlaylistThumbnailFromExisting (inputPath: string, playlist: VideoPlaylistModel, keepOriginal = false, size?: ImageSize) {
function createPlaylistMiniatureFromExisting (inputPath: string, playlist: VideoPlaylistModel, keepOriginal = false, size?: ImageSize) {
const { filename, outputPath, height, width, existingThumbnail } = buildMetadataFromPlaylist(playlist, size)
const type = ThumbnailType.THUMBNAIL
const type = ThumbnailType.MINIATURE
const thumbnailCreator = () => processImage({ path: inputPath }, outputPath, { width, height }, keepOriginal)
return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail })
}
function createPlaylistThumbnailFromUrl (url: string, playlist: VideoPlaylistModel, size?: ImageSize) {
function createPlaylistMiniatureFromUrl (url: string, playlist: VideoPlaylistModel, size?: ImageSize) {
const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromPlaylist(playlist, size)
const type = ThumbnailType.THUMBNAIL
const type = ThumbnailType.MINIATURE
const thumbnailCreator = () => downloadImage(url, basePath, filename, { width, height })
return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail, url })
}
function createVideoThumbnailFromUrl (url: string, video: VideoModel, type: ThumbnailType, size?: ImageSize) {
function createVideoMiniatureFromUrl (url: string, video: VideoModel, type: ThumbnailType, size?: ImageSize) {
const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size)
const thumbnailCreator = () => downloadImage(url, basePath, filename, { width, height })
return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail, url })
}
function createVideoThumbnailFromExisting (inputPath: string, video: VideoModel, type: ThumbnailType, size?: ImageSize) {
function createVideoMiniatureFromExisting (inputPath: string, video: VideoModel, type: ThumbnailType, size?: ImageSize) {
const { filename, outputPath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size)
const thumbnailCreator = () => processImage({ path: inputPath }, outputPath, { width, height })
return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail })
}
function generateVideoThumbnail (video: VideoModel, videoFile: VideoFileModel, type: ThumbnailType) {
function generateVideoMiniature (video: VideoModel, videoFile: VideoFileModel, type: ThumbnailType) {
const input = video.getVideoFilePath(videoFile)
const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type)
@ -68,12 +68,12 @@ function createPlaceholderThumbnail (url: string, video: VideoModel, type: Thumb
// ---------------------------------------------------------------------------
export {
generateVideoThumbnail,
createVideoThumbnailFromUrl,
createVideoThumbnailFromExisting,
generateVideoMiniature,
createVideoMiniatureFromUrl,
createVideoMiniatureFromExisting,
createPlaceholderThumbnail,
createPlaylistThumbnailFromUrl,
createPlaylistThumbnailFromExisting
createPlaylistMiniatureFromUrl,
createPlaylistMiniatureFromExisting
}
function buildMetadataFromPlaylist (playlist: VideoPlaylistModel, size: ImageSize) {
@ -95,7 +95,7 @@ function buildMetadataFromVideo (video: VideoModel, type: ThumbnailType, size?:
? video.Thumbnails.find(t => t.type === type)
: undefined
if (type === ThumbnailType.THUMBNAIL) {
if (type === ThumbnailType.MINIATURE) {
const filename = video.generateThumbnailName()
const basePath = CONFIG.STORAGE.THUMBNAILS_DIR

View File

@ -35,6 +35,8 @@ function authenticateSocket (socket: Socket, next: (err?: any) => void) {
logger.debug('Checking socket access token %s.', accessToken)
if (!accessToken) return next(new Error('No access token provided'))
getAccessToken(accessToken)
.then(tokenDB => {
const now = new Date()

View File

@ -68,6 +68,7 @@ const videosAddValidator = getCommonVideoEditAttributes().concat([
if (!await doesVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req)
const isAble = await user.isAbleToUploadVideo(videoFile)
if (isAble === false) {
res.status(403)
.json({ error: 'The user video quota is exceeded with this video.' })

View File

@ -8,22 +8,22 @@ enum ScopeNames {
WITH_ACCOUNTS = 'WITH_ACCOUNTS'
}
@Scopes({
@Scopes(() => ({
[ScopeNames.WITH_ACCOUNTS]: {
include: [
{
model: () => AccountModel,
model: AccountModel,
required: true,
as: 'ByAccount'
},
{
model: () => AccountModel,
model: AccountModel,
required: true,
as: 'BlockedAccount'
}
]
}
})
}))
@Table({
tableName: 'accountBlocklist',
@ -83,7 +83,7 @@ export class AccountBlocklistModel extends Model<AccountBlocklistModel> {
attributes: [ 'accountId', 'id' ],
where: {
accountId: {
[Op.any]: accountIds
[Op.in]: accountIds // FIXME: sequelize ANY seems broken
},
targetAccountId
},

View File

@ -33,15 +33,15 @@ export enum ScopeNames {
SUMMARY = 'SUMMARY'
}
@DefaultScope({
@DefaultScope(() => ({
include: [
{
model: () => ActorModel, // Default scope includes avatar and server
model: ActorModel, // Default scope includes avatar and server
required: true
}
]
})
@Scopes({
}))
@Scopes(() => ({
[ ScopeNames.SUMMARY ]: (whereActor?: WhereOptions) => {
return {
attributes: [ 'id', 'name' ],
@ -66,7 +66,7 @@ export enum ScopeNames {
]
}
}
})
}))
@Table({
tableName: 'account',
indexes: [

View File

@ -6,7 +6,7 @@ import { isUserNotificationTypeValid } from '../../helpers/custom-validators/use
import { UserModel } from './user'
import { VideoModel } from '../video/video'
import { VideoCommentModel } from '../video/video-comment'
import { FindOptions, Op } from 'sequelize'
import { FindOptions, ModelIndexesOptions, Op, WhereOptions } from 'sequelize'
import { VideoChannelModel } from '../video/video-channel'
import { AccountModel } from './account'
import { VideoAbuseModel } from '../video/video-abuse'
@ -24,17 +24,17 @@ enum ScopeNames {
function buildActorWithAvatarInclude () {
return {
attributes: [ 'preferredUsername' ],
model: () => ActorModel.unscoped(),
model: ActorModel.unscoped(),
required: true,
include: [
{
attributes: [ 'filename' ],
model: () => AvatarModel.unscoped(),
model: AvatarModel.unscoped(),
required: false
},
{
attributes: [ 'host' ],
model: () => ServerModel.unscoped(),
model: ServerModel.unscoped(),
required: false
}
]
@ -44,7 +44,7 @@ function buildActorWithAvatarInclude () {
function buildVideoInclude (required: boolean) {
return {
attributes: [ 'id', 'uuid', 'name' ],
model: () => VideoModel.unscoped(),
model: VideoModel.unscoped(),
required
}
}
@ -53,7 +53,7 @@ function buildChannelInclude (required: boolean, withActor = false) {
return {
required,
attributes: [ 'id', 'name' ],
model: () => VideoChannelModel.unscoped(),
model: VideoChannelModel.unscoped(),
include: withActor === true ? [ buildActorWithAvatarInclude() ] : []
}
}
@ -62,12 +62,12 @@ function buildAccountInclude (required: boolean, withActor = false) {
return {
required,
attributes: [ 'id', 'name' ],
model: () => AccountModel.unscoped(),
model: AccountModel.unscoped(),
include: withActor === true ? [ buildActorWithAvatarInclude() ] : []
}
}
@Scopes({
@Scopes(() => ({
[ScopeNames.WITH_ALL]: {
include: [
Object.assign(buildVideoInclude(false), {
@ -76,7 +76,7 @@ function buildAccountInclude (required: boolean, withActor = false) {
{
attributes: [ 'id', 'originCommentId' ],
model: () => VideoCommentModel.unscoped(),
model: VideoCommentModel.unscoped(),
required: false,
include: [
buildAccountInclude(true, true),
@ -86,56 +86,56 @@ function buildAccountInclude (required: boolean, withActor = false) {
{
attributes: [ 'id' ],
model: () => VideoAbuseModel.unscoped(),
model: VideoAbuseModel.unscoped(),
required: false,
include: [ buildVideoInclude(true) ]
},
{
attributes: [ 'id' ],
model: () => VideoBlacklistModel.unscoped(),
model: VideoBlacklistModel.unscoped(),
required: false,
include: [ buildVideoInclude(true) ]
},
{
attributes: [ 'id', 'magnetUri', 'targetUrl', 'torrentName' ],
model: () => VideoImportModel.unscoped(),
model: VideoImportModel.unscoped(),
required: false,
include: [ buildVideoInclude(false) ]
},
{
attributes: [ 'id', 'state' ],
model: () => ActorFollowModel.unscoped(),
model: ActorFollowModel.unscoped(),
required: false,
include: [
{
attributes: [ 'preferredUsername' ],
model: () => ActorModel.unscoped(),
model: ActorModel.unscoped(),
required: true,
as: 'ActorFollower',
include: [
{
attributes: [ 'id', 'name' ],
model: () => AccountModel.unscoped(),
model: AccountModel.unscoped(),
required: true
},
{
attributes: [ 'filename' ],
model: () => AvatarModel.unscoped(),
model: AvatarModel.unscoped(),
required: false
},
{
attributes: [ 'host' ],
model: () => ServerModel.unscoped(),
model: ServerModel.unscoped(),
required: false
}
]
},
{
attributes: [ 'preferredUsername' ],
model: () => ActorModel.unscoped(),
model: ActorModel.unscoped(),
required: true,
as: 'ActorFollowing',
include: [
@ -147,9 +147,9 @@ function buildAccountInclude (required: boolean, withActor = false) {
},
buildAccountInclude(false, true)
] as any // FIXME: sequelize typings
]
}
})
}))
@Table({
tableName: 'userNotification',
indexes: [
@ -212,7 +212,7 @@ function buildAccountInclude (required: boolean, withActor = false) {
}
}
}
] as any // FIXME: sequelize typings
] as (ModelIndexesOptions & { where?: WhereOptions })[]
})
export class UserNotificationModel extends Model<UserNotificationModel> {
@ -357,7 +357,7 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
where: {
userId,
id: {
[Op.any]: notificationIds
[Op.in]: notificationIds // FIXME: sequelize ANY seems broken
}
}
}

View File

@ -1,4 +1,4 @@
import * as Sequelize from 'sequelize'
import { FindOptions, literal, Op, QueryTypes } from 'sequelize'
import {
AfterDestroy,
AfterUpdate,
@ -56,33 +56,33 @@ enum ScopeNames {
WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL'
}
@DefaultScope({
@DefaultScope(() => ({
include: [
{
model: () => AccountModel,
model: AccountModel,
required: true
},
{
model: () => UserNotificationSettingModel,
model: UserNotificationSettingModel,
required: true
}
]
})
@Scopes({
}))
@Scopes(() => ({
[ScopeNames.WITH_VIDEO_CHANNEL]: {
include: [
{
model: () => AccountModel,
model: AccountModel,
required: true,
include: [ () => VideoChannelModel ]
include: [ VideoChannelModel ]
},
{
model: () => UserNotificationSettingModel,
model: UserNotificationSettingModel,
required: true
}
] as any // FIXME: sequelize typings
]
}
})
}))
@Table({
tableName: 'user',
indexes: [
@ -233,26 +233,26 @@ export class UserModel extends Model<UserModel> {
let where = undefined
if (search) {
where = {
[Sequelize.Op.or]: [
[Op.or]: [
{
email: {
[Sequelize.Op.iLike]: '%' + search + '%'
[Op.iLike]: '%' + search + '%'
}
},
{
username: {
[ Sequelize.Op.iLike ]: '%' + search + '%'
[ Op.iLike ]: '%' + search + '%'
}
}
]
}
}
const query = {
const query: FindOptions = {
attributes: {
include: [
[
Sequelize.literal(
literal(
'(' +
'SELECT COALESCE(SUM("size"), 0) ' +
'FROM (' +
@ -265,7 +265,7 @@ export class UserModel extends Model<UserModel> {
')'
),
'videoQuotaUsed'
] as any // FIXME: typings
]
]
},
offset: start,
@ -291,7 +291,7 @@ export class UserModel extends Model<UserModel> {
const query = {
where: {
role: {
[Sequelize.Op.in]: roles
[Op.in]: roles
}
}
}
@ -387,7 +387,7 @@ export class UserModel extends Model<UserModel> {
const query = {
where: {
[ Sequelize.Op.or ]: [ { username }, { email } ]
[ Op.or ]: [ { username }, { email } ]
}
}
@ -510,7 +510,7 @@ export class UserModel extends Model<UserModel> {
const query = {
where: {
username: {
[ Sequelize.Op.like ]: `%${search}%`
[ Op.like ]: `%${search}%`
}
},
limit: 10
@ -591,15 +591,11 @@ export class UserModel extends Model<UserModel> {
const uploadedTotal = videoFile.size + totalBytes
const uploadedDaily = videoFile.size + totalBytesDaily
if (this.videoQuotaDaily === -1) {
return uploadedTotal < this.videoQuota
}
if (this.videoQuota === -1) {
return uploadedDaily < this.videoQuotaDaily
}
return (uploadedTotal < this.videoQuota) &&
(uploadedDaily < this.videoQuotaDaily)
if (this.videoQuotaDaily === -1) return uploadedTotal < this.videoQuota
if (this.videoQuota === -1) return uploadedDaily < this.videoQuotaDaily
return uploadedTotal < this.videoQuota && uploadedDaily < this.videoQuotaDaily
}
private static generateUserQuotaBaseSQL (where?: string) {
@ -619,14 +615,14 @@ export class UserModel extends Model<UserModel> {
private static getTotalRawQuery (query: string, userId: number) {
const options = {
bind: { userId },
type: Sequelize.QueryTypes.SELECT as Sequelize.QueryTypes.SELECT
type: QueryTypes.SELECT as QueryTypes.SELECT
}
return UserModel.sequelize.query<{ total: number }>(query, options)
return UserModel.sequelize.query<{ total: string }>(query, options)
.then(([ { total } ]) => {
if (total === null) return 0
return parseInt(total + '', 10)
return parseInt(total, 10)
})
}
}

View File

@ -56,46 +56,46 @@ export const unusedActorAttributesForAPI = [
'updatedAt'
]
@DefaultScope({
@DefaultScope(() => ({
include: [
{
model: () => ServerModel,
model: ServerModel,
required: false
},
{
model: () => AvatarModel,
model: AvatarModel,
required: false
}
]
})
@Scopes({
}))
@Scopes(() => ({
[ScopeNames.FULL]: {
include: [
{
model: () => AccountModel.unscoped(),
model: AccountModel.unscoped(),
required: false
},
{
model: () => VideoChannelModel.unscoped(),
model: VideoChannelModel.unscoped(),
required: false,
include: [
{
model: () => AccountModel,
model: AccountModel,
required: true
}
]
},
{
model: () => ServerModel,
model: ServerModel,
required: false
},
{
model: () => AvatarModel,
model: AvatarModel,
required: false
}
] as any // FIXME: sequelize typings
]
}
})
}))
@Table({
tableName: 'actor',
indexes: [
@ -131,7 +131,7 @@ export const unusedActorAttributesForAPI = [
export class ActorModel extends Model<ActorModel> {
@AllowNull(false)
@Column({ type: DataType.ENUM(...values(ACTIVITY_PUB_ACTOR_TYPES)) }) // FIXME: sequelize typings
@Column(DataType.ENUM(...values(ACTIVITY_PUB_ACTOR_TYPES)))
type: ActivityPubActorType
@AllowNull(false)
@ -280,14 +280,16 @@ export class ActorModel extends Model<ActorModel> {
attributes: [ 'id' ],
model: VideoChannelModel.unscoped(),
required: true,
include: {
attributes: [ 'id' ],
model: VideoModel.unscoped(),
required: true,
where: {
id: videoId
include: [
{
attributes: [ 'id' ],
model: VideoModel.unscoped(),
required: true,
where: {
id: videoId
}
}
}
]
}
]
}
@ -295,7 +297,7 @@ export class ActorModel extends Model<ActorModel> {
transaction
}
return ActorModel.unscoped().findOne(query as any) // FIXME: typings
return ActorModel.unscoped().findOne(query)
}
static isActorUrlExist (url: string) {
@ -389,8 +391,7 @@ export class ActorModel extends Model<ActorModel> {
}
static incrementFollows (id: number, column: 'followersCount' | 'followingCount', by: number) {
// FIXME: typings
return (ActorModel as any).increment(column, {
return ActorModel.increment(column, {
by,
where: {
id

View File

@ -1,14 +1,14 @@
import { AllowNull, Column, Default, DefaultScope, HasOne, IsInt, Model, Table } from 'sequelize-typescript'
import { AccountModel } from '../account/account'
@DefaultScope({
@DefaultScope(() => ({
include: [
{
model: () => AccountModel,
model: AccountModel,
required: true
}
]
})
}))
@Table({
tableName: 'application'
})

View File

@ -24,10 +24,10 @@ export class OAuthClientModel extends Model<OAuthClientModel> {
@Column
clientSecret: string
@Column({ type: DataType.ARRAY(DataType.STRING) }) // FIXME: sequelize typings
@Column(DataType.ARRAY(DataType.STRING))
grants: string[]
@Column({ type: DataType.ARRAY(DataType.STRING) }) // FIXME: sequelize typings
@Column(DataType.ARRAY(DataType.STRING))
redirectUris: string[]
@CreatedAt

View File

@ -34,30 +34,30 @@ enum ScopeNames {
WITH_USER = 'WITH_USER'
}
@Scopes({
@Scopes(() => ({
[ScopeNames.WITH_USER]: {
include: [
{
model: () => UserModel.unscoped(),
model: UserModel.unscoped(),
required: true,
include: [
{
attributes: [ 'id' ],
model: () => AccountModel.unscoped(),
model: AccountModel.unscoped(),
required: true,
include: [
{
attributes: [ 'id', 'url' ],
model: () => ActorModel.unscoped(),
model: ActorModel.unscoped(),
required: true
}
]
}
]
}
] as any // FIXME: sequelize typings
]
}
})
}))
@Table({
tableName: 'oAuthToken',
indexes: [
@ -167,11 +167,13 @@ export class OAuthTokenModel extends Model<OAuthTokenModel> {
}
}
return OAuthTokenModel.scope(ScopeNames.WITH_USER).findOne(query).then(token => {
if (token) token['user'] = token.User
return OAuthTokenModel.scope(ScopeNames.WITH_USER)
.findOne(query)
.then(token => {
if (token) token[ 'user' ] = token.User
return token
})
return token
})
}
static getByRefreshTokenAndPopulateUser (refreshToken: string) {

View File

@ -13,7 +13,7 @@ import {
UpdatedAt
} from 'sequelize-typescript'
import { ActorModel } from '../activitypub/actor'
import { getVideoSort, throwIfNotValid } from '../utils'
import { getVideoSort, parseAggregateResult, throwIfNotValid } from '../utils'
import { isActivityPubUrlValid, isUrlValid } from '../../helpers/custom-validators/activitypub/misc'
import { CONSTRAINTS_FIELDS, MIMETYPES } from '../../initializers/constants'
import { VideoFileModel } from '../video/video-file'
@ -27,7 +27,7 @@ import { ServerModel } from '../server/server'
import { sample } from 'lodash'
import { isTestInstance } from '../../helpers/core-utils'
import * as Bluebird from 'bluebird'
import * as Sequelize from 'sequelize'
import { col, FindOptions, fn, literal, Op, Transaction } from 'sequelize'
import { VideoStreamingPlaylistModel } from '../video/video-streaming-playlist'
import { CONFIG } from '../../initializers/config'
@ -35,32 +35,32 @@ export enum ScopeNames {
WITH_VIDEO = 'WITH_VIDEO'
}
@Scopes({
@Scopes(() => ({
[ ScopeNames.WITH_VIDEO ]: {
include: [
{
model: () => VideoFileModel,
model: VideoFileModel,
required: false,
include: [
{
model: () => VideoModel,
model: VideoModel,
required: true
}
]
},
{
model: () => VideoStreamingPlaylistModel,
model: VideoStreamingPlaylistModel,
required: false,
include: [
{
model: () => VideoModel,
model: VideoModel,
required: true
}
]
}
] as any // FIXME: sequelize typings
]
}
})
}))
@Table({
tableName: 'videoRedundancy',
@ -192,7 +192,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query)
}
static loadByUrl (url: string, transaction?: Sequelize.Transaction) {
static loadByUrl (url: string, transaction?: Transaction) {
const query = {
where: {
url
@ -292,7 +292,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
where: {
privacy: VideoPrivacy.PUBLIC,
views: {
[ Sequelize.Op.gte ]: minViews
[ Op.gte ]: minViews
}
},
include: [
@ -315,7 +315,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
actorId: actor.id,
strategy,
createdAt: {
[ Sequelize.Op.lt ]: expiredDate
[ Op.lt ]: expiredDate
}
}
}
@ -326,7 +326,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
static async getTotalDuplicated (strategy: VideoRedundancyStrategy) {
const actor = await getServerActor()
const options = {
const query: FindOptions = {
include: [
{
attributes: [],
@ -340,12 +340,8 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
]
}
return VideoFileModel.sum('size', options as any) // FIXME: typings
.then(v => {
if (!v || isNaN(v)) return 0
return v
})
return VideoFileModel.aggregate('size', 'SUM', query)
.then(result => parseAggregateResult(result))
}
static async listLocalExpired () {
@ -355,7 +351,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
where: {
actorId: actor.id,
expiresOn: {
[ Sequelize.Op.lt ]: new Date()
[ Op.lt ]: new Date()
}
}
}
@ -369,10 +365,10 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
const query = {
where: {
actorId: {
[Sequelize.Op.ne]: actor.id
[Op.ne]: actor.id
},
expiresOn: {
[ Sequelize.Op.lt ]: new Date()
[ Op.lt ]: new Date()
}
}
}
@ -428,12 +424,12 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
static async getStats (strategy: VideoRedundancyStrategy) {
const actor = await getServerActor()
const query = {
const query: FindOptions = {
raw: true,
attributes: [
[ Sequelize.fn('COALESCE', Sequelize.fn('SUM', Sequelize.col('VideoFile.size')), '0'), 'totalUsed' ],
[ Sequelize.fn('COUNT', Sequelize.fn('DISTINCT', Sequelize.col('videoId'))), 'totalVideos' ],
[ Sequelize.fn('COUNT', Sequelize.col('videoFileId')), 'totalVideoFiles' ]
[ fn('COALESCE', fn('SUM', col('VideoFile.size')), '0'), 'totalUsed' ],
[ fn('COUNT', fn('DISTINCT', col('videoId'))), 'totalVideos' ],
[ fn('COUNT', col('videoFileId')), 'totalVideoFiles' ]
],
where: {
strategy,
@ -448,9 +444,9 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
]
}
return VideoRedundancyModel.findOne(query as any) // FIXME: typings
return VideoRedundancyModel.findOne(query)
.then((r: any) => ({
totalUsed: parseInt(r.totalUsed.toString(), 10),
totalUsed: parseAggregateResult(r.totalUsed),
totalVideos: r.totalVideos,
totalVideoFiles: r.totalVideoFiles
}))
@ -503,7 +499,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
private static async buildVideoFileForDuplication () {
const actor = await getServerActor()
const notIn = Sequelize.literal(
const notIn = literal(
'(' +
`SELECT "videoFileId" FROM "videoRedundancy" WHERE "actorId" = ${actor.id} AND "videoFileId" IS NOT NULL` +
')'
@ -515,7 +511,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
required: true,
where: {
id: {
[ Sequelize.Op.notIn ]: notIn
[ Op.notIn ]: notIn
}
}
}

View File

@ -9,11 +9,11 @@ enum ScopeNames {
WITH_SERVER = 'WITH_SERVER'
}
@Scopes({
@Scopes(() => ({
[ScopeNames.WITH_ACCOUNT]: {
include: [
{
model: () => AccountModel,
model: AccountModel,
required: true
}
]
@ -21,12 +21,12 @@ enum ScopeNames {
[ScopeNames.WITH_SERVER]: {
include: [
{
model: () => ServerModel,
model: ServerModel,
required: true
}
]
}
})
}))
@Table({
tableName: 'serverBlocklist',

View File

@ -118,6 +118,15 @@ function buildWhereIdOrUUID (id: number | string) {
return validator.isInt('' + id) ? { id } : { uuid: id }
}
function parseAggregateResult (result: any) {
if (!result) return 0
const total = parseInt(result + '', 10)
if (isNaN(total)) return 0
return total
}
// ---------------------------------------------------------------------------
export {
@ -131,7 +140,8 @@ export {
buildServerIdsFollowedBy,
buildTrigramSearchIndex,
buildWhereIdOrUUID,
isOutdated
isOutdated,
parseAggregateResult
}
// ---------------------------------------------------------------------------

View File

@ -75,7 +75,7 @@ export class TagModel extends Model<TagModel> {
type: QueryTypes.SELECT as QueryTypes.SELECT
}
return TagModel.sequelize.query<{ name }>(query, options)
return TagModel.sequelize.query<{ name: string }>(query, options)
.then(data => data.map(d => d.name))
}
}

View File

@ -75,8 +75,8 @@ export class ThumbnailModel extends Model<ThumbnailModel> {
updatedAt: Date
private static types: { [ id in ThumbnailType ]: { label: string, directory: string, staticPath: string } } = {
[ThumbnailType.THUMBNAIL]: {
label: 'thumbnail',
[ThumbnailType.MINIATURE]: {
label: 'miniature',
directory: CONFIG.STORAGE.THUMBNAILS_DIR,
staticPath: STATIC_PATHS.THUMBNAILS
},

View File

@ -12,7 +12,7 @@ import {
Table,
UpdatedAt
} from 'sequelize-typescript'
import { throwIfNotValid } from '../utils'
import { buildWhereIdOrUUID, throwIfNotValid } from '../utils'
import { VideoModel } from './video'
import { isVideoCaptionLanguageValid } from '../../helpers/custom-validators/video-captions'
import { VideoCaption } from '../../../shared/models/videos/caption/video-caption.model'
@ -26,17 +26,17 @@ export enum ScopeNames {
WITH_VIDEO_UUID_AND_REMOTE = 'WITH_VIDEO_UUID_AND_REMOTE'
}
@Scopes({
@Scopes(() => ({
[ScopeNames.WITH_VIDEO_UUID_AND_REMOTE]: {
include: [
{
attributes: [ 'uuid', 'remote' ],
model: () => VideoModel.unscoped(),
model: VideoModel.unscoped(),
required: true
}
]
}
})
}))
@Table({
tableName: 'videoCaption',
@ -97,12 +97,9 @@ export class VideoCaptionModel extends Model<VideoCaptionModel> {
const videoInclude = {
model: VideoModel.unscoped(),
attributes: [ 'id', 'remote', 'uuid' ],
where: { }
where: buildWhereIdOrUUID(videoId)
}
if (typeof videoId === 'string') videoInclude.where['uuid'] = videoId
else videoInclude.where['id'] = videoId
const query = {
where: {
language

View File

@ -23,29 +23,29 @@ enum ScopeNames {
}
]
})
@Scopes({
@Scopes(() => ({
[ScopeNames.FULL]: {
include: [
{
model: () => AccountModel,
model: AccountModel,
as: 'Initiator',
required: true
},
{
model: () => AccountModel,
model: AccountModel,
as: 'NextOwner',
required: true
},
{
model: () => VideoModel,
model: VideoModel,
required: true,
include: [
{ model: () => VideoFileModel }
{ model: VideoFileModel }
]
}
] as any // FIXME: sequelize typings
]
}
})
}))
export class VideoChangeOwnershipModel extends Model<VideoChangeOwnershipModel> {
@CreatedAt
createdAt: Date

View File

@ -58,15 +58,15 @@ type AvailableForListOptions = {
actorId: number
}
@DefaultScope({
@DefaultScope(() => ({
include: [
{
model: () => ActorModel,
model: ActorModel,
required: true
}
]
})
@Scopes({
}))
@Scopes(() => ({
[ScopeNames.SUMMARY]: (withAccount = false) => {
const base: FindOptions = {
attributes: [ 'name', 'description', 'id', 'actorId' ],
@ -142,22 +142,22 @@ type AvailableForListOptions = {
[ScopeNames.WITH_ACCOUNT]: {
include: [
{
model: () => AccountModel,
model: AccountModel,
required: true
}
]
},
[ScopeNames.WITH_VIDEOS]: {
include: [
() => VideoModel
VideoModel
]
},
[ScopeNames.WITH_ACTOR]: {
include: [
() => ActorModel
ActorModel
]
}
})
}))
@Table({
tableName: 'videoChannel',
indexes

View File

@ -30,7 +30,7 @@ import { UserModel } from '../account/user'
import { actorNameAlphabet } from '../../helpers/custom-validators/activitypub/actor'
import { regexpCapture } from '../../helpers/regexp'
import { uniq } from 'lodash'
import { FindOptions, Op, Order, Sequelize, Transaction } from 'sequelize'
import { FindOptions, Op, Order, ScopeOptions, Sequelize, Transaction } from 'sequelize'
enum ScopeNames {
WITH_ACCOUNT = 'WITH_ACCOUNT',
@ -39,7 +39,7 @@ enum ScopeNames {
ATTRIBUTES_FOR_API = 'ATTRIBUTES_FOR_API'
}
@Scopes({
@Scopes(() => ({
[ScopeNames.ATTRIBUTES_FOR_API]: (serverAccountId: number, userAccountId?: number) => {
return {
attributes: {
@ -63,34 +63,34 @@ enum ScopeNames {
]
]
}
}
} as FindOptions
},
[ScopeNames.WITH_ACCOUNT]: {
include: [
{
model: () => AccountModel,
model: AccountModel,
include: [
{
model: () => ActorModel,
model: ActorModel,
include: [
{
model: () => ServerModel,
model: ServerModel,
required: false
},
{
model: () => AvatarModel,
model: AvatarModel,
required: false
}
]
}
]
}
] as any // FIXME: sequelize typings
]
},
[ScopeNames.WITH_IN_REPLY_TO]: {
include: [
{
model: () => VideoCommentModel,
model: VideoCommentModel,
as: 'InReplyToVideoComment'
}
]
@ -98,19 +98,19 @@ enum ScopeNames {
[ScopeNames.WITH_VIDEO]: {
include: [
{
model: () => VideoModel,
model: VideoModel,
required: true,
include: [
{
model: () => VideoChannelModel.unscoped(),
model: VideoChannelModel.unscoped(),
required: true,
include: [
{
model: () => AccountModel,
model: AccountModel,
required: true,
include: [
{
model: () => ActorModel,
model: ActorModel,
required: true
}
]
@ -119,9 +119,9 @@ enum ScopeNames {
}
]
}
] as any // FIXME: sequelize typings
]
}
})
}))
@Table({
tableName: 'videoComment',
indexes: [
@ -313,8 +313,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
}
}
// FIXME: typings
const scopes: any[] = [
const scopes: (string | ScopeOptions)[] = [
ScopeNames.WITH_ACCOUNT,
{
method: [ ScopeNames.ATTRIBUTES_FOR_API, serverAccountId, userAccountId ]

View File

@ -19,11 +19,11 @@ import {
isVideoFileSizeValid,
isVideoFPSResolutionValid
} from '../../helpers/custom-validators/videos'
import { throwIfNotValid } from '../utils'
import { parseAggregateResult, throwIfNotValid } from '../utils'
import { VideoModel } from './video'
import * as Sequelize from 'sequelize'
import { VideoRedundancyModel } from '../redundancy/video-redundancy'
import { VideoStreamingPlaylistModel } from './video-streaming-playlist'
import { FindOptions, QueryTypes, Transaction } from 'sequelize'
@Table({
tableName: 'videoFile',
@ -97,15 +97,13 @@ export class VideoFileModel extends Model<VideoFileModel> {
static doesInfohashExist (infoHash: string) {
const query = 'SELECT 1 FROM "videoFile" WHERE "infoHash" = $infoHash LIMIT 1'
const options = {
type: Sequelize.QueryTypes.SELECT,
type: QueryTypes.SELECT,
bind: { infoHash },
raw: true
}
return VideoModel.sequelize.query(query, options)
.then(results => {
return results.length === 1
})
.then(results => results.length === 1)
}
static loadWithVideo (id: number) {
@ -121,7 +119,7 @@ export class VideoFileModel extends Model<VideoFileModel> {
return VideoFileModel.findByPk(id, options)
}
static listByStreamingPlaylist (streamingPlaylistId: number, transaction: Sequelize.Transaction) {
static listByStreamingPlaylist (streamingPlaylistId: number, transaction: Transaction) {
const query = {
include: [
{
@ -144,8 +142,8 @@ export class VideoFileModel extends Model<VideoFileModel> {
return VideoFileModel.findAll(query)
}
static async getStats () {
let totalLocalVideoFilesSize = await VideoFileModel.sum('size', {
static getStats () {
const query: FindOptions = {
include: [
{
attributes: [],
@ -155,13 +153,12 @@ export class VideoFileModel extends Model<VideoFileModel> {
}
}
]
} as any)
// Sequelize could return null...
if (!totalLocalVideoFilesSize) totalLocalVideoFilesSize = 0
return {
totalLocalVideoFilesSize
}
return VideoFileModel.aggregate('size', 'SUM', query)
.then(result => ({
totalLocalVideoFilesSize: parseAggregateResult(result)
}))
}
hasSameUniqueKeysThan (other: VideoFileModel) {

View File

@ -59,7 +59,7 @@ function videoModelToFormattedJSON (video: VideoModel, options?: VideoFormatting
views: video.views,
likes: video.likes,
dislikes: video.dislikes,
thumbnailPath: video.getThumbnailStaticPath(),
thumbnailPath: video.getMiniatureStaticPath(),
previewPath: video.getPreviewStaticPath(),
embedPath: video.getEmbedStaticPath(),
createdAt: video.createdAt,
@ -301,6 +301,8 @@ function videoModelToActivityPubObject (video: VideoModel): VideoTorrentObject {
})
}
const miniature = video.getMiniature()
return {
type: 'Video' as 'Video',
id: video.url,
@ -326,10 +328,10 @@ function videoModelToActivityPubObject (video: VideoModel): VideoTorrentObject {
subtitleLanguage,
icon: {
type: 'Image',
url: video.getThumbnail().getUrl(),
url: miniature.getUrl(),
mediaType: 'image/jpeg',
width: video.getThumbnail().width,
height: video.getThumbnail().height
width: miniature.width,
height: miniature.height
},
url,
likes: getVideoLikesActivityPubUrl(video),

View File

@ -21,18 +21,18 @@ import { VideoImport, VideoImportState } from '../../../shared'
import { isVideoMagnetUriValid } from '../../helpers/custom-validators/videos'
import { UserModel } from '../account/user'
@DefaultScope({
@DefaultScope(() => ({
include: [
{
model: () => UserModel.unscoped(),
model: UserModel.unscoped(),
required: true
},
{
model: () => VideoModel.scope([ VideoModelScopeNames.WITH_ACCOUNT_DETAILS, VideoModelScopeNames.WITH_TAGS]),
model: VideoModel.scope([ VideoModelScopeNames.WITH_ACCOUNT_DETAILS, VideoModelScopeNames.WITH_TAGS]),
required: false
}
]
})
}))
@Table({
tableName: 'videoImport',

View File

@ -42,7 +42,7 @@ import { activityPubCollectionPagination } from '../../helpers/activitypub'
import { VideoPlaylistType } from '../../../shared/models/videos/playlist/video-playlist-type.model'
import { ThumbnailModel } from './thumbnail'
import { ActivityIconObject } from '../../../shared/models/activitypub/objects'
import { fn, literal, Op, Transaction } from 'sequelize'
import { FindOptions, literal, Op, ScopeOptions, Transaction, WhereOptions } from 'sequelize'
enum ScopeNames {
AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST',
@ -61,11 +61,11 @@ type AvailableForListOptions = {
privateAndUnlisted?: boolean
}
@Scopes({
@Scopes(() => ({
[ ScopeNames.WITH_THUMBNAIL ]: {
include: [
{
model: () => ThumbnailModel,
model: ThumbnailModel,
required: false
}
]
@ -73,21 +73,17 @@ type AvailableForListOptions = {
[ ScopeNames.WITH_VIDEOS_LENGTH ]: {
attributes: {
include: [
[
fn('COUNT', 'toto'),
'coucou'
],
[
literal('(SELECT COUNT("id") FROM "videoPlaylistElement" WHERE "videoPlaylistId" = "VideoPlaylistModel"."id")'),
'videosLength'
]
]
}
},
} as FindOptions,
[ ScopeNames.WITH_ACCOUNT ]: {
include: [
{
model: () => AccountModel,
model: AccountModel,
required: true
}
]
@ -95,11 +91,11 @@ type AvailableForListOptions = {
[ ScopeNames.WITH_ACCOUNT_AND_CHANNEL_SUMMARY ]: {
include: [
{
model: () => AccountModel.scope(AccountScopeNames.SUMMARY),
model: AccountModel.scope(AccountScopeNames.SUMMARY),
required: true
},
{
model: () => VideoChannelModel.scope(VideoChannelScopeNames.SUMMARY),
model: VideoChannelModel.scope(VideoChannelScopeNames.SUMMARY),
required: false
}
]
@ -107,11 +103,11 @@ type AvailableForListOptions = {
[ ScopeNames.WITH_ACCOUNT_AND_CHANNEL ]: {
include: [
{
model: () => AccountModel,
model: AccountModel,
required: true
},
{
model: () => VideoChannelModel,
model: VideoChannelModel,
required: false
}
]
@ -132,7 +128,7 @@ type AvailableForListOptions = {
]
}
const whereAnd: any[] = []
const whereAnd: WhereOptions[] = []
if (options.privateAndUnlisted !== true) {
whereAnd.push({
@ -178,9 +174,9 @@ type AvailableForListOptions = {
required: false
}
]
}
} as FindOptions
}
})
}))
@Table({
tableName: 'videoPlaylist',
@ -269,6 +265,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
VideoPlaylistElements: VideoPlaylistElementModel[]
@HasOne(() => ThumbnailModel, {
foreignKey: {
name: 'videoPlaylistId',
allowNull: true
@ -294,7 +291,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
order: getSort(options.sort)
}
const scopes = [
const scopes: (string | ScopeOptions)[] = [
{
method: [
ScopeNames.AVAILABLE_FOR_LIST,
@ -306,7 +303,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
privateAndUnlisted: options.privateAndUnlisted
} as AvailableForListOptions
]
} as any, // FIXME: typings
},
ScopeNames.WITH_VIDEOS_LENGTH,
ScopeNames.WITH_THUMBNAIL
]
@ -348,7 +345,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
model: VideoPlaylistElementModel.unscoped(),
where: {
videoId: {
[Op.any]: videoIds
[Op.in]: videoIds // FIXME: sequelize ANY seems broken
}
},
required: true
@ -427,12 +424,10 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
return VideoPlaylistModel.update({ privacy: VideoPlaylistPrivacy.PRIVATE, videoChannelId: null }, query)
}
setThumbnail (thumbnail: ThumbnailModel) {
this.Thumbnail = thumbnail
}
async setAndSaveThumbnail (thumbnail: ThumbnailModel, t: Transaction) {
thumbnail.videoPlaylistId = this.id
getThumbnail () {
return this.Thumbnail
this.Thumbnail = await thumbnail.save({ transaction: t })
}
hasThumbnail () {
@ -448,13 +443,13 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
getThumbnailUrl () {
if (!this.hasThumbnail()) return null
return WEBSERVER.URL + STATIC_PATHS.THUMBNAILS + this.getThumbnail().filename
return WEBSERVER.URL + STATIC_PATHS.THUMBNAILS + this.Thumbnail.filename
}
getThumbnailStaticPath () {
if (!this.hasThumbnail()) return null
return join(STATIC_PATHS.THUMBNAILS, this.getThumbnail().filename)
return join(STATIC_PATHS.THUMBNAILS, this.Thumbnail.filename)
}
setAsRefreshed () {

View File

@ -14,15 +14,15 @@ enum ScopeNames {
WITH_ACTOR = 'WITH_ACTOR'
}
@Scopes({
@Scopes(() => ({
[ScopeNames.FULL]: {
include: [
{
model: () => ActorModel,
model: ActorModel,
required: true
},
{
model: () => VideoModel,
model: VideoModel,
required: true
}
]
@ -30,12 +30,12 @@ enum ScopeNames {
[ScopeNames.WITH_ACTOR]: {
include: [
{
model: () => ActorModel,
model: ActorModel,
required: true
}
]
}
})
}))
@Table({
tableName: 'videoShare',
indexes: [

View File

@ -26,7 +26,7 @@ import { QueryTypes, Op } from 'sequelize'
fields: [ 'p2pMediaLoaderInfohashes' ],
using: 'gin'
}
] as any // FIXME: sequelize typings
]
})
export class VideoStreamingPlaylistModel extends Model<VideoStreamingPlaylistModel> {
@CreatedAt
@ -46,7 +46,7 @@ export class VideoStreamingPlaylistModel extends Model<VideoStreamingPlaylistMod
@AllowNull(false)
@Is('VideoStreamingPlaylistInfoHashes', value => throwIfNotValid(value, v => isArrayOf(v, isVideoFileInfoHashValid), 'info hashes'))
@Column({ type: DataType.ARRAY(DataType.STRING) }) // FIXME: typings
@Column(DataType.ARRAY(DataType.STRING))
p2pMediaLoaderInfohashes: string[]
@AllowNull(false)
@ -87,7 +87,7 @@ export class VideoStreamingPlaylistModel extends Model<VideoStreamingPlaylistMod
raw: true
}
return VideoModel.sequelize.query<any>(query, options)
return VideoModel.sequelize.query<object>(query, options)
.then(results => results.length === 1)
}

View File

@ -227,12 +227,12 @@ type AvailableForListIDsOptions = {
historyOfUser?: UserModel
}
@Scopes({
@Scopes(() => ({
[ ScopeNames.FOR_API ]: (options: ForAPIOptions) => {
const query: FindOptions = {
where: {
id: {
[ Op.in ]: options.ids // FIXME: sequelize any seems broken
[ Op.in ]: options.ids // FIXME: sequelize ANY seems broken
}
},
include: [
@ -486,7 +486,7 @@ type AvailableForListIDsOptions = {
[ ScopeNames.WITH_THUMBNAILS ]: {
include: [
{
model: () => ThumbnailModel,
model: ThumbnailModel,
required: false
}
]
@ -495,48 +495,48 @@ type AvailableForListIDsOptions = {
include: [
{
attributes: [ 'accountId' ],
model: () => VideoChannelModel.unscoped(),
model: VideoChannelModel.unscoped(),
required: true,
include: [
{
attributes: [ 'userId' ],
model: () => AccountModel.unscoped(),
model: AccountModel.unscoped(),
required: true
}
]
}
] as any // FIXME: sequelize typings
]
},
[ ScopeNames.WITH_ACCOUNT_DETAILS ]: {
include: [
{
model: () => VideoChannelModel.unscoped(),
model: VideoChannelModel.unscoped(),
required: true,
include: [
{
attributes: {
exclude: [ 'privateKey', 'publicKey' ]
},
model: () => ActorModel.unscoped(),
model: ActorModel.unscoped(),
required: true,
include: [
{
attributes: [ 'host' ],
model: () => ServerModel.unscoped(),
model: ServerModel.unscoped(),
required: false
},
{
model: () => AvatarModel.unscoped(),
model: AvatarModel.unscoped(),
required: false
}
]
},
{
model: () => AccountModel.unscoped(),
model: AccountModel.unscoped(),
required: true,
include: [
{
model: () => ActorModel.unscoped(),
model: ActorModel.unscoped(),
attributes: {
exclude: [ 'privateKey', 'publicKey' ]
},
@ -544,11 +544,11 @@ type AvailableForListIDsOptions = {
include: [
{
attributes: [ 'host' ],
model: () => ServerModel.unscoped(),
model: ServerModel.unscoped(),
required: false
},
{
model: () => AvatarModel.unscoped(),
model: AvatarModel.unscoped(),
required: false
}
]
@ -557,16 +557,16 @@ type AvailableForListIDsOptions = {
}
]
}
] as any // FIXME: sequelize typings
]
},
[ ScopeNames.WITH_TAGS ]: {
include: [ () => TagModel ]
include: [ TagModel ]
},
[ ScopeNames.WITH_BLACKLISTED ]: {
include: [
{
attributes: [ 'id', 'reason' ],
model: () => VideoBlacklistModel,
model: VideoBlacklistModel,
required: false
}
]
@ -588,8 +588,7 @@ type AvailableForListIDsOptions = {
include: [
{
model: VideoFileModel.unscoped(),
// FIXME: typings
[ 'separate' as any ]: true, // We may have multiple files, having multiple redundancies so let's separate this join
separate: true, // We may have multiple files, having multiple redundancies so let's separate this join
required: false,
include: subInclude
}
@ -613,8 +612,7 @@ type AvailableForListIDsOptions = {
include: [
{
model: VideoStreamingPlaylistModel.unscoped(),
// FIXME: typings
[ 'separate' as any ]: true, // We may have multiple streaming playlists, having multiple redundancies so let's separate this join
separate: true, // We may have multiple streaming playlists, having multiple redundancies so let's separate this join
required: false,
include: subInclude
}
@ -624,7 +622,7 @@ type AvailableForListIDsOptions = {
[ ScopeNames.WITH_SCHEDULED_UPDATE ]: {
include: [
{
model: () => ScheduleVideoUpdateModel.unscoped(),
model: ScheduleVideoUpdateModel.unscoped(),
required: false
}
]
@ -643,7 +641,7 @@ type AvailableForListIDsOptions = {
]
}
}
})
}))
@Table({
tableName: 'video',
indexes
@ -1075,15 +1073,14 @@ export class VideoModel extends Model<VideoModel> {
}
return Bluebird.all([
// FIXME: typing issue
VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findAll(query as any),
VideoModel.sequelize.query<{ total: number }>(rawCountQuery, { type: QueryTypes.SELECT })
VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findAll(query),
VideoModel.sequelize.query<{ total: string }>(rawCountQuery, { type: QueryTypes.SELECT })
]).then(([ rows, totals ]) => {
// totals: totalVideos + totalVideoShares
let totalVideos = 0
let totalVideoShares = 0
if (totals[ 0 ]) totalVideos = parseInt(totals[ 0 ].total + '', 10)
if (totals[ 1 ]) totalVideoShares = parseInt(totals[ 1 ].total + '', 10)
if (totals[ 0 ]) totalVideos = parseInt(totals[ 0 ].total, 10)
if (totals[ 1 ]) totalVideoShares = parseInt(totals[ 1 ].total, 10)
const total = totalVideos + totalVideoShares
return {
@ -1094,50 +1091,58 @@ export class VideoModel extends Model<VideoModel> {
}
static listUserVideosForApi (accountId: number, start: number, count: number, sort: string, withFiles = false) {
const query: FindOptions = {
offset: start,
limit: count,
order: getVideoSort(sort),
include: [
{
model: VideoChannelModel,
required: true,
include: [
{
model: AccountModel,
where: {
id: accountId
},
required: true
}
]
},
{
model: ScheduleVideoUpdateModel,
required: false
},
{
model: VideoBlacklistModel,
required: false
}
]
function buildBaseQuery (): FindOptions {
return {
offset: start,
limit: count,
order: getVideoSort(sort),
include: [
{
model: VideoChannelModel,
required: true,
include: [
{
model: AccountModel,
where: {
id: accountId
},
required: true
}
]
}
]
}
}
const countQuery = buildBaseQuery()
const findQuery = buildBaseQuery()
findQuery.include.push({
model: ScheduleVideoUpdateModel,
required: false
})
findQuery.include.push({
model: VideoBlacklistModel,
required: false
})
if (withFiles === true) {
query.include.push({
findQuery.include.push({
model: VideoFileModel.unscoped(),
required: true
})
}
return VideoModel.scope(ScopeNames.WITH_THUMBNAILS)
.findAndCountAll(query)
.then(({ rows, count }) => {
return {
data: rows,
total: count
}
})
return Promise.all([
VideoModel.count(countQuery),
VideoModel.findAll(findQuery)
]).then(([ count, rows ]) => {
return {
data: rows,
total: count
}
})
}
static async listForApi (options: {
@ -1404,12 +1409,12 @@ export class VideoModel extends Model<VideoModel> {
const where = buildWhereIdOrUUID(id)
const options = {
order: [ [ 'Tags', 'name', 'ASC' ] ] as any, // FIXME: sequelize typings
order: [ [ 'Tags', 'name', 'ASC' ] ] as any,
where,
transaction: t
}
const scopes = [
const scopes: (string | ScopeOptions)[] = [
ScopeNames.WITH_TAGS,
ScopeNames.WITH_BLACKLISTED,
ScopeNames.WITH_ACCOUNT_DETAILS,
@ -1420,7 +1425,7 @@ export class VideoModel extends Model<VideoModel> {
]
if (userId) {
scopes.push({ method: [ ScopeNames.WITH_USER_HISTORY, userId ] } as any) // FIXME: typings
scopes.push({ method: [ ScopeNames.WITH_USER_HISTORY, userId ] })
}
return VideoModel
@ -1437,18 +1442,18 @@ export class VideoModel extends Model<VideoModel> {
transaction: t
}
const scopes = [
const scopes: (string | ScopeOptions)[] = [
ScopeNames.WITH_TAGS,
ScopeNames.WITH_BLACKLISTED,
ScopeNames.WITH_ACCOUNT_DETAILS,
ScopeNames.WITH_SCHEDULED_UPDATE,
ScopeNames.WITH_THUMBNAILS,
{ method: [ ScopeNames.WITH_FILES, true ] } as any, // FIXME: typings
{ method: [ ScopeNames.WITH_STREAMING_PLAYLISTS, true ] } as any // FIXME: typings
{ method: [ ScopeNames.WITH_FILES, true ] },
{ method: [ ScopeNames.WITH_STREAMING_PLAYLISTS, true ] }
]
if (userId) {
scopes.push({ method: [ ScopeNames.WITH_USER_HISTORY, userId ] } as any) // FIXME: typings
scopes.push({ method: [ ScopeNames.WITH_USER_HISTORY, userId ] })
}
return VideoModel
@ -1520,9 +1525,9 @@ export class VideoModel extends Model<VideoModel> {
attributes: [ field ],
limit: count,
group: field,
having: Sequelize.where(Sequelize.fn('COUNT', Sequelize.col(field)), {
[ Op.gte ]: threshold
}) as any, // FIXME: typings
having: Sequelize.where(
Sequelize.fn('COUNT', Sequelize.col(field)), { [ Op.gte ]: threshold }
),
order: [ (this.sequelize as any).random() ]
}
@ -1594,16 +1599,10 @@ export class VideoModel extends Model<VideoModel> {
]
}
// FIXME: typing
const apiScope: any[] = [ ScopeNames.WITH_THUMBNAILS ]
const apiScope: (string | ScopeOptions)[] = [ ScopeNames.WITH_THUMBNAILS ]
if (options.user) {
apiScope.push({ method: [ ScopeNames.WITH_USER_HISTORY, options.user.id ] })
// Even if the relation is n:m, we know that a user only have 0..1 video history
// So we won't have multiple rows for the same video
// A subquery adds some bugs in our query so disable it
secondQuery.subQuery = false
}
apiScope.push({
@ -1651,13 +1650,17 @@ export class VideoModel extends Model<VideoModel> {
return maxBy(this.VideoFiles, file => file.resolution)
}
addThumbnail (thumbnail: ThumbnailModel) {
async addAndSaveThumbnail (thumbnail: ThumbnailModel, transaction: Transaction) {
thumbnail.videoId = this.id
const savedThumbnail = await thumbnail.save({ transaction })
if (Array.isArray(this.Thumbnails) === false) this.Thumbnails = []
// Already have this thumbnail, skip
if (this.Thumbnails.find(t => t.id === thumbnail.id)) return
if (this.Thumbnails.find(t => t.id === savedThumbnail.id)) return
this.Thumbnails.push(thumbnail)
this.Thumbnails.push(savedThumbnail)
}
getVideoFilename (videoFile: VideoFileModel) {
@ -1668,10 +1671,10 @@ export class VideoModel extends Model<VideoModel> {
return this.uuid + '.jpg'
}
getThumbnail () {
getMiniature () {
if (Array.isArray(this.Thumbnails) === false) return undefined
return this.Thumbnails.find(t => t.type === ThumbnailType.THUMBNAIL)
return this.Thumbnails.find(t => t.type === ThumbnailType.MINIATURE)
}
generatePreviewName () {
@ -1732,8 +1735,8 @@ export class VideoModel extends Model<VideoModel> {
return '/videos/embed/' + this.uuid
}
getThumbnailStaticPath () {
const thumbnail = this.getThumbnail()
getMiniatureStaticPath () {
const thumbnail = this.getMiniature()
if (!thumbnail) return null
return join(STATIC_PATHS.THUMBNAILS, thumbnail.filename)

View File

@ -0,0 +1,18 @@
import { Model } from 'sequelize-typescript'
// Thanks to sequelize-typescript: https://github.com/RobinBuschmann/sequelize-typescript
export type Diff<T extends string | symbol | number, U extends string | symbol | number> =
({ [P in T]: P } & { [P in U]: never } & { [ x: string ]: never })[T]
export type Omit<T, K extends keyof T> = { [P in Diff<keyof T, K>]: T[P] }
export type RecursivePartial<T> = { [P in keyof T]?: RecursivePartial<T[P]> }
export type FilteredModelAttributes<T extends Model<T>> = RecursivePartial<Omit<T, keyof Model<any>>> & {
id?: number | any
createdAt?: Date | any
updatedAt?: Date | any
deletedAt?: Date | any
version?: number | any
}

View File

@ -15,7 +15,6 @@ function getSequelize (serverNumber: number) {
dialect: 'postgres',
host,
port,
operatorsAliases: false,
logging: false
})

View File

@ -1,4 +1,4 @@
export enum ThumbnailType {
THUMBNAIL = 1,
MINIATURE = 1,
PREVIEW = 2
}

View File

@ -7461,17 +7461,17 @@ sequelize-pool@^1.0.2:
dependencies:
bluebird "^3.5.3"
sequelize-typescript@^1.0.0-beta.1:
version "1.0.0-beta.1"
resolved "https://registry.yarnpkg.com/sequelize-typescript/-/sequelize-typescript-1.0.0-beta.1.tgz#402279fec52669cbd78ecbf50e189638483a7360"
integrity sha512-xD28kqa1rIKujlmgA4hWQgtwFfRM6tLv1/mnZOrOFEZxvSWazUbTzqGB7OZydZDNj3iJnyrV1l6i6HOfvrpvEw==
sequelize-typescript@1.0.0-beta.2:
version "1.0.0-beta.2"
resolved "https://registry.yarnpkg.com/sequelize-typescript/-/sequelize-typescript-1.0.0-beta.2.tgz#fd9ae47ecf8b159e32e19c1298426cc9773cebd8"
integrity sha512-Iu67kF/RunoeBQBsU5llViJkxAHBVmeS9DBP+eC63hkEwxeDGZgxOkodyW5v5k3h2DJ0MBO+clRURXoDb+/OHg==
dependencies:
glob "7.1.2"
sequelize@5.6.1:
version "5.6.1"
resolved "https://registry.yarnpkg.com/sequelize/-/sequelize-5.6.1.tgz#fc22306109fb2504a6573edfb3c469ec86fae873"
integrity sha512-QsXUDar6ow0HrF9BtnHRaNumu6qRYb97dfwvez/Z5guH3i6w6k8+bp6gP3VCiDC+2qX+jQIyrYohKg9evy8GFg==
sequelize@5.7.4:
version "5.7.4"
resolved "https://registry.yarnpkg.com/sequelize/-/sequelize-5.7.4.tgz#1631faadff65f3a345b9757fca60429c65ba8e57"
integrity sha512-CaVYpAgZQEsGDuZ+Oq6uIZy4pxQxscotuh5UGIaFRa0VkTIgV0IiF7vAhSv+1Wn+NvhKCvgJJ85M34BP3AdGNg==
dependencies:
bluebird "^3.5.0"
cls-bluebird "^2.1.0"
@ -8678,10 +8678,10 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
typescript@^3.1.6:
version "3.4.1"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.4.1.tgz#b6691be11a881ffa9a05765a205cb7383f3b63c6"
integrity sha512-3NSMb2VzDQm8oBTLH6Nj55VVtUEpe/rgkIzMir0qVoLyjDZlnMBva0U6vDiV3IH+sl/Yu6oP5QwsAQtHPmDd2Q==
typescript@^3.4.3:
version "3.4.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.4.3.tgz#0eb320e4ace9b10eadf5bc6103286b0f8b7c224f"
integrity sha512-FFgHdPt4T/duxx6Ndf7hwgMZZjZpB+U0nMNGVCYPq0rEzWKjEDobm4J6yb3CS7naZ0yURFqdw9Gwc7UOh/P9oQ==
uid-number@0.0.6:
version "0.0.6"