Upgrade sequelize
This commit is contained in:
parent
1735c82572
commit
3acc508440
|
@ -16,8 +16,8 @@
|
||||||
/config/production.yaml
|
/config/production.yaml
|
||||||
/config/local*
|
/config/local*
|
||||||
/ffmpeg/
|
/ffmpeg/
|
||||||
/ffmpeg-4/
|
|
||||||
/ffmpeg-3/
|
/ffmpeg-3/
|
||||||
|
/ffmpeg-4/
|
||||||
/thumbnails/
|
/thumbnails/
|
||||||
/torrents/
|
/torrents/
|
||||||
/videos/
|
/videos/
|
||||||
|
|
|
@ -142,8 +142,8 @@
|
||||||
"reflect-metadata": "^0.1.12",
|
"reflect-metadata": "^0.1.12",
|
||||||
"request": "^2.81.0",
|
"request": "^2.81.0",
|
||||||
"scripty": "^1.5.0",
|
"scripty": "^1.5.0",
|
||||||
"sequelize": "5.6.1",
|
"sequelize": "5.7.4",
|
||||||
"sequelize-typescript": "^1.0.0-beta.1",
|
"sequelize-typescript": "1.0.0-beta.2",
|
||||||
"sharp": "^0.22.0",
|
"sharp": "^0.22.0",
|
||||||
"sitemap": "^2.1.0",
|
"sitemap": "^2.1.0",
|
||||||
"socket.io": "^2.2.0",
|
"socket.io": "^2.2.0",
|
||||||
|
@ -212,7 +212,7 @@
|
||||||
"ts-node": "8.0.3",
|
"ts-node": "8.0.3",
|
||||||
"tslint": "^5.7.0",
|
"tslint": "^5.7.0",
|
||||||
"tslint-config-standard": "^8.0.1",
|
"tslint-config-standard": "^8.0.1",
|
||||||
"typescript": "^3.1.6",
|
"typescript": "^3.4.3",
|
||||||
"xliff": "^4.0.0"
|
"xliff": "^4.0.0"
|
||||||
},
|
},
|
||||||
"scripty": {
|
"scripty": {
|
||||||
|
|
|
@ -41,7 +41,7 @@ import { VideoPlaylistReorder } from '../../../shared/models/videos/playlist/vid
|
||||||
import { JobQueue } from '../../lib/job-queue'
|
import { JobQueue } from '../../lib/job-queue'
|
||||||
import { CONFIG } from '../../initializers/config'
|
import { CONFIG } from '../../initializers/config'
|
||||||
import { sequelizeTypescript } from '../../initializers/database'
|
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 })
|
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 thumbnailField = req.files['thumbnailfile']
|
||||||
const thumbnailModel = thumbnailField
|
const thumbnailModel = thumbnailField
|
||||||
? await createPlaylistThumbnailFromExisting(thumbnailField[0].path, videoPlaylist)
|
? await createPlaylistMiniatureFromExisting(thumbnailField[0].path, videoPlaylist)
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
const videoPlaylistCreated: VideoPlaylistModel = await sequelizeTypescript.transaction(async t => {
|
const videoPlaylistCreated: VideoPlaylistModel = await sequelizeTypescript.transaction(async t => {
|
||||||
const videoPlaylistCreated = await videoPlaylist.save({ transaction: t })
|
const videoPlaylistCreated = await videoPlaylist.save({ transaction: t })
|
||||||
|
|
||||||
if (thumbnailModel) {
|
if (thumbnailModel) await videoPlaylistCreated.setAndSaveThumbnail(thumbnailModel, t)
|
||||||
thumbnailModel.videoPlaylistId = videoPlaylistCreated.id
|
|
||||||
videoPlaylistCreated.setThumbnail(await thumbnailModel.save({ transaction: t }))
|
|
||||||
}
|
|
||||||
|
|
||||||
// We need more attributes for the federation
|
// We need more attributes for the federation
|
||||||
videoPlaylistCreated.OwnerAccount = await AccountModel.load(user.Account.id, t)
|
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 thumbnailField = req.files['thumbnailfile']
|
||||||
const thumbnailModel = thumbnailField
|
const thumbnailModel = thumbnailField
|
||||||
? await createPlaylistThumbnailFromExisting(thumbnailField[0].path, videoPlaylistInstance)
|
? await createPlaylistMiniatureFromExisting(thumbnailField[0].path, videoPlaylistInstance)
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -239,10 +236,7 @@ async function updateVideoPlaylist (req: express.Request, res: express.Response)
|
||||||
|
|
||||||
const playlistUpdated = await videoPlaylistInstance.save(sequelizeOptions)
|
const playlistUpdated = await videoPlaylistInstance.save(sequelizeOptions)
|
||||||
|
|
||||||
if (thumbnailModel) {
|
if (thumbnailModel) await playlistUpdated.setAndSaveThumbnail(thumbnailModel, t)
|
||||||
thumbnailModel.videoPlaylistId = playlistUpdated.id
|
|
||||||
playlistUpdated.setThumbnail(await thumbnailModel.save({ transaction: t }))
|
|
||||||
}
|
|
||||||
|
|
||||||
const isNewPlaylist = wasPrivatePlaylist && playlistUpdated.privacy !== VideoPlaylistPrivacy.PRIVATE
|
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) {
|
if (playlistElement.position === 1 && videoPlaylist.hasThumbnail() === false) {
|
||||||
logger.info('Generating default thumbnail to playlist %s.', videoPlaylist.url)
|
logger.info('Generating default thumbnail to playlist %s.', videoPlaylist.url)
|
||||||
|
|
||||||
const inputPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnail().filename)
|
const inputPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getMiniature().filename)
|
||||||
const thumbnailModel = await createPlaylistThumbnailFromExisting(inputPath, videoPlaylist, true)
|
const thumbnailModel = await createPlaylistMiniatureFromExisting(inputPath, videoPlaylist, true)
|
||||||
|
|
||||||
thumbnailModel.videoPlaylistId = videoPlaylist.id
|
thumbnailModel.videoPlaylistId = videoPlaylist.id
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ import { move, readFile } from 'fs-extra'
|
||||||
import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist'
|
import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist'
|
||||||
import { CONFIG } from '../../../initializers/config'
|
import { CONFIG } from '../../../initializers/config'
|
||||||
import { sequelizeTypescript } from '../../../initializers/database'
|
import { sequelizeTypescript } from '../../../initializers/database'
|
||||||
import { createVideoThumbnailFromExisting } from '../../../lib/thumbnail'
|
import { createVideoMiniatureFromExisting } from '../../../lib/thumbnail'
|
||||||
import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type'
|
import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type'
|
||||||
import { ThumbnailModel } from '../../../models/video/thumbnail'
|
import { ThumbnailModel } from '../../../models/video/thumbnail'
|
||||||
|
|
||||||
|
@ -204,7 +204,7 @@ async function processThumbnail (req: express.Request, video: VideoModel) {
|
||||||
if (thumbnailField) {
|
if (thumbnailField) {
|
||||||
const thumbnailPhysicalFile = thumbnailField[ 0 ]
|
const thumbnailPhysicalFile = thumbnailField[ 0 ]
|
||||||
|
|
||||||
return createVideoThumbnailFromExisting(thumbnailPhysicalFile.path, video, ThumbnailType.THUMBNAIL)
|
return createVideoMiniatureFromExisting(thumbnailPhysicalFile.path, video, ThumbnailType.MINIATURE)
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined
|
return undefined
|
||||||
|
@ -215,7 +215,7 @@ async function processPreview (req: express.Request, video: VideoModel) {
|
||||||
if (previewField) {
|
if (previewField) {
|
||||||
const previewPhysicalFile = previewField[0]
|
const previewPhysicalFile = previewField[0]
|
||||||
|
|
||||||
return createVideoThumbnailFromExisting(previewPhysicalFile.path, video, ThumbnailType.PREVIEW)
|
return createVideoMiniatureFromExisting(previewPhysicalFile.path, video, ThumbnailType.PREVIEW)
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined
|
return undefined
|
||||||
|
@ -238,14 +238,8 @@ function insertIntoDB (parameters: {
|
||||||
const videoCreated = await video.save(sequelizeOptions)
|
const videoCreated = await video.save(sequelizeOptions)
|
||||||
videoCreated.VideoChannel = videoChannel
|
videoCreated.VideoChannel = videoChannel
|
||||||
|
|
||||||
if (thumbnailModel) {
|
if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t)
|
||||||
thumbnailModel.videoId = videoCreated.id
|
if (previewModel) await videoCreated.addAndSaveThumbnail(previewModel, t)
|
||||||
videoCreated.addThumbnail(await thumbnailModel.save({ transaction: t }))
|
|
||||||
}
|
|
||||||
if (previewModel) {
|
|
||||||
previewModel.videoId = videoCreated.id
|
|
||||||
videoCreated.addThumbnail(await previewModel.save({ transaction: t }))
|
|
||||||
}
|
|
||||||
|
|
||||||
await autoBlacklistVideoIfNeeded(video, videoChannel.Account.User, t)
|
await autoBlacklistVideoIfNeeded(video, videoChannel.Account.User, t)
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ import { Notifier } from '../../../lib/notifier'
|
||||||
import { sendView } from '../../../lib/activitypub/send/send-view'
|
import { sendView } from '../../../lib/activitypub/send/send-view'
|
||||||
import { CONFIG } from '../../../initializers/config'
|
import { CONFIG } from '../../../initializers/config'
|
||||||
import { sequelizeTypescript } from '../../../initializers/database'
|
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'
|
import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type'
|
||||||
|
|
||||||
const auditLogger = auditLoggerFactory('videos')
|
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
|
// Process thumbnail or create it from the video
|
||||||
const thumbnailField = req.files['thumbnailfile']
|
const thumbnailField = req.files['thumbnailfile']
|
||||||
const thumbnailModel = thumbnailField
|
const thumbnailModel = thumbnailField
|
||||||
? await createVideoThumbnailFromExisting(thumbnailField[0].path, video, ThumbnailType.THUMBNAIL)
|
? await createVideoMiniatureFromExisting(thumbnailField[0].path, video, ThumbnailType.MINIATURE)
|
||||||
: await generateVideoThumbnail(video, videoFile, ThumbnailType.THUMBNAIL)
|
: await generateVideoMiniature(video, videoFile, ThumbnailType.MINIATURE)
|
||||||
|
|
||||||
// Process preview or create it from the video
|
// Process preview or create it from the video
|
||||||
const previewField = req.files['previewfile']
|
const previewField = req.files['previewfile']
|
||||||
const previewModel = previewField
|
const previewModel = previewField
|
||||||
? await createVideoThumbnailFromExisting(previewField[0].path, video, ThumbnailType.PREVIEW)
|
? await createVideoMiniatureFromExisting(previewField[0].path, video, ThumbnailType.PREVIEW)
|
||||||
: await generateVideoThumbnail(video, videoFile, ThumbnailType.PREVIEW)
|
: await generateVideoMiniature(video, videoFile, ThumbnailType.PREVIEW)
|
||||||
|
|
||||||
// Create the torrent file
|
// Create the torrent file
|
||||||
await video.createTorrentAndSetInfoHash(videoFile)
|
await video.createTorrentAndSetInfoHash(videoFile)
|
||||||
|
@ -231,11 +231,8 @@ async function addVideo (req: express.Request, res: express.Response) {
|
||||||
|
|
||||||
const videoCreated = await video.save(sequelizeOptions)
|
const videoCreated = await video.save(sequelizeOptions)
|
||||||
|
|
||||||
thumbnailModel.videoId = videoCreated.id
|
await videoCreated.addAndSaveThumbnail(thumbnailModel, t)
|
||||||
previewModel.videoId = videoCreated.id
|
await videoCreated.addAndSaveThumbnail(previewModel, t)
|
||||||
|
|
||||||
videoCreated.addThumbnail(await thumbnailModel.save({ transaction: t }))
|
|
||||||
videoCreated.addThumbnail(await previewModel.save({ transaction: t }))
|
|
||||||
|
|
||||||
// Do not forget to add video channel information to the created video
|
// Do not forget to add video channel information to the created video
|
||||||
videoCreated.VideoChannel = res.locals.videoChannel
|
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
|
// Process thumbnail or create it from the video
|
||||||
const thumbnailModel = req.files && req.files['thumbnailfile']
|
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
|
: undefined
|
||||||
|
|
||||||
const previewModel = req.files && req.files['previewfile']
|
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
|
: undefined
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -346,14 +343,8 @@ async function updateVideo (req: express.Request, res: express.Response) {
|
||||||
|
|
||||||
const videoInstanceUpdated = await videoInstance.save(sequelizeOptions)
|
const videoInstanceUpdated = await videoInstance.save(sequelizeOptions)
|
||||||
|
|
||||||
if (thumbnailModel) {
|
if (thumbnailModel) await videoInstanceUpdated.addAndSaveThumbnail(thumbnailModel, t)
|
||||||
thumbnailModel.videoId = videoInstanceUpdated.id
|
if (previewModel) await videoInstanceUpdated.addAndSaveThumbnail(previewModel, t)
|
||||||
videoInstanceUpdated.addThumbnail(await thumbnailModel.save({ transaction: t }))
|
|
||||||
}
|
|
||||||
if (previewModel) {
|
|
||||||
previewModel.videoId = videoInstanceUpdated.id
|
|
||||||
videoInstanceUpdated.addThumbnail(await previewModel.save({ transaction: t }))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Video tags update?
|
// Video tags update?
|
||||||
if (videoInfoToUpdate.tags !== undefined) {
|
if (videoInfoToUpdate.tags !== undefined) {
|
||||||
|
|
|
@ -85,7 +85,7 @@ async function getSitemapLocalVideoUrls () {
|
||||||
// Sitemap description should be < 2000 characters
|
// Sitemap description should be < 2000 characters
|
||||||
description: truncate(v.description || v.name, { length: 2000, omission: '...' }),
|
description: truncate(v.description || v.name, { length: 2000, omission: '...' }),
|
||||||
player_loc: WEBSERVER.URL + '/videos/embed/' + v.uuid,
|
player_loc: WEBSERVER.URL + '/videos/embed/' + v.uuid,
|
||||||
thumbnail_loc: WEBSERVER.URL + v.getThumbnailStaticPath()
|
thumbnail_loc: WEBSERVER.URL + v.getMiniatureStaticPath()
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}))
|
}))
|
||||||
|
|
|
@ -137,7 +137,7 @@ async function generateVideoFeed (req: express.Request, res: express.Response) {
|
||||||
torrent: torrents,
|
torrent: torrents,
|
||||||
thumbnail: [
|
thumbnail: [
|
||||||
{
|
{
|
||||||
url: WEBSERVER.URL + video.getThumbnailStaticPath(),
|
url: WEBSERVER.URL + video.getMiniatureStaticPath(),
|
||||||
height: THUMBNAILS_SIZE.height,
|
height: THUMBNAILS_SIZE.height,
|
||||||
width: THUMBNAILS_SIZE.width
|
width: THUMBNAILS_SIZE.width
|
||||||
}
|
}
|
||||||
|
|
|
@ -165,20 +165,20 @@ export {
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
async function getPreview (req: express.Request, res: express.Response) {
|
async function getPreview (req: express.Request, res: express.Response) {
|
||||||
const path = await VideosPreviewCache.Instance.getFilePath(req.params.uuid)
|
const result = await VideosPreviewCache.Instance.getFilePath(req.params.uuid)
|
||||||
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 getVideoCaption (req: express.Request, res: express.Response) {
|
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,
|
videoId: req.params.videoId,
|
||||||
language: req.params.captionLanguage
|
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) {
|
async function generateNodeinfo (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||||
|
|
|
@ -140,15 +140,15 @@ async function checkPostgresExtensions () {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkPostgresExtension (extension: string) {
|
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 = {
|
const options = {
|
||||||
type: QueryTypes.SELECT as QueryTypes.SELECT,
|
type: QueryTypes.SELECT as QueryTypes.SELECT,
|
||||||
raw: true
|
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 to create the extension ourselves
|
||||||
try {
|
try {
|
||||||
await sequelizeTypescript.query(`CREATE EXTENSION ${extension};`, { raw: true })
|
await sequelizeTypescript.query(`CREATE EXTENSION ${extension};`, { raw: true })
|
||||||
|
|
|
@ -16,7 +16,8 @@ import { VideoPlaylistElementModel } from '../../models/video/video-playlist-ele
|
||||||
import { VideoModel } from '../../models/video/video'
|
import { VideoModel } from '../../models/video/video'
|
||||||
import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model'
|
import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model'
|
||||||
import { sequelizeTypescript } from '../../initializers/database'
|
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[]) {
|
function playlistObjectToDBAttributes (playlistObject: PlaylistObject, byAccount: AccountModel, to: string[]) {
|
||||||
const privacy = to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 ? VideoPlaylistPrivacy.PUBLIC : VideoPlaylistPrivacy.UNLISTED
|
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 })
|
||||||
const [ playlist ] = (await VideoPlaylistModel.upsert<VideoPlaylistModel>(playlistAttributes, { returning: true }) as any)
|
|
||||||
|
|
||||||
let accItems: string[] = []
|
let accItems: string[] = []
|
||||||
await crawlCollectionPage<string>(playlistObject.id, items => {
|
await crawlCollectionPage<string>(playlistObject.id, items => {
|
||||||
|
@ -100,10 +100,8 @@ async function createOrUpdateVideoPlaylist (playlistObject: PlaylistObject, byAc
|
||||||
|
|
||||||
if (playlistObject.icon) {
|
if (playlistObject.icon) {
|
||||||
try {
|
try {
|
||||||
const thumbnailModel = await createPlaylistThumbnailFromUrl(playlistObject.icon.url, refreshedPlaylist)
|
const thumbnailModel = await createPlaylistMiniatureFromUrl(playlistObject.icon.url, refreshedPlaylist)
|
||||||
thumbnailModel.videoPlaylistId = refreshedPlaylist.id
|
await refreshedPlaylist.setAndSaveThumbnail(thumbnailModel, undefined)
|
||||||
|
|
||||||
refreshedPlaylist.setThumbnail(await thumbnailModel.save())
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.warn('Cannot generate thumbnail of %s.', playlistObject.id, { err })
|
logger.warn('Cannot generate thumbnail of %s.', playlistObject.id, { err })
|
||||||
}
|
}
|
||||||
|
@ -156,7 +154,7 @@ export {
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
async function resetVideoPlaylistElements (elementUrls: string[], playlist: VideoPlaylistModel) {
|
async function resetVideoPlaylistElements (elementUrls: string[], playlist: VideoPlaylistModel) {
|
||||||
const elementsToCreate: object[] = [] // FIXME: sequelize typings
|
const elementsToCreate: FilteredModelAttributes<VideoPlaylistElementModel>[] = []
|
||||||
|
|
||||||
await Bluebird.map(elementUrls, async elementUrl => {
|
await Bluebird.map(elementUrls, async elementUrl => {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -73,8 +73,7 @@ async function addVideoComment (videoInstance: VideoModel, commentUrl: string) {
|
||||||
const entry = await videoCommentActivityObjectToDBAttributes(videoInstance, actor, body)
|
const entry = await videoCommentActivityObjectToDBAttributes(videoInstance, actor, body)
|
||||||
if (!entry) return { created: false }
|
if (!entry) return { created: false }
|
||||||
|
|
||||||
// FIXME: sequelize typings
|
const [ comment, created ] = await VideoCommentModel.upsert<VideoCommentModel>(entry, { returning: true })
|
||||||
const [ comment, created ] = (await VideoCommentModel.upsert<VideoCommentModel>(entry, { returning: true }) as any)
|
|
||||||
comment.Account = actor.Account
|
comment.Account = actor.Account
|
||||||
comment.Video = videoInstance
|
comment.Video = videoInstance
|
||||||
|
|
||||||
|
|
|
@ -49,10 +49,11 @@ import { AccountVideoRateModel } from '../../models/account/account-video-rate'
|
||||||
import { VideoShareModel } from '../../models/video/video-share'
|
import { VideoShareModel } from '../../models/video/video-share'
|
||||||
import { VideoCommentModel } from '../../models/video/video-comment'
|
import { VideoCommentModel } from '../../models/video/video-comment'
|
||||||
import { sequelizeTypescript } from '../../initializers/database'
|
import { sequelizeTypescript } from '../../initializers/database'
|
||||||
import { createPlaceholderThumbnail, createVideoThumbnailFromUrl } from '../thumbnail'
|
import { createPlaceholderThumbnail, createVideoMiniatureFromUrl } from '../thumbnail'
|
||||||
import { ThumbnailModel } from '../../models/video/thumbnail'
|
import { ThumbnailModel } from '../../models/video/thumbnail'
|
||||||
import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type'
|
import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
|
import { FilteredModelAttributes } from '../../typings/sequelize'
|
||||||
|
|
||||||
async function federateVideoIfNeeded (video: VideoModel, isNewVideo: boolean, transaction?: sequelize.Transaction) {
|
async function federateVideoIfNeeded (video: VideoModel, isNewVideo: boolean, transaction?: sequelize.Transaction) {
|
||||||
// If the video is not private and is published, we federate it
|
// If the video is not private and is published, we federate it
|
||||||
|
@ -247,7 +248,7 @@ async function updateVideoFromAP (options: {
|
||||||
let thumbnailModel: ThumbnailModel
|
let thumbnailModel: ThumbnailModel
|
||||||
|
|
||||||
try {
|
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) {
|
} catch (err) {
|
||||||
logger.warn('Cannot generate thumbnail of %s.', options.videoObject.id, { 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)
|
await options.video.save(sequelizeOptions)
|
||||||
|
|
||||||
if (thumbnailModel) {
|
if (thumbnailModel) if (thumbnailModel) await options.video.addAndSaveThumbnail(thumbnailModel, t)
|
||||||
thumbnailModel.videoId = options.video.id
|
|
||||||
options.video.addThumbnail(await thumbnailModel.save({ transaction: t }))
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: use icon URL instead
|
// FIXME: use icon URL instead
|
||||||
const previewUrl = buildRemoteBaseUrl(options.video, join(STATIC_PATHS.PREVIEWS, options.video.getPreview().filename))
|
const previewUrl = buildRemoteBaseUrl(options.video, join(STATIC_PATHS.PREVIEWS, options.video.getPreview().filename))
|
||||||
const previewModel = createPlaceholderThumbnail(previewUrl, options.video, ThumbnailType.PREVIEW, PREVIEWS_SIZE)
|
const previewModel = createPlaceholderThumbnail(previewUrl, options.video, ThumbnailType.PREVIEW, PREVIEWS_SIZE)
|
||||||
|
await options.video.addAndSaveThumbnail(previewModel, t)
|
||||||
options.video.addThumbnail(await previewModel.save({ transaction: t }))
|
|
||||||
|
|
||||||
{
|
{
|
||||||
const videoFileAttributes = videoFileActivityUrlToDBAttributes(options.video, options.videoObject)
|
const videoFileAttributes = videoFileActivityUrlToDBAttributes(options.video, options.videoObject)
|
||||||
|
@ -311,7 +308,7 @@ async function updateVideoFromAP (options: {
|
||||||
|
|
||||||
// Update or add other one
|
// Update or add other one
|
||||||
const upsertTasks = videoFileAttributes.map(a => {
|
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)
|
.then(([ file ]) => file)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -334,8 +331,7 @@ async function updateVideoFromAP (options: {
|
||||||
|
|
||||||
// Update or add other one
|
// Update or add other one
|
||||||
const upsertTasks = streamingPlaylistAttributes.map(a => {
|
const upsertTasks = streamingPlaylistAttributes.map(a => {
|
||||||
// FIXME: sequelize typings
|
return VideoStreamingPlaylistModel.upsert<VideoStreamingPlaylistModel>(a, { returning: true, transaction: t })
|
||||||
return (VideoStreamingPlaylistModel.upsert<VideoStreamingPlaylistModel>(a, { returning: true, transaction: t }) as any)
|
|
||||||
.then(([ streamingPlaylist ]) => streamingPlaylist)
|
.then(([ streamingPlaylist ]) => streamingPlaylist)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -464,7 +460,7 @@ async function createVideo (videoObject: VideoTorrentObject, channelActor: Actor
|
||||||
const videoData = await videoActivityObjectToDBAttributes(channelActor.VideoChannel, videoObject, videoObject.to)
|
const videoData = await videoActivityObjectToDBAttributes(channelActor.VideoChannel, videoObject, videoObject.to)
|
||||||
const video = VideoModel.build(videoData)
|
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
|
let thumbnailModel: ThumbnailModel
|
||||||
if (waitThumbnail === true) {
|
if (waitThumbnail === true) {
|
||||||
|
@ -477,18 +473,12 @@ async function createVideo (videoObject: VideoTorrentObject, channelActor: Actor
|
||||||
const videoCreated = await video.save(sequelizeOptions)
|
const videoCreated = await video.save(sequelizeOptions)
|
||||||
videoCreated.VideoChannel = channelActor.VideoChannel
|
videoCreated.VideoChannel = channelActor.VideoChannel
|
||||||
|
|
||||||
if (thumbnailModel) {
|
if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t)
|
||||||
thumbnailModel.videoId = videoCreated.id
|
|
||||||
|
|
||||||
videoCreated.addThumbnail(await thumbnailModel.save({ transaction: t }))
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: use icon URL instead
|
// FIXME: use icon URL instead
|
||||||
const previewUrl = buildRemoteBaseUrl(videoCreated, join(STATIC_PATHS.PREVIEWS, video.generatePreviewName()))
|
const previewUrl = buildRemoteBaseUrl(videoCreated, join(STATIC_PATHS.PREVIEWS, video.generatePreviewName()))
|
||||||
const previewModel = createPlaceholderThumbnail(previewUrl, video, ThumbnailType.PREVIEW, PREVIEWS_SIZE)
|
const previewModel = createPlaceholderThumbnail(previewUrl, video, ThumbnailType.PREVIEW, PREVIEWS_SIZE)
|
||||||
previewModel.videoId = videoCreated.id
|
if (thumbnailModel) await videoCreated.addAndSaveThumbnail(previewModel, t)
|
||||||
|
|
||||||
videoCreated.addThumbnail(await previewModel.save({ transaction: t }))
|
|
||||||
|
|
||||||
// Process files
|
// Process files
|
||||||
const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoCreated, videoObject)
|
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)
|
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) {
|
for (const fileUrl of fileUrls) {
|
||||||
// Fetch associated magnet uri
|
// Fetch associated magnet uri
|
||||||
const magnet = videoObject.url.find(u => {
|
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[]
|
const playlistUrls = videoObject.url.filter(u => isAPStreamingPlaylistUrlObject(u)) as ActivityPlaylistUrlObject[]
|
||||||
if (playlistUrls.length === 0) return []
|
if (playlistUrls.length === 0) return []
|
||||||
|
|
||||||
const attributes: object[] = [] // FIXME: add typings
|
const attributes: FilteredModelAttributes<VideoStreamingPlaylistModel>[] = []
|
||||||
for (const playlistUrlObject of playlistUrls) {
|
for (const playlistUrlObject of playlistUrls) {
|
||||||
const segmentsSha256UrlObject = playlistUrlObject.tag
|
const segmentsSha256UrlObject = playlistUrlObject.tag
|
||||||
.find(t => {
|
.find(t => {
|
||||||
|
|
|
@ -4,24 +4,28 @@ import { VideoModel } from '../../models/video/video'
|
||||||
import { fetchRemoteVideoStaticFile } from '../activitypub'
|
import { fetchRemoteVideoStaticFile } from '../activitypub'
|
||||||
import * as memoizee from 'memoizee'
|
import * as memoizee from 'memoizee'
|
||||||
|
|
||||||
|
type GetFilePathResult = { isOwned: boolean, path: string } | undefined
|
||||||
|
|
||||||
export abstract class AbstractVideoStaticFileCache <T> {
|
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
|
// 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) {
|
init (max: number, maxAge: number) {
|
||||||
this.getFilePath = memoizee(this.getFilePathImpl, {
|
this.getFilePath = memoizee(this.getFilePathImpl, {
|
||||||
maxAge,
|
maxAge,
|
||||||
max,
|
max,
|
||||||
promise: true,
|
promise: true,
|
||||||
dispose: (value: string) => {
|
dispose: (result: GetFilePathResult) => {
|
||||||
remove(value)
|
if (result.isOwned !== true) {
|
||||||
.then(() => logger.debug('%s evicted from %s', value, this.constructor.name))
|
remove(result.path)
|
||||||
.catch(err => logger.error('Cannot remove %s from cache %s.', value, this.constructor.name, { err }))
|
.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 }))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { VideoModel } from '../../models/video/video'
|
||||||
import { VideoCaptionModel } from '../../models/video/video-caption'
|
import { VideoCaptionModel } from '../../models/video/video-caption'
|
||||||
import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache'
|
import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache'
|
||||||
import { CONFIG } from '../../initializers/config'
|
import { CONFIG } from '../../initializers/config'
|
||||||
|
import { logger } from '../../helpers/logger'
|
||||||
|
|
||||||
type GetPathParam = { videoId: string, language: string }
|
type GetPathParam = { videoId: string, language: string }
|
||||||
|
|
||||||
|
@ -24,13 +25,15 @@ class VideosCaptionCache extends AbstractVideoStaticFileCache <GetPathParam> {
|
||||||
const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(params.videoId, params.language)
|
const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(params.videoId, params.language)
|
||||||
if (!videoCaption) return undefined
|
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
|
const key = params.videoId + VideosCaptionCache.KEY_DELIMITER + params.language
|
||||||
return this.loadRemoteFile(key)
|
return this.loadRemoteFile(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async loadRemoteFile (key: string) {
|
protected async loadRemoteFile (key: string) {
|
||||||
|
logger.debug('Loading remote caption file %s.', key)
|
||||||
|
|
||||||
const [ videoId, language ] = key.split(VideosCaptionCache.KEY_DELIMITER)
|
const [ videoId, language ] = key.split(VideosCaptionCache.KEY_DELIMITER)
|
||||||
|
|
||||||
const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(videoId, language)
|
const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(videoId, language)
|
||||||
|
@ -46,7 +49,9 @@ class VideosCaptionCache extends AbstractVideoStaticFileCache <GetPathParam> {
|
||||||
const remoteStaticPath = videoCaption.getCaptionStaticPath()
|
const remoteStaticPath = videoCaption.getCaptionStaticPath()
|
||||||
const destPath = join(FILES_CACHE.VIDEO_CAPTIONS.DIRECTORY, videoCaption.getCaptionName())
|
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 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ class VideosPreviewCache extends AbstractVideoStaticFileCache <string> {
|
||||||
const video = await VideoModel.loadByUUIDWithFile(videoUUID)
|
const video = await VideoModel.loadByUUIDWithFile(videoUUID)
|
||||||
if (!video) return undefined
|
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)
|
return this.loadRemoteFile(videoUUID)
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,9 @@ class VideosPreviewCache extends AbstractVideoStaticFileCache <string> {
|
||||||
const remoteStaticPath = join(STATIC_PATHS.PREVIEWS, video.getPreview().filename)
|
const remoteStaticPath = join(STATIC_PATHS.PREVIEWS, video.getPreview().filename)
|
||||||
const destPath = join(FILES_CACHE.PREVIEWS.DIRECTORY, 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 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ import { Notifier } from '../../notifier'
|
||||||
import { CONFIG } from '../../../initializers/config'
|
import { CONFIG } from '../../../initializers/config'
|
||||||
import { sequelizeTypescript } from '../../../initializers/database'
|
import { sequelizeTypescript } from '../../../initializers/database'
|
||||||
import { ThumbnailModel } from '../../../models/video/thumbnail'
|
import { ThumbnailModel } from '../../../models/video/thumbnail'
|
||||||
import { createVideoThumbnailFromUrl, generateVideoThumbnail } from '../../thumbnail'
|
import { createVideoMiniatureFromUrl, generateVideoMiniature } from '../../thumbnail'
|
||||||
import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type'
|
import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type'
|
||||||
|
|
||||||
type VideoImportYoutubeDLPayload = {
|
type VideoImportYoutubeDLPayload = {
|
||||||
|
@ -150,17 +150,17 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide
|
||||||
// Process thumbnail
|
// Process thumbnail
|
||||||
let thumbnailModel: ThumbnailModel
|
let thumbnailModel: ThumbnailModel
|
||||||
if (options.downloadThumbnail && options.thumbnailUrl) {
|
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) {
|
} else if (options.generateThumbnail || options.downloadThumbnail) {
|
||||||
thumbnailModel = await generateVideoThumbnail(videoImport.Video, videoFile, ThumbnailType.THUMBNAIL)
|
thumbnailModel = await generateVideoMiniature(videoImport.Video, videoFile, ThumbnailType.MINIATURE)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process preview
|
// Process preview
|
||||||
let previewModel: ThumbnailModel
|
let previewModel: ThumbnailModel
|
||||||
if (options.downloadPreview && options.thumbnailUrl) {
|
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) {
|
} else if (options.generatePreview || options.downloadPreview) {
|
||||||
previewModel = await generateVideoThumbnail(videoImport.Video, videoFile, ThumbnailType.PREVIEW)
|
previewModel = await generateVideoMiniature(videoImport.Video, videoFile, ThumbnailType.PREVIEW)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create torrent
|
// Create torrent
|
||||||
|
@ -180,14 +180,8 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide
|
||||||
video.state = CONFIG.TRANSCODING.ENABLED ? VideoState.TO_TRANSCODE : VideoState.PUBLISHED
|
video.state = CONFIG.TRANSCODING.ENABLED ? VideoState.TO_TRANSCODE : VideoState.PUBLISHED
|
||||||
await video.save({ transaction: t })
|
await video.save({ transaction: t })
|
||||||
|
|
||||||
if (thumbnailModel) {
|
if (thumbnailModel) await video.addAndSaveThumbnail(thumbnailModel, t)
|
||||||
thumbnailModel.videoId = video.id
|
if (previewModel) await video.addAndSaveThumbnail(previewModel, t)
|
||||||
video.addThumbnail(await thumbnailModel.save({ transaction: t }))
|
|
||||||
}
|
|
||||||
if (previewModel) {
|
|
||||||
previewModel.videoId = video.id
|
|
||||||
video.addThumbnail(await previewModel.save({ transaction: t }))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now we can federate the video (reload from database, we need more attributes)
|
// Now we can federate the video (reload from database, we need more attributes)
|
||||||
const videoForFederation = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t)
|
const videoForFederation = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t)
|
||||||
|
|
|
@ -39,6 +39,8 @@ function clearCacheByToken (token: string) {
|
||||||
function getAccessToken (bearerToken: string) {
|
function getAccessToken (bearerToken: string) {
|
||||||
logger.debug('Getting access token (bearerToken: ' + bearerToken + ').')
|
logger.debug('Getting access token (bearerToken: ' + bearerToken + ').')
|
||||||
|
|
||||||
|
if (!bearerToken) return Bluebird.resolve(undefined)
|
||||||
|
|
||||||
if (accessTokenCache[bearerToken] !== undefined) return Bluebird.resolve(accessTokenCache[bearerToken])
|
if (accessTokenCache[bearerToken] !== undefined) return Bluebird.resolve(accessTokenCache[bearerToken])
|
||||||
|
|
||||||
return OAuthTokenModel.getByTokenAndPopulateUser(bearerToken)
|
return OAuthTokenModel.getByTokenAndPopulateUser(bearerToken)
|
||||||
|
|
|
@ -12,37 +12,37 @@ import { VideoPlaylistModel } from '../models/video/video-playlist'
|
||||||
|
|
||||||
type ImageSize = { height: number, width: number }
|
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 { 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)
|
const thumbnailCreator = () => processImage({ path: inputPath }, outputPath, { width, height }, keepOriginal)
|
||||||
return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail })
|
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 { filename, basePath, height, width, existingThumbnail } = buildMetadataFromPlaylist(playlist, size)
|
||||||
const type = ThumbnailType.THUMBNAIL
|
const type = ThumbnailType.MINIATURE
|
||||||
|
|
||||||
const thumbnailCreator = () => downloadImage(url, basePath, filename, { width, height })
|
const thumbnailCreator = () => downloadImage(url, basePath, filename, { width, height })
|
||||||
return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail, url })
|
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 { filename, basePath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size)
|
||||||
const thumbnailCreator = () => downloadImage(url, basePath, filename, { width, height })
|
const thumbnailCreator = () => downloadImage(url, basePath, filename, { width, height })
|
||||||
|
|
||||||
return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail, url })
|
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 { filename, outputPath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size)
|
||||||
const thumbnailCreator = () => processImage({ path: inputPath }, outputPath, { width, height })
|
const thumbnailCreator = () => processImage({ path: inputPath }, outputPath, { width, height })
|
||||||
|
|
||||||
return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail })
|
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 input = video.getVideoFilePath(videoFile)
|
||||||
|
|
||||||
const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type)
|
const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type)
|
||||||
|
@ -68,12 +68,12 @@ function createPlaceholderThumbnail (url: string, video: VideoModel, type: Thumb
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
export {
|
export {
|
||||||
generateVideoThumbnail,
|
generateVideoMiniature,
|
||||||
createVideoThumbnailFromUrl,
|
createVideoMiniatureFromUrl,
|
||||||
createVideoThumbnailFromExisting,
|
createVideoMiniatureFromExisting,
|
||||||
createPlaceholderThumbnail,
|
createPlaceholderThumbnail,
|
||||||
createPlaylistThumbnailFromUrl,
|
createPlaylistMiniatureFromUrl,
|
||||||
createPlaylistThumbnailFromExisting
|
createPlaylistMiniatureFromExisting
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildMetadataFromPlaylist (playlist: VideoPlaylistModel, size: ImageSize) {
|
function buildMetadataFromPlaylist (playlist: VideoPlaylistModel, size: ImageSize) {
|
||||||
|
@ -95,7 +95,7 @@ function buildMetadataFromVideo (video: VideoModel, type: ThumbnailType, size?:
|
||||||
? video.Thumbnails.find(t => t.type === type)
|
? video.Thumbnails.find(t => t.type === type)
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
if (type === ThumbnailType.THUMBNAIL) {
|
if (type === ThumbnailType.MINIATURE) {
|
||||||
const filename = video.generateThumbnailName()
|
const filename = video.generateThumbnailName()
|
||||||
const basePath = CONFIG.STORAGE.THUMBNAILS_DIR
|
const basePath = CONFIG.STORAGE.THUMBNAILS_DIR
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,8 @@ function authenticateSocket (socket: Socket, next: (err?: any) => void) {
|
||||||
|
|
||||||
logger.debug('Checking socket access token %s.', accessToken)
|
logger.debug('Checking socket access token %s.', accessToken)
|
||||||
|
|
||||||
|
if (!accessToken) return next(new Error('No access token provided'))
|
||||||
|
|
||||||
getAccessToken(accessToken)
|
getAccessToken(accessToken)
|
||||||
.then(tokenDB => {
|
.then(tokenDB => {
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
|
|
|
@ -68,6 +68,7 @@ const videosAddValidator = getCommonVideoEditAttributes().concat([
|
||||||
if (!await doesVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req)
|
if (!await doesVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req)
|
||||||
|
|
||||||
const isAble = await user.isAbleToUploadVideo(videoFile)
|
const isAble = await user.isAbleToUploadVideo(videoFile)
|
||||||
|
|
||||||
if (isAble === false) {
|
if (isAble === false) {
|
||||||
res.status(403)
|
res.status(403)
|
||||||
.json({ error: 'The user video quota is exceeded with this video.' })
|
.json({ error: 'The user video quota is exceeded with this video.' })
|
||||||
|
|
|
@ -8,22 +8,22 @@ enum ScopeNames {
|
||||||
WITH_ACCOUNTS = 'WITH_ACCOUNTS'
|
WITH_ACCOUNTS = 'WITH_ACCOUNTS'
|
||||||
}
|
}
|
||||||
|
|
||||||
@Scopes({
|
@Scopes(() => ({
|
||||||
[ScopeNames.WITH_ACCOUNTS]: {
|
[ScopeNames.WITH_ACCOUNTS]: {
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: () => AccountModel,
|
model: AccountModel,
|
||||||
required: true,
|
required: true,
|
||||||
as: 'ByAccount'
|
as: 'ByAccount'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
model: () => AccountModel,
|
model: AccountModel,
|
||||||
required: true,
|
required: true,
|
||||||
as: 'BlockedAccount'
|
as: 'BlockedAccount'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
})
|
}))
|
||||||
|
|
||||||
@Table({
|
@Table({
|
||||||
tableName: 'accountBlocklist',
|
tableName: 'accountBlocklist',
|
||||||
|
@ -83,7 +83,7 @@ export class AccountBlocklistModel extends Model<AccountBlocklistModel> {
|
||||||
attributes: [ 'accountId', 'id' ],
|
attributes: [ 'accountId', 'id' ],
|
||||||
where: {
|
where: {
|
||||||
accountId: {
|
accountId: {
|
||||||
[Op.any]: accountIds
|
[Op.in]: accountIds // FIXME: sequelize ANY seems broken
|
||||||
},
|
},
|
||||||
targetAccountId
|
targetAccountId
|
||||||
},
|
},
|
||||||
|
|
|
@ -33,15 +33,15 @@ export enum ScopeNames {
|
||||||
SUMMARY = 'SUMMARY'
|
SUMMARY = 'SUMMARY'
|
||||||
}
|
}
|
||||||
|
|
||||||
@DefaultScope({
|
@DefaultScope(() => ({
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: () => ActorModel, // Default scope includes avatar and server
|
model: ActorModel, // Default scope includes avatar and server
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
}))
|
||||||
@Scopes({
|
@Scopes(() => ({
|
||||||
[ ScopeNames.SUMMARY ]: (whereActor?: WhereOptions) => {
|
[ ScopeNames.SUMMARY ]: (whereActor?: WhereOptions) => {
|
||||||
return {
|
return {
|
||||||
attributes: [ 'id', 'name' ],
|
attributes: [ 'id', 'name' ],
|
||||||
|
@ -66,7 +66,7 @@ export enum ScopeNames {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}))
|
||||||
@Table({
|
@Table({
|
||||||
tableName: 'account',
|
tableName: 'account',
|
||||||
indexes: [
|
indexes: [
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { isUserNotificationTypeValid } from '../../helpers/custom-validators/use
|
||||||
import { UserModel } from './user'
|
import { UserModel } from './user'
|
||||||
import { VideoModel } from '../video/video'
|
import { VideoModel } from '../video/video'
|
||||||
import { VideoCommentModel } from '../video/video-comment'
|
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 { VideoChannelModel } from '../video/video-channel'
|
||||||
import { AccountModel } from './account'
|
import { AccountModel } from './account'
|
||||||
import { VideoAbuseModel } from '../video/video-abuse'
|
import { VideoAbuseModel } from '../video/video-abuse'
|
||||||
|
@ -24,17 +24,17 @@ enum ScopeNames {
|
||||||
function buildActorWithAvatarInclude () {
|
function buildActorWithAvatarInclude () {
|
||||||
return {
|
return {
|
||||||
attributes: [ 'preferredUsername' ],
|
attributes: [ 'preferredUsername' ],
|
||||||
model: () => ActorModel.unscoped(),
|
model: ActorModel.unscoped(),
|
||||||
required: true,
|
required: true,
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
attributes: [ 'filename' ],
|
attributes: [ 'filename' ],
|
||||||
model: () => AvatarModel.unscoped(),
|
model: AvatarModel.unscoped(),
|
||||||
required: false
|
required: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
attributes: [ 'host' ],
|
attributes: [ 'host' ],
|
||||||
model: () => ServerModel.unscoped(),
|
model: ServerModel.unscoped(),
|
||||||
required: false
|
required: false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -44,7 +44,7 @@ function buildActorWithAvatarInclude () {
|
||||||
function buildVideoInclude (required: boolean) {
|
function buildVideoInclude (required: boolean) {
|
||||||
return {
|
return {
|
||||||
attributes: [ 'id', 'uuid', 'name' ],
|
attributes: [ 'id', 'uuid', 'name' ],
|
||||||
model: () => VideoModel.unscoped(),
|
model: VideoModel.unscoped(),
|
||||||
required
|
required
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ function buildChannelInclude (required: boolean, withActor = false) {
|
||||||
return {
|
return {
|
||||||
required,
|
required,
|
||||||
attributes: [ 'id', 'name' ],
|
attributes: [ 'id', 'name' ],
|
||||||
model: () => VideoChannelModel.unscoped(),
|
model: VideoChannelModel.unscoped(),
|
||||||
include: withActor === true ? [ buildActorWithAvatarInclude() ] : []
|
include: withActor === true ? [ buildActorWithAvatarInclude() ] : []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,12 +62,12 @@ function buildAccountInclude (required: boolean, withActor = false) {
|
||||||
return {
|
return {
|
||||||
required,
|
required,
|
||||||
attributes: [ 'id', 'name' ],
|
attributes: [ 'id', 'name' ],
|
||||||
model: () => AccountModel.unscoped(),
|
model: AccountModel.unscoped(),
|
||||||
include: withActor === true ? [ buildActorWithAvatarInclude() ] : []
|
include: withActor === true ? [ buildActorWithAvatarInclude() ] : []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Scopes({
|
@Scopes(() => ({
|
||||||
[ScopeNames.WITH_ALL]: {
|
[ScopeNames.WITH_ALL]: {
|
||||||
include: [
|
include: [
|
||||||
Object.assign(buildVideoInclude(false), {
|
Object.assign(buildVideoInclude(false), {
|
||||||
|
@ -76,7 +76,7 @@ function buildAccountInclude (required: boolean, withActor = false) {
|
||||||
|
|
||||||
{
|
{
|
||||||
attributes: [ 'id', 'originCommentId' ],
|
attributes: [ 'id', 'originCommentId' ],
|
||||||
model: () => VideoCommentModel.unscoped(),
|
model: VideoCommentModel.unscoped(),
|
||||||
required: false,
|
required: false,
|
||||||
include: [
|
include: [
|
||||||
buildAccountInclude(true, true),
|
buildAccountInclude(true, true),
|
||||||
|
@ -86,56 +86,56 @@ function buildAccountInclude (required: boolean, withActor = false) {
|
||||||
|
|
||||||
{
|
{
|
||||||
attributes: [ 'id' ],
|
attributes: [ 'id' ],
|
||||||
model: () => VideoAbuseModel.unscoped(),
|
model: VideoAbuseModel.unscoped(),
|
||||||
required: false,
|
required: false,
|
||||||
include: [ buildVideoInclude(true) ]
|
include: [ buildVideoInclude(true) ]
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
attributes: [ 'id' ],
|
attributes: [ 'id' ],
|
||||||
model: () => VideoBlacklistModel.unscoped(),
|
model: VideoBlacklistModel.unscoped(),
|
||||||
required: false,
|
required: false,
|
||||||
include: [ buildVideoInclude(true) ]
|
include: [ buildVideoInclude(true) ]
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
attributes: [ 'id', 'magnetUri', 'targetUrl', 'torrentName' ],
|
attributes: [ 'id', 'magnetUri', 'targetUrl', 'torrentName' ],
|
||||||
model: () => VideoImportModel.unscoped(),
|
model: VideoImportModel.unscoped(),
|
||||||
required: false,
|
required: false,
|
||||||
include: [ buildVideoInclude(false) ]
|
include: [ buildVideoInclude(false) ]
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
attributes: [ 'id', 'state' ],
|
attributes: [ 'id', 'state' ],
|
||||||
model: () => ActorFollowModel.unscoped(),
|
model: ActorFollowModel.unscoped(),
|
||||||
required: false,
|
required: false,
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
attributes: [ 'preferredUsername' ],
|
attributes: [ 'preferredUsername' ],
|
||||||
model: () => ActorModel.unscoped(),
|
model: ActorModel.unscoped(),
|
||||||
required: true,
|
required: true,
|
||||||
as: 'ActorFollower',
|
as: 'ActorFollower',
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
attributes: [ 'id', 'name' ],
|
attributes: [ 'id', 'name' ],
|
||||||
model: () => AccountModel.unscoped(),
|
model: AccountModel.unscoped(),
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
attributes: [ 'filename' ],
|
attributes: [ 'filename' ],
|
||||||
model: () => AvatarModel.unscoped(),
|
model: AvatarModel.unscoped(),
|
||||||
required: false
|
required: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
attributes: [ 'host' ],
|
attributes: [ 'host' ],
|
||||||
model: () => ServerModel.unscoped(),
|
model: ServerModel.unscoped(),
|
||||||
required: false
|
required: false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
attributes: [ 'preferredUsername' ],
|
attributes: [ 'preferredUsername' ],
|
||||||
model: () => ActorModel.unscoped(),
|
model: ActorModel.unscoped(),
|
||||||
required: true,
|
required: true,
|
||||||
as: 'ActorFollowing',
|
as: 'ActorFollowing',
|
||||||
include: [
|
include: [
|
||||||
|
@ -147,9 +147,9 @@ function buildAccountInclude (required: boolean, withActor = false) {
|
||||||
},
|
},
|
||||||
|
|
||||||
buildAccountInclude(false, true)
|
buildAccountInclude(false, true)
|
||||||
] as any // FIXME: sequelize typings
|
]
|
||||||
}
|
}
|
||||||
})
|
}))
|
||||||
@Table({
|
@Table({
|
||||||
tableName: 'userNotification',
|
tableName: 'userNotification',
|
||||||
indexes: [
|
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> {
|
export class UserNotificationModel extends Model<UserNotificationModel> {
|
||||||
|
|
||||||
|
@ -357,7 +357,7 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
|
||||||
where: {
|
where: {
|
||||||
userId,
|
userId,
|
||||||
id: {
|
id: {
|
||||||
[Op.any]: notificationIds
|
[Op.in]: notificationIds // FIXME: sequelize ANY seems broken
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import * as Sequelize from 'sequelize'
|
import { FindOptions, literal, Op, QueryTypes } from 'sequelize'
|
||||||
import {
|
import {
|
||||||
AfterDestroy,
|
AfterDestroy,
|
||||||
AfterUpdate,
|
AfterUpdate,
|
||||||
|
@ -56,33 +56,33 @@ enum ScopeNames {
|
||||||
WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL'
|
WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL'
|
||||||
}
|
}
|
||||||
|
|
||||||
@DefaultScope({
|
@DefaultScope(() => ({
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: () => AccountModel,
|
model: AccountModel,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
model: () => UserNotificationSettingModel,
|
model: UserNotificationSettingModel,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
}))
|
||||||
@Scopes({
|
@Scopes(() => ({
|
||||||
[ScopeNames.WITH_VIDEO_CHANNEL]: {
|
[ScopeNames.WITH_VIDEO_CHANNEL]: {
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: () => AccountModel,
|
model: AccountModel,
|
||||||
required: true,
|
required: true,
|
||||||
include: [ () => VideoChannelModel ]
|
include: [ VideoChannelModel ]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
model: () => UserNotificationSettingModel,
|
model: UserNotificationSettingModel,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
] as any // FIXME: sequelize typings
|
]
|
||||||
}
|
}
|
||||||
})
|
}))
|
||||||
@Table({
|
@Table({
|
||||||
tableName: 'user',
|
tableName: 'user',
|
||||||
indexes: [
|
indexes: [
|
||||||
|
@ -233,26 +233,26 @@ export class UserModel extends Model<UserModel> {
|
||||||
let where = undefined
|
let where = undefined
|
||||||
if (search) {
|
if (search) {
|
||||||
where = {
|
where = {
|
||||||
[Sequelize.Op.or]: [
|
[Op.or]: [
|
||||||
{
|
{
|
||||||
email: {
|
email: {
|
||||||
[Sequelize.Op.iLike]: '%' + search + '%'
|
[Op.iLike]: '%' + search + '%'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
username: {
|
username: {
|
||||||
[ Sequelize.Op.iLike ]: '%' + search + '%'
|
[ Op.iLike ]: '%' + search + '%'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const query = {
|
const query: FindOptions = {
|
||||||
attributes: {
|
attributes: {
|
||||||
include: [
|
include: [
|
||||||
[
|
[
|
||||||
Sequelize.literal(
|
literal(
|
||||||
'(' +
|
'(' +
|
||||||
'SELECT COALESCE(SUM("size"), 0) ' +
|
'SELECT COALESCE(SUM("size"), 0) ' +
|
||||||
'FROM (' +
|
'FROM (' +
|
||||||
|
@ -265,7 +265,7 @@ export class UserModel extends Model<UserModel> {
|
||||||
')'
|
')'
|
||||||
),
|
),
|
||||||
'videoQuotaUsed'
|
'videoQuotaUsed'
|
||||||
] as any // FIXME: typings
|
]
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
offset: start,
|
offset: start,
|
||||||
|
@ -291,7 +291,7 @@ export class UserModel extends Model<UserModel> {
|
||||||
const query = {
|
const query = {
|
||||||
where: {
|
where: {
|
||||||
role: {
|
role: {
|
||||||
[Sequelize.Op.in]: roles
|
[Op.in]: roles
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -387,7 +387,7 @@ export class UserModel extends Model<UserModel> {
|
||||||
|
|
||||||
const query = {
|
const query = {
|
||||||
where: {
|
where: {
|
||||||
[ Sequelize.Op.or ]: [ { username }, { email } ]
|
[ Op.or ]: [ { username }, { email } ]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -510,7 +510,7 @@ export class UserModel extends Model<UserModel> {
|
||||||
const query = {
|
const query = {
|
||||||
where: {
|
where: {
|
||||||
username: {
|
username: {
|
||||||
[ Sequelize.Op.like ]: `%${search}%`
|
[ Op.like ]: `%${search}%`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
limit: 10
|
limit: 10
|
||||||
|
@ -591,15 +591,11 @@ export class UserModel extends Model<UserModel> {
|
||||||
|
|
||||||
const uploadedTotal = videoFile.size + totalBytes
|
const uploadedTotal = videoFile.size + totalBytes
|
||||||
const uploadedDaily = videoFile.size + totalBytesDaily
|
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) &&
|
if (this.videoQuotaDaily === -1) return uploadedTotal < this.videoQuota
|
||||||
(uploadedDaily < this.videoQuotaDaily)
|
if (this.videoQuota === -1) return uploadedDaily < this.videoQuotaDaily
|
||||||
|
|
||||||
|
return uploadedTotal < this.videoQuota && uploadedDaily < this.videoQuotaDaily
|
||||||
}
|
}
|
||||||
|
|
||||||
private static generateUserQuotaBaseSQL (where?: string) {
|
private static generateUserQuotaBaseSQL (where?: string) {
|
||||||
|
@ -619,14 +615,14 @@ export class UserModel extends Model<UserModel> {
|
||||||
private static getTotalRawQuery (query: string, userId: number) {
|
private static getTotalRawQuery (query: string, userId: number) {
|
||||||
const options = {
|
const options = {
|
||||||
bind: { userId },
|
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 } ]) => {
|
.then(([ { total } ]) => {
|
||||||
if (total === null) return 0
|
if (total === null) return 0
|
||||||
|
|
||||||
return parseInt(total + '', 10)
|
return parseInt(total, 10)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,46 +56,46 @@ export const unusedActorAttributesForAPI = [
|
||||||
'updatedAt'
|
'updatedAt'
|
||||||
]
|
]
|
||||||
|
|
||||||
@DefaultScope({
|
@DefaultScope(() => ({
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: () => ServerModel,
|
model: ServerModel,
|
||||||
required: false
|
required: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
model: () => AvatarModel,
|
model: AvatarModel,
|
||||||
required: false
|
required: false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
}))
|
||||||
@Scopes({
|
@Scopes(() => ({
|
||||||
[ScopeNames.FULL]: {
|
[ScopeNames.FULL]: {
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: () => AccountModel.unscoped(),
|
model: AccountModel.unscoped(),
|
||||||
required: false
|
required: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
model: () => VideoChannelModel.unscoped(),
|
model: VideoChannelModel.unscoped(),
|
||||||
required: false,
|
required: false,
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: () => AccountModel,
|
model: AccountModel,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
model: () => ServerModel,
|
model: ServerModel,
|
||||||
required: false
|
required: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
model: () => AvatarModel,
|
model: AvatarModel,
|
||||||
required: false
|
required: false
|
||||||
}
|
}
|
||||||
] as any // FIXME: sequelize typings
|
]
|
||||||
}
|
}
|
||||||
})
|
}))
|
||||||
@Table({
|
@Table({
|
||||||
tableName: 'actor',
|
tableName: 'actor',
|
||||||
indexes: [
|
indexes: [
|
||||||
|
@ -131,7 +131,7 @@ export const unusedActorAttributesForAPI = [
|
||||||
export class ActorModel extends Model<ActorModel> {
|
export class ActorModel extends Model<ActorModel> {
|
||||||
|
|
||||||
@AllowNull(false)
|
@AllowNull(false)
|
||||||
@Column({ type: DataType.ENUM(...values(ACTIVITY_PUB_ACTOR_TYPES)) }) // FIXME: sequelize typings
|
@Column(DataType.ENUM(...values(ACTIVITY_PUB_ACTOR_TYPES)))
|
||||||
type: ActivityPubActorType
|
type: ActivityPubActorType
|
||||||
|
|
||||||
@AllowNull(false)
|
@AllowNull(false)
|
||||||
|
@ -280,7 +280,8 @@ export class ActorModel extends Model<ActorModel> {
|
||||||
attributes: [ 'id' ],
|
attributes: [ 'id' ],
|
||||||
model: VideoChannelModel.unscoped(),
|
model: VideoChannelModel.unscoped(),
|
||||||
required: true,
|
required: true,
|
||||||
include: {
|
include: [
|
||||||
|
{
|
||||||
attributes: [ 'id' ],
|
attributes: [ 'id' ],
|
||||||
model: VideoModel.unscoped(),
|
model: VideoModel.unscoped(),
|
||||||
required: true,
|
required: true,
|
||||||
|
@ -288,6 +289,7 @@ export class ActorModel extends Model<ActorModel> {
|
||||||
id: videoId
|
id: videoId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -295,7 +297,7 @@ export class ActorModel extends Model<ActorModel> {
|
||||||
transaction
|
transaction
|
||||||
}
|
}
|
||||||
|
|
||||||
return ActorModel.unscoped().findOne(query as any) // FIXME: typings
|
return ActorModel.unscoped().findOne(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
static isActorUrlExist (url: string) {
|
static isActorUrlExist (url: string) {
|
||||||
|
@ -389,8 +391,7 @@ export class ActorModel extends Model<ActorModel> {
|
||||||
}
|
}
|
||||||
|
|
||||||
static incrementFollows (id: number, column: 'followersCount' | 'followingCount', by: number) {
|
static incrementFollows (id: number, column: 'followersCount' | 'followingCount', by: number) {
|
||||||
// FIXME: typings
|
return ActorModel.increment(column, {
|
||||||
return (ActorModel as any).increment(column, {
|
|
||||||
by,
|
by,
|
||||||
where: {
|
where: {
|
||||||
id
|
id
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import { AllowNull, Column, Default, DefaultScope, HasOne, IsInt, Model, Table } from 'sequelize-typescript'
|
import { AllowNull, Column, Default, DefaultScope, HasOne, IsInt, Model, Table } from 'sequelize-typescript'
|
||||||
import { AccountModel } from '../account/account'
|
import { AccountModel } from '../account/account'
|
||||||
|
|
||||||
@DefaultScope({
|
@DefaultScope(() => ({
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: () => AccountModel,
|
model: AccountModel,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
}))
|
||||||
@Table({
|
@Table({
|
||||||
tableName: 'application'
|
tableName: 'application'
|
||||||
})
|
})
|
||||||
|
|
|
@ -24,10 +24,10 @@ export class OAuthClientModel extends Model<OAuthClientModel> {
|
||||||
@Column
|
@Column
|
||||||
clientSecret: string
|
clientSecret: string
|
||||||
|
|
||||||
@Column({ type: DataType.ARRAY(DataType.STRING) }) // FIXME: sequelize typings
|
@Column(DataType.ARRAY(DataType.STRING))
|
||||||
grants: string[]
|
grants: string[]
|
||||||
|
|
||||||
@Column({ type: DataType.ARRAY(DataType.STRING) }) // FIXME: sequelize typings
|
@Column(DataType.ARRAY(DataType.STRING))
|
||||||
redirectUris: string[]
|
redirectUris: string[]
|
||||||
|
|
||||||
@CreatedAt
|
@CreatedAt
|
||||||
|
|
|
@ -34,30 +34,30 @@ enum ScopeNames {
|
||||||
WITH_USER = 'WITH_USER'
|
WITH_USER = 'WITH_USER'
|
||||||
}
|
}
|
||||||
|
|
||||||
@Scopes({
|
@Scopes(() => ({
|
||||||
[ScopeNames.WITH_USER]: {
|
[ScopeNames.WITH_USER]: {
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: () => UserModel.unscoped(),
|
model: UserModel.unscoped(),
|
||||||
required: true,
|
required: true,
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
attributes: [ 'id' ],
|
attributes: [ 'id' ],
|
||||||
model: () => AccountModel.unscoped(),
|
model: AccountModel.unscoped(),
|
||||||
required: true,
|
required: true,
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
attributes: [ 'id', 'url' ],
|
attributes: [ 'id', 'url' ],
|
||||||
model: () => ActorModel.unscoped(),
|
model: ActorModel.unscoped(),
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
] as any // FIXME: sequelize typings
|
]
|
||||||
}
|
}
|
||||||
})
|
}))
|
||||||
@Table({
|
@Table({
|
||||||
tableName: 'oAuthToken',
|
tableName: 'oAuthToken',
|
||||||
indexes: [
|
indexes: [
|
||||||
|
@ -167,8 +167,10 @@ export class OAuthTokenModel extends Model<OAuthTokenModel> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return OAuthTokenModel.scope(ScopeNames.WITH_USER).findOne(query).then(token => {
|
return OAuthTokenModel.scope(ScopeNames.WITH_USER)
|
||||||
if (token) token['user'] = token.User
|
.findOne(query)
|
||||||
|
.then(token => {
|
||||||
|
if (token) token[ 'user' ] = token.User
|
||||||
|
|
||||||
return token
|
return token
|
||||||
})
|
})
|
||||||
|
|
|
@ -13,7 +13,7 @@ import {
|
||||||
UpdatedAt
|
UpdatedAt
|
||||||
} from 'sequelize-typescript'
|
} from 'sequelize-typescript'
|
||||||
import { ActorModel } from '../activitypub/actor'
|
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 { isActivityPubUrlValid, isUrlValid } from '../../helpers/custom-validators/activitypub/misc'
|
||||||
import { CONSTRAINTS_FIELDS, MIMETYPES } from '../../initializers/constants'
|
import { CONSTRAINTS_FIELDS, MIMETYPES } from '../../initializers/constants'
|
||||||
import { VideoFileModel } from '../video/video-file'
|
import { VideoFileModel } from '../video/video-file'
|
||||||
|
@ -27,7 +27,7 @@ import { ServerModel } from '../server/server'
|
||||||
import { sample } from 'lodash'
|
import { sample } from 'lodash'
|
||||||
import { isTestInstance } from '../../helpers/core-utils'
|
import { isTestInstance } from '../../helpers/core-utils'
|
||||||
import * as Bluebird from 'bluebird'
|
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 { VideoStreamingPlaylistModel } from '../video/video-streaming-playlist'
|
||||||
import { CONFIG } from '../../initializers/config'
|
import { CONFIG } from '../../initializers/config'
|
||||||
|
|
||||||
|
@ -35,32 +35,32 @@ export enum ScopeNames {
|
||||||
WITH_VIDEO = 'WITH_VIDEO'
|
WITH_VIDEO = 'WITH_VIDEO'
|
||||||
}
|
}
|
||||||
|
|
||||||
@Scopes({
|
@Scopes(() => ({
|
||||||
[ ScopeNames.WITH_VIDEO ]: {
|
[ ScopeNames.WITH_VIDEO ]: {
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: () => VideoFileModel,
|
model: VideoFileModel,
|
||||||
required: false,
|
required: false,
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: () => VideoModel,
|
model: VideoModel,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
model: () => VideoStreamingPlaylistModel,
|
model: VideoStreamingPlaylistModel,
|
||||||
required: false,
|
required: false,
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: () => VideoModel,
|
model: VideoModel,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
] as any // FIXME: sequelize typings
|
]
|
||||||
}
|
}
|
||||||
})
|
}))
|
||||||
|
|
||||||
@Table({
|
@Table({
|
||||||
tableName: 'videoRedundancy',
|
tableName: 'videoRedundancy',
|
||||||
|
@ -192,7 +192,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
|
||||||
return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query)
|
return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
static loadByUrl (url: string, transaction?: Sequelize.Transaction) {
|
static loadByUrl (url: string, transaction?: Transaction) {
|
||||||
const query = {
|
const query = {
|
||||||
where: {
|
where: {
|
||||||
url
|
url
|
||||||
|
@ -292,7 +292,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
|
||||||
where: {
|
where: {
|
||||||
privacy: VideoPrivacy.PUBLIC,
|
privacy: VideoPrivacy.PUBLIC,
|
||||||
views: {
|
views: {
|
||||||
[ Sequelize.Op.gte ]: minViews
|
[ Op.gte ]: minViews
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
include: [
|
include: [
|
||||||
|
@ -315,7 +315,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
|
||||||
actorId: actor.id,
|
actorId: actor.id,
|
||||||
strategy,
|
strategy,
|
||||||
createdAt: {
|
createdAt: {
|
||||||
[ Sequelize.Op.lt ]: expiredDate
|
[ Op.lt ]: expiredDate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -326,7 +326,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
|
||||||
static async getTotalDuplicated (strategy: VideoRedundancyStrategy) {
|
static async getTotalDuplicated (strategy: VideoRedundancyStrategy) {
|
||||||
const actor = await getServerActor()
|
const actor = await getServerActor()
|
||||||
|
|
||||||
const options = {
|
const query: FindOptions = {
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
attributes: [],
|
attributes: [],
|
||||||
|
@ -340,12 +340,8 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
return VideoFileModel.sum('size', options as any) // FIXME: typings
|
return VideoFileModel.aggregate('size', 'SUM', query)
|
||||||
.then(v => {
|
.then(result => parseAggregateResult(result))
|
||||||
if (!v || isNaN(v)) return 0
|
|
||||||
|
|
||||||
return v
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static async listLocalExpired () {
|
static async listLocalExpired () {
|
||||||
|
@ -355,7 +351,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
|
||||||
where: {
|
where: {
|
||||||
actorId: actor.id,
|
actorId: actor.id,
|
||||||
expiresOn: {
|
expiresOn: {
|
||||||
[ Sequelize.Op.lt ]: new Date()
|
[ Op.lt ]: new Date()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -369,10 +365,10 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
|
||||||
const query = {
|
const query = {
|
||||||
where: {
|
where: {
|
||||||
actorId: {
|
actorId: {
|
||||||
[Sequelize.Op.ne]: actor.id
|
[Op.ne]: actor.id
|
||||||
},
|
},
|
||||||
expiresOn: {
|
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) {
|
static async getStats (strategy: VideoRedundancyStrategy) {
|
||||||
const actor = await getServerActor()
|
const actor = await getServerActor()
|
||||||
|
|
||||||
const query = {
|
const query: FindOptions = {
|
||||||
raw: true,
|
raw: true,
|
||||||
attributes: [
|
attributes: [
|
||||||
[ Sequelize.fn('COALESCE', Sequelize.fn('SUM', Sequelize.col('VideoFile.size')), '0'), 'totalUsed' ],
|
[ fn('COALESCE', fn('SUM', col('VideoFile.size')), '0'), 'totalUsed' ],
|
||||||
[ Sequelize.fn('COUNT', Sequelize.fn('DISTINCT', Sequelize.col('videoId'))), 'totalVideos' ],
|
[ fn('COUNT', fn('DISTINCT', col('videoId'))), 'totalVideos' ],
|
||||||
[ Sequelize.fn('COUNT', Sequelize.col('videoFileId')), 'totalVideoFiles' ]
|
[ fn('COUNT', col('videoFileId')), 'totalVideoFiles' ]
|
||||||
],
|
],
|
||||||
where: {
|
where: {
|
||||||
strategy,
|
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) => ({
|
.then((r: any) => ({
|
||||||
totalUsed: parseInt(r.totalUsed.toString(), 10),
|
totalUsed: parseAggregateResult(r.totalUsed),
|
||||||
totalVideos: r.totalVideos,
|
totalVideos: r.totalVideos,
|
||||||
totalVideoFiles: r.totalVideoFiles
|
totalVideoFiles: r.totalVideoFiles
|
||||||
}))
|
}))
|
||||||
|
@ -503,7 +499,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
|
||||||
private static async buildVideoFileForDuplication () {
|
private static async buildVideoFileForDuplication () {
|
||||||
const actor = await getServerActor()
|
const actor = await getServerActor()
|
||||||
|
|
||||||
const notIn = Sequelize.literal(
|
const notIn = literal(
|
||||||
'(' +
|
'(' +
|
||||||
`SELECT "videoFileId" FROM "videoRedundancy" WHERE "actorId" = ${actor.id} AND "videoFileId" IS NOT NULL` +
|
`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,
|
required: true,
|
||||||
where: {
|
where: {
|
||||||
id: {
|
id: {
|
||||||
[ Sequelize.Op.notIn ]: notIn
|
[ Op.notIn ]: notIn
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,11 +9,11 @@ enum ScopeNames {
|
||||||
WITH_SERVER = 'WITH_SERVER'
|
WITH_SERVER = 'WITH_SERVER'
|
||||||
}
|
}
|
||||||
|
|
||||||
@Scopes({
|
@Scopes(() => ({
|
||||||
[ScopeNames.WITH_ACCOUNT]: {
|
[ScopeNames.WITH_ACCOUNT]: {
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: () => AccountModel,
|
model: AccountModel,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -21,12 +21,12 @@ enum ScopeNames {
|
||||||
[ScopeNames.WITH_SERVER]: {
|
[ScopeNames.WITH_SERVER]: {
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: () => ServerModel,
|
model: ServerModel,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
})
|
}))
|
||||||
|
|
||||||
@Table({
|
@Table({
|
||||||
tableName: 'serverBlocklist',
|
tableName: 'serverBlocklist',
|
||||||
|
|
|
@ -118,6 +118,15 @@ function buildWhereIdOrUUID (id: number | string) {
|
||||||
return validator.isInt('' + id) ? { id } : { uuid: id }
|
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 {
|
export {
|
||||||
|
@ -131,7 +140,8 @@ export {
|
||||||
buildServerIdsFollowedBy,
|
buildServerIdsFollowedBy,
|
||||||
buildTrigramSearchIndex,
|
buildTrigramSearchIndex,
|
||||||
buildWhereIdOrUUID,
|
buildWhereIdOrUUID,
|
||||||
isOutdated
|
isOutdated,
|
||||||
|
parseAggregateResult
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
|
@ -75,7 +75,7 @@ export class TagModel extends Model<TagModel> {
|
||||||
type: QueryTypes.SELECT as QueryTypes.SELECT
|
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))
|
.then(data => data.map(d => d.name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,8 +75,8 @@ export class ThumbnailModel extends Model<ThumbnailModel> {
|
||||||
updatedAt: Date
|
updatedAt: Date
|
||||||
|
|
||||||
private static types: { [ id in ThumbnailType ]: { label: string, directory: string, staticPath: string } } = {
|
private static types: { [ id in ThumbnailType ]: { label: string, directory: string, staticPath: string } } = {
|
||||||
[ThumbnailType.THUMBNAIL]: {
|
[ThumbnailType.MINIATURE]: {
|
||||||
label: 'thumbnail',
|
label: 'miniature',
|
||||||
directory: CONFIG.STORAGE.THUMBNAILS_DIR,
|
directory: CONFIG.STORAGE.THUMBNAILS_DIR,
|
||||||
staticPath: STATIC_PATHS.THUMBNAILS
|
staticPath: STATIC_PATHS.THUMBNAILS
|
||||||
},
|
},
|
||||||
|
|
|
@ -12,7 +12,7 @@ import {
|
||||||
Table,
|
Table,
|
||||||
UpdatedAt
|
UpdatedAt
|
||||||
} from 'sequelize-typescript'
|
} from 'sequelize-typescript'
|
||||||
import { throwIfNotValid } from '../utils'
|
import { buildWhereIdOrUUID, throwIfNotValid } from '../utils'
|
||||||
import { VideoModel } from './video'
|
import { VideoModel } from './video'
|
||||||
import { isVideoCaptionLanguageValid } from '../../helpers/custom-validators/video-captions'
|
import { isVideoCaptionLanguageValid } from '../../helpers/custom-validators/video-captions'
|
||||||
import { VideoCaption } from '../../../shared/models/videos/caption/video-caption.model'
|
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'
|
WITH_VIDEO_UUID_AND_REMOTE = 'WITH_VIDEO_UUID_AND_REMOTE'
|
||||||
}
|
}
|
||||||
|
|
||||||
@Scopes({
|
@Scopes(() => ({
|
||||||
[ScopeNames.WITH_VIDEO_UUID_AND_REMOTE]: {
|
[ScopeNames.WITH_VIDEO_UUID_AND_REMOTE]: {
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
attributes: [ 'uuid', 'remote' ],
|
attributes: [ 'uuid', 'remote' ],
|
||||||
model: () => VideoModel.unscoped(),
|
model: VideoModel.unscoped(),
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
})
|
}))
|
||||||
|
|
||||||
@Table({
|
@Table({
|
||||||
tableName: 'videoCaption',
|
tableName: 'videoCaption',
|
||||||
|
@ -97,12 +97,9 @@ export class VideoCaptionModel extends Model<VideoCaptionModel> {
|
||||||
const videoInclude = {
|
const videoInclude = {
|
||||||
model: VideoModel.unscoped(),
|
model: VideoModel.unscoped(),
|
||||||
attributes: [ 'id', 'remote', 'uuid' ],
|
attributes: [ 'id', 'remote', 'uuid' ],
|
||||||
where: { }
|
where: buildWhereIdOrUUID(videoId)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof videoId === 'string') videoInclude.where['uuid'] = videoId
|
|
||||||
else videoInclude.where['id'] = videoId
|
|
||||||
|
|
||||||
const query = {
|
const query = {
|
||||||
where: {
|
where: {
|
||||||
language
|
language
|
||||||
|
|
|
@ -23,29 +23,29 @@ enum ScopeNames {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
@Scopes({
|
@Scopes(() => ({
|
||||||
[ScopeNames.FULL]: {
|
[ScopeNames.FULL]: {
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: () => AccountModel,
|
model: AccountModel,
|
||||||
as: 'Initiator',
|
as: 'Initiator',
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
model: () => AccountModel,
|
model: AccountModel,
|
||||||
as: 'NextOwner',
|
as: 'NextOwner',
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
model: () => VideoModel,
|
model: VideoModel,
|
||||||
required: true,
|
required: true,
|
||||||
include: [
|
include: [
|
||||||
{ model: () => VideoFileModel }
|
{ model: VideoFileModel }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
] as any // FIXME: sequelize typings
|
]
|
||||||
}
|
}
|
||||||
})
|
}))
|
||||||
export class VideoChangeOwnershipModel extends Model<VideoChangeOwnershipModel> {
|
export class VideoChangeOwnershipModel extends Model<VideoChangeOwnershipModel> {
|
||||||
@CreatedAt
|
@CreatedAt
|
||||||
createdAt: Date
|
createdAt: Date
|
||||||
|
|
|
@ -58,15 +58,15 @@ type AvailableForListOptions = {
|
||||||
actorId: number
|
actorId: number
|
||||||
}
|
}
|
||||||
|
|
||||||
@DefaultScope({
|
@DefaultScope(() => ({
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: () => ActorModel,
|
model: ActorModel,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
}))
|
||||||
@Scopes({
|
@Scopes(() => ({
|
||||||
[ScopeNames.SUMMARY]: (withAccount = false) => {
|
[ScopeNames.SUMMARY]: (withAccount = false) => {
|
||||||
const base: FindOptions = {
|
const base: FindOptions = {
|
||||||
attributes: [ 'name', 'description', 'id', 'actorId' ],
|
attributes: [ 'name', 'description', 'id', 'actorId' ],
|
||||||
|
@ -142,22 +142,22 @@ type AvailableForListOptions = {
|
||||||
[ScopeNames.WITH_ACCOUNT]: {
|
[ScopeNames.WITH_ACCOUNT]: {
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: () => AccountModel,
|
model: AccountModel,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
[ScopeNames.WITH_VIDEOS]: {
|
[ScopeNames.WITH_VIDEOS]: {
|
||||||
include: [
|
include: [
|
||||||
() => VideoModel
|
VideoModel
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
[ScopeNames.WITH_ACTOR]: {
|
[ScopeNames.WITH_ACTOR]: {
|
||||||
include: [
|
include: [
|
||||||
() => ActorModel
|
ActorModel
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
})
|
}))
|
||||||
@Table({
|
@Table({
|
||||||
tableName: 'videoChannel',
|
tableName: 'videoChannel',
|
||||||
indexes
|
indexes
|
||||||
|
|
|
@ -30,7 +30,7 @@ import { UserModel } from '../account/user'
|
||||||
import { actorNameAlphabet } from '../../helpers/custom-validators/activitypub/actor'
|
import { actorNameAlphabet } from '../../helpers/custom-validators/activitypub/actor'
|
||||||
import { regexpCapture } from '../../helpers/regexp'
|
import { regexpCapture } from '../../helpers/regexp'
|
||||||
import { uniq } from 'lodash'
|
import { uniq } from 'lodash'
|
||||||
import { FindOptions, Op, Order, Sequelize, Transaction } from 'sequelize'
|
import { FindOptions, Op, Order, ScopeOptions, Sequelize, Transaction } from 'sequelize'
|
||||||
|
|
||||||
enum ScopeNames {
|
enum ScopeNames {
|
||||||
WITH_ACCOUNT = 'WITH_ACCOUNT',
|
WITH_ACCOUNT = 'WITH_ACCOUNT',
|
||||||
|
@ -39,7 +39,7 @@ enum ScopeNames {
|
||||||
ATTRIBUTES_FOR_API = 'ATTRIBUTES_FOR_API'
|
ATTRIBUTES_FOR_API = 'ATTRIBUTES_FOR_API'
|
||||||
}
|
}
|
||||||
|
|
||||||
@Scopes({
|
@Scopes(() => ({
|
||||||
[ScopeNames.ATTRIBUTES_FOR_API]: (serverAccountId: number, userAccountId?: number) => {
|
[ScopeNames.ATTRIBUTES_FOR_API]: (serverAccountId: number, userAccountId?: number) => {
|
||||||
return {
|
return {
|
||||||
attributes: {
|
attributes: {
|
||||||
|
@ -63,34 +63,34 @@ enum ScopeNames {
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
} as FindOptions
|
||||||
},
|
},
|
||||||
[ScopeNames.WITH_ACCOUNT]: {
|
[ScopeNames.WITH_ACCOUNT]: {
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: () => AccountModel,
|
model: AccountModel,
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: () => ActorModel,
|
model: ActorModel,
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: () => ServerModel,
|
model: ServerModel,
|
||||||
required: false
|
required: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
model: () => AvatarModel,
|
model: AvatarModel,
|
||||||
required: false
|
required: false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
] as any // FIXME: sequelize typings
|
]
|
||||||
},
|
},
|
||||||
[ScopeNames.WITH_IN_REPLY_TO]: {
|
[ScopeNames.WITH_IN_REPLY_TO]: {
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: () => VideoCommentModel,
|
model: VideoCommentModel,
|
||||||
as: 'InReplyToVideoComment'
|
as: 'InReplyToVideoComment'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -98,19 +98,19 @@ enum ScopeNames {
|
||||||
[ScopeNames.WITH_VIDEO]: {
|
[ScopeNames.WITH_VIDEO]: {
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: () => VideoModel,
|
model: VideoModel,
|
||||||
required: true,
|
required: true,
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: () => VideoChannelModel.unscoped(),
|
model: VideoChannelModel.unscoped(),
|
||||||
required: true,
|
required: true,
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: () => AccountModel,
|
model: AccountModel,
|
||||||
required: true,
|
required: true,
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: () => ActorModel,
|
model: ActorModel,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -119,9 +119,9 @@ enum ScopeNames {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
] as any // FIXME: sequelize typings
|
]
|
||||||
}
|
}
|
||||||
})
|
}))
|
||||||
@Table({
|
@Table({
|
||||||
tableName: 'videoComment',
|
tableName: 'videoComment',
|
||||||
indexes: [
|
indexes: [
|
||||||
|
@ -313,8 +313,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: typings
|
const scopes: (string | ScopeOptions)[] = [
|
||||||
const scopes: any[] = [
|
|
||||||
ScopeNames.WITH_ACCOUNT,
|
ScopeNames.WITH_ACCOUNT,
|
||||||
{
|
{
|
||||||
method: [ ScopeNames.ATTRIBUTES_FOR_API, serverAccountId, userAccountId ]
|
method: [ ScopeNames.ATTRIBUTES_FOR_API, serverAccountId, userAccountId ]
|
||||||
|
|
|
@ -19,11 +19,11 @@ import {
|
||||||
isVideoFileSizeValid,
|
isVideoFileSizeValid,
|
||||||
isVideoFPSResolutionValid
|
isVideoFPSResolutionValid
|
||||||
} from '../../helpers/custom-validators/videos'
|
} from '../../helpers/custom-validators/videos'
|
||||||
import { throwIfNotValid } from '../utils'
|
import { parseAggregateResult, throwIfNotValid } from '../utils'
|
||||||
import { VideoModel } from './video'
|
import { VideoModel } from './video'
|
||||||
import * as Sequelize from 'sequelize'
|
|
||||||
import { VideoRedundancyModel } from '../redundancy/video-redundancy'
|
import { VideoRedundancyModel } from '../redundancy/video-redundancy'
|
||||||
import { VideoStreamingPlaylistModel } from './video-streaming-playlist'
|
import { VideoStreamingPlaylistModel } from './video-streaming-playlist'
|
||||||
|
import { FindOptions, QueryTypes, Transaction } from 'sequelize'
|
||||||
|
|
||||||
@Table({
|
@Table({
|
||||||
tableName: 'videoFile',
|
tableName: 'videoFile',
|
||||||
|
@ -97,15 +97,13 @@ export class VideoFileModel extends Model<VideoFileModel> {
|
||||||
static doesInfohashExist (infoHash: string) {
|
static doesInfohashExist (infoHash: string) {
|
||||||
const query = 'SELECT 1 FROM "videoFile" WHERE "infoHash" = $infoHash LIMIT 1'
|
const query = 'SELECT 1 FROM "videoFile" WHERE "infoHash" = $infoHash LIMIT 1'
|
||||||
const options = {
|
const options = {
|
||||||
type: Sequelize.QueryTypes.SELECT,
|
type: QueryTypes.SELECT,
|
||||||
bind: { infoHash },
|
bind: { infoHash },
|
||||||
raw: true
|
raw: true
|
||||||
}
|
}
|
||||||
|
|
||||||
return VideoModel.sequelize.query(query, options)
|
return VideoModel.sequelize.query(query, options)
|
||||||
.then(results => {
|
.then(results => results.length === 1)
|
||||||
return results.length === 1
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static loadWithVideo (id: number) {
|
static loadWithVideo (id: number) {
|
||||||
|
@ -121,7 +119,7 @@ export class VideoFileModel extends Model<VideoFileModel> {
|
||||||
return VideoFileModel.findByPk(id, options)
|
return VideoFileModel.findByPk(id, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
static listByStreamingPlaylist (streamingPlaylistId: number, transaction: Sequelize.Transaction) {
|
static listByStreamingPlaylist (streamingPlaylistId: number, transaction: Transaction) {
|
||||||
const query = {
|
const query = {
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
|
@ -144,8 +142,8 @@ export class VideoFileModel extends Model<VideoFileModel> {
|
||||||
return VideoFileModel.findAll(query)
|
return VideoFileModel.findAll(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getStats () {
|
static getStats () {
|
||||||
let totalLocalVideoFilesSize = await VideoFileModel.sum('size', {
|
const query: FindOptions = {
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
attributes: [],
|
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) {
|
hasSameUniqueKeysThan (other: VideoFileModel) {
|
||||||
|
|
|
@ -59,7 +59,7 @@ function videoModelToFormattedJSON (video: VideoModel, options?: VideoFormatting
|
||||||
views: video.views,
|
views: video.views,
|
||||||
likes: video.likes,
|
likes: video.likes,
|
||||||
dislikes: video.dislikes,
|
dislikes: video.dislikes,
|
||||||
thumbnailPath: video.getThumbnailStaticPath(),
|
thumbnailPath: video.getMiniatureStaticPath(),
|
||||||
previewPath: video.getPreviewStaticPath(),
|
previewPath: video.getPreviewStaticPath(),
|
||||||
embedPath: video.getEmbedStaticPath(),
|
embedPath: video.getEmbedStaticPath(),
|
||||||
createdAt: video.createdAt,
|
createdAt: video.createdAt,
|
||||||
|
@ -301,6 +301,8 @@ function videoModelToActivityPubObject (video: VideoModel): VideoTorrentObject {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const miniature = video.getMiniature()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'Video' as 'Video',
|
type: 'Video' as 'Video',
|
||||||
id: video.url,
|
id: video.url,
|
||||||
|
@ -326,10 +328,10 @@ function videoModelToActivityPubObject (video: VideoModel): VideoTorrentObject {
|
||||||
subtitleLanguage,
|
subtitleLanguage,
|
||||||
icon: {
|
icon: {
|
||||||
type: 'Image',
|
type: 'Image',
|
||||||
url: video.getThumbnail().getUrl(),
|
url: miniature.getUrl(),
|
||||||
mediaType: 'image/jpeg',
|
mediaType: 'image/jpeg',
|
||||||
width: video.getThumbnail().width,
|
width: miniature.width,
|
||||||
height: video.getThumbnail().height
|
height: miniature.height
|
||||||
},
|
},
|
||||||
url,
|
url,
|
||||||
likes: getVideoLikesActivityPubUrl(video),
|
likes: getVideoLikesActivityPubUrl(video),
|
||||||
|
|
|
@ -21,18 +21,18 @@ import { VideoImport, VideoImportState } from '../../../shared'
|
||||||
import { isVideoMagnetUriValid } from '../../helpers/custom-validators/videos'
|
import { isVideoMagnetUriValid } from '../../helpers/custom-validators/videos'
|
||||||
import { UserModel } from '../account/user'
|
import { UserModel } from '../account/user'
|
||||||
|
|
||||||
@DefaultScope({
|
@DefaultScope(() => ({
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: () => UserModel.unscoped(),
|
model: UserModel.unscoped(),
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
model: () => VideoModel.scope([ VideoModelScopeNames.WITH_ACCOUNT_DETAILS, VideoModelScopeNames.WITH_TAGS]),
|
model: VideoModel.scope([ VideoModelScopeNames.WITH_ACCOUNT_DETAILS, VideoModelScopeNames.WITH_TAGS]),
|
||||||
required: false
|
required: false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
}))
|
||||||
|
|
||||||
@Table({
|
@Table({
|
||||||
tableName: 'videoImport',
|
tableName: 'videoImport',
|
||||||
|
|
|
@ -42,7 +42,7 @@ import { activityPubCollectionPagination } from '../../helpers/activitypub'
|
||||||
import { VideoPlaylistType } from '../../../shared/models/videos/playlist/video-playlist-type.model'
|
import { VideoPlaylistType } from '../../../shared/models/videos/playlist/video-playlist-type.model'
|
||||||
import { ThumbnailModel } from './thumbnail'
|
import { ThumbnailModel } from './thumbnail'
|
||||||
import { ActivityIconObject } from '../../../shared/models/activitypub/objects'
|
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 {
|
enum ScopeNames {
|
||||||
AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST',
|
AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST',
|
||||||
|
@ -61,11 +61,11 @@ type AvailableForListOptions = {
|
||||||
privateAndUnlisted?: boolean
|
privateAndUnlisted?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@Scopes({
|
@Scopes(() => ({
|
||||||
[ ScopeNames.WITH_THUMBNAIL ]: {
|
[ ScopeNames.WITH_THUMBNAIL ]: {
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: () => ThumbnailModel,
|
model: ThumbnailModel,
|
||||||
required: false
|
required: false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -73,21 +73,17 @@ type AvailableForListOptions = {
|
||||||
[ ScopeNames.WITH_VIDEOS_LENGTH ]: {
|
[ ScopeNames.WITH_VIDEOS_LENGTH ]: {
|
||||||
attributes: {
|
attributes: {
|
||||||
include: [
|
include: [
|
||||||
[
|
|
||||||
fn('COUNT', 'toto'),
|
|
||||||
'coucou'
|
|
||||||
],
|
|
||||||
[
|
[
|
||||||
literal('(SELECT COUNT("id") FROM "videoPlaylistElement" WHERE "videoPlaylistId" = "VideoPlaylistModel"."id")'),
|
literal('(SELECT COUNT("id") FROM "videoPlaylistElement" WHERE "videoPlaylistId" = "VideoPlaylistModel"."id")'),
|
||||||
'videosLength'
|
'videosLength'
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
} as FindOptions,
|
||||||
[ ScopeNames.WITH_ACCOUNT ]: {
|
[ ScopeNames.WITH_ACCOUNT ]: {
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: () => AccountModel,
|
model: AccountModel,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -95,11 +91,11 @@ type AvailableForListOptions = {
|
||||||
[ ScopeNames.WITH_ACCOUNT_AND_CHANNEL_SUMMARY ]: {
|
[ ScopeNames.WITH_ACCOUNT_AND_CHANNEL_SUMMARY ]: {
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: () => AccountModel.scope(AccountScopeNames.SUMMARY),
|
model: AccountModel.scope(AccountScopeNames.SUMMARY),
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
model: () => VideoChannelModel.scope(VideoChannelScopeNames.SUMMARY),
|
model: VideoChannelModel.scope(VideoChannelScopeNames.SUMMARY),
|
||||||
required: false
|
required: false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -107,11 +103,11 @@ type AvailableForListOptions = {
|
||||||
[ ScopeNames.WITH_ACCOUNT_AND_CHANNEL ]: {
|
[ ScopeNames.WITH_ACCOUNT_AND_CHANNEL ]: {
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: () => AccountModel,
|
model: AccountModel,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
model: () => VideoChannelModel,
|
model: VideoChannelModel,
|
||||||
required: false
|
required: false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -132,7 +128,7 @@ type AvailableForListOptions = {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
const whereAnd: any[] = []
|
const whereAnd: WhereOptions[] = []
|
||||||
|
|
||||||
if (options.privateAndUnlisted !== true) {
|
if (options.privateAndUnlisted !== true) {
|
||||||
whereAnd.push({
|
whereAnd.push({
|
||||||
|
@ -178,9 +174,9 @@ type AvailableForListOptions = {
|
||||||
required: false
|
required: false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
} as FindOptions
|
||||||
}
|
}
|
||||||
}
|
}))
|
||||||
})
|
|
||||||
|
|
||||||
@Table({
|
@Table({
|
||||||
tableName: 'videoPlaylist',
|
tableName: 'videoPlaylist',
|
||||||
|
@ -269,6 +265,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
|
||||||
VideoPlaylistElements: VideoPlaylistElementModel[]
|
VideoPlaylistElements: VideoPlaylistElementModel[]
|
||||||
|
|
||||||
@HasOne(() => ThumbnailModel, {
|
@HasOne(() => ThumbnailModel, {
|
||||||
|
|
||||||
foreignKey: {
|
foreignKey: {
|
||||||
name: 'videoPlaylistId',
|
name: 'videoPlaylistId',
|
||||||
allowNull: true
|
allowNull: true
|
||||||
|
@ -294,7 +291,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
|
||||||
order: getSort(options.sort)
|
order: getSort(options.sort)
|
||||||
}
|
}
|
||||||
|
|
||||||
const scopes = [
|
const scopes: (string | ScopeOptions)[] = [
|
||||||
{
|
{
|
||||||
method: [
|
method: [
|
||||||
ScopeNames.AVAILABLE_FOR_LIST,
|
ScopeNames.AVAILABLE_FOR_LIST,
|
||||||
|
@ -306,7 +303,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
|
||||||
privateAndUnlisted: options.privateAndUnlisted
|
privateAndUnlisted: options.privateAndUnlisted
|
||||||
} as AvailableForListOptions
|
} as AvailableForListOptions
|
||||||
]
|
]
|
||||||
} as any, // FIXME: typings
|
},
|
||||||
ScopeNames.WITH_VIDEOS_LENGTH,
|
ScopeNames.WITH_VIDEOS_LENGTH,
|
||||||
ScopeNames.WITH_THUMBNAIL
|
ScopeNames.WITH_THUMBNAIL
|
||||||
]
|
]
|
||||||
|
@ -348,7 +345,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
|
||||||
model: VideoPlaylistElementModel.unscoped(),
|
model: VideoPlaylistElementModel.unscoped(),
|
||||||
where: {
|
where: {
|
||||||
videoId: {
|
videoId: {
|
||||||
[Op.any]: videoIds
|
[Op.in]: videoIds // FIXME: sequelize ANY seems broken
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
required: true
|
required: true
|
||||||
|
@ -427,12 +424,10 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
|
||||||
return VideoPlaylistModel.update({ privacy: VideoPlaylistPrivacy.PRIVATE, videoChannelId: null }, query)
|
return VideoPlaylistModel.update({ privacy: VideoPlaylistPrivacy.PRIVATE, videoChannelId: null }, query)
|
||||||
}
|
}
|
||||||
|
|
||||||
setThumbnail (thumbnail: ThumbnailModel) {
|
async setAndSaveThumbnail (thumbnail: ThumbnailModel, t: Transaction) {
|
||||||
this.Thumbnail = thumbnail
|
thumbnail.videoPlaylistId = this.id
|
||||||
}
|
|
||||||
|
|
||||||
getThumbnail () {
|
this.Thumbnail = await thumbnail.save({ transaction: t })
|
||||||
return this.Thumbnail
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hasThumbnail () {
|
hasThumbnail () {
|
||||||
|
@ -448,13 +443,13 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
|
||||||
getThumbnailUrl () {
|
getThumbnailUrl () {
|
||||||
if (!this.hasThumbnail()) return null
|
if (!this.hasThumbnail()) return null
|
||||||
|
|
||||||
return WEBSERVER.URL + STATIC_PATHS.THUMBNAILS + this.getThumbnail().filename
|
return WEBSERVER.URL + STATIC_PATHS.THUMBNAILS + this.Thumbnail.filename
|
||||||
}
|
}
|
||||||
|
|
||||||
getThumbnailStaticPath () {
|
getThumbnailStaticPath () {
|
||||||
if (!this.hasThumbnail()) return null
|
if (!this.hasThumbnail()) return null
|
||||||
|
|
||||||
return join(STATIC_PATHS.THUMBNAILS, this.getThumbnail().filename)
|
return join(STATIC_PATHS.THUMBNAILS, this.Thumbnail.filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
setAsRefreshed () {
|
setAsRefreshed () {
|
||||||
|
|
|
@ -14,15 +14,15 @@ enum ScopeNames {
|
||||||
WITH_ACTOR = 'WITH_ACTOR'
|
WITH_ACTOR = 'WITH_ACTOR'
|
||||||
}
|
}
|
||||||
|
|
||||||
@Scopes({
|
@Scopes(() => ({
|
||||||
[ScopeNames.FULL]: {
|
[ScopeNames.FULL]: {
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: () => ActorModel,
|
model: ActorModel,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
model: () => VideoModel,
|
model: VideoModel,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -30,12 +30,12 @@ enum ScopeNames {
|
||||||
[ScopeNames.WITH_ACTOR]: {
|
[ScopeNames.WITH_ACTOR]: {
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: () => ActorModel,
|
model: ActorModel,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
})
|
}))
|
||||||
@Table({
|
@Table({
|
||||||
tableName: 'videoShare',
|
tableName: 'videoShare',
|
||||||
indexes: [
|
indexes: [
|
||||||
|
|
|
@ -26,7 +26,7 @@ import { QueryTypes, Op } from 'sequelize'
|
||||||
fields: [ 'p2pMediaLoaderInfohashes' ],
|
fields: [ 'p2pMediaLoaderInfohashes' ],
|
||||||
using: 'gin'
|
using: 'gin'
|
||||||
}
|
}
|
||||||
] as any // FIXME: sequelize typings
|
]
|
||||||
})
|
})
|
||||||
export class VideoStreamingPlaylistModel extends Model<VideoStreamingPlaylistModel> {
|
export class VideoStreamingPlaylistModel extends Model<VideoStreamingPlaylistModel> {
|
||||||
@CreatedAt
|
@CreatedAt
|
||||||
|
@ -46,7 +46,7 @@ export class VideoStreamingPlaylistModel extends Model<VideoStreamingPlaylistMod
|
||||||
|
|
||||||
@AllowNull(false)
|
@AllowNull(false)
|
||||||
@Is('VideoStreamingPlaylistInfoHashes', value => throwIfNotValid(value, v => isArrayOf(v, isVideoFileInfoHashValid), 'info hashes'))
|
@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[]
|
p2pMediaLoaderInfohashes: string[]
|
||||||
|
|
||||||
@AllowNull(false)
|
@AllowNull(false)
|
||||||
|
@ -87,7 +87,7 @@ export class VideoStreamingPlaylistModel extends Model<VideoStreamingPlaylistMod
|
||||||
raw: true
|
raw: true
|
||||||
}
|
}
|
||||||
|
|
||||||
return VideoModel.sequelize.query<any>(query, options)
|
return VideoModel.sequelize.query<object>(query, options)
|
||||||
.then(results => results.length === 1)
|
.then(results => results.length === 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -227,12 +227,12 @@ type AvailableForListIDsOptions = {
|
||||||
historyOfUser?: UserModel
|
historyOfUser?: UserModel
|
||||||
}
|
}
|
||||||
|
|
||||||
@Scopes({
|
@Scopes(() => ({
|
||||||
[ ScopeNames.FOR_API ]: (options: ForAPIOptions) => {
|
[ ScopeNames.FOR_API ]: (options: ForAPIOptions) => {
|
||||||
const query: FindOptions = {
|
const query: FindOptions = {
|
||||||
where: {
|
where: {
|
||||||
id: {
|
id: {
|
||||||
[ Op.in ]: options.ids // FIXME: sequelize any seems broken
|
[ Op.in ]: options.ids // FIXME: sequelize ANY seems broken
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
include: [
|
include: [
|
||||||
|
@ -486,7 +486,7 @@ type AvailableForListIDsOptions = {
|
||||||
[ ScopeNames.WITH_THUMBNAILS ]: {
|
[ ScopeNames.WITH_THUMBNAILS ]: {
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: () => ThumbnailModel,
|
model: ThumbnailModel,
|
||||||
required: false
|
required: false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -495,48 +495,48 @@ type AvailableForListIDsOptions = {
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
attributes: [ 'accountId' ],
|
attributes: [ 'accountId' ],
|
||||||
model: () => VideoChannelModel.unscoped(),
|
model: VideoChannelModel.unscoped(),
|
||||||
required: true,
|
required: true,
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
attributes: [ 'userId' ],
|
attributes: [ 'userId' ],
|
||||||
model: () => AccountModel.unscoped(),
|
model: AccountModel.unscoped(),
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
] as any // FIXME: sequelize typings
|
]
|
||||||
},
|
},
|
||||||
[ ScopeNames.WITH_ACCOUNT_DETAILS ]: {
|
[ ScopeNames.WITH_ACCOUNT_DETAILS ]: {
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: () => VideoChannelModel.unscoped(),
|
model: VideoChannelModel.unscoped(),
|
||||||
required: true,
|
required: true,
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
attributes: {
|
attributes: {
|
||||||
exclude: [ 'privateKey', 'publicKey' ]
|
exclude: [ 'privateKey', 'publicKey' ]
|
||||||
},
|
},
|
||||||
model: () => ActorModel.unscoped(),
|
model: ActorModel.unscoped(),
|
||||||
required: true,
|
required: true,
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
attributes: [ 'host' ],
|
attributes: [ 'host' ],
|
||||||
model: () => ServerModel.unscoped(),
|
model: ServerModel.unscoped(),
|
||||||
required: false
|
required: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
model: () => AvatarModel.unscoped(),
|
model: AvatarModel.unscoped(),
|
||||||
required: false
|
required: false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
model: () => AccountModel.unscoped(),
|
model: AccountModel.unscoped(),
|
||||||
required: true,
|
required: true,
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: () => ActorModel.unscoped(),
|
model: ActorModel.unscoped(),
|
||||||
attributes: {
|
attributes: {
|
||||||
exclude: [ 'privateKey', 'publicKey' ]
|
exclude: [ 'privateKey', 'publicKey' ]
|
||||||
},
|
},
|
||||||
|
@ -544,11 +544,11 @@ type AvailableForListIDsOptions = {
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
attributes: [ 'host' ],
|
attributes: [ 'host' ],
|
||||||
model: () => ServerModel.unscoped(),
|
model: ServerModel.unscoped(),
|
||||||
required: false
|
required: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
model: () => AvatarModel.unscoped(),
|
model: AvatarModel.unscoped(),
|
||||||
required: false
|
required: false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -557,16 +557,16 @@ type AvailableForListIDsOptions = {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
] as any // FIXME: sequelize typings
|
]
|
||||||
},
|
},
|
||||||
[ ScopeNames.WITH_TAGS ]: {
|
[ ScopeNames.WITH_TAGS ]: {
|
||||||
include: [ () => TagModel ]
|
include: [ TagModel ]
|
||||||
},
|
},
|
||||||
[ ScopeNames.WITH_BLACKLISTED ]: {
|
[ ScopeNames.WITH_BLACKLISTED ]: {
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
attributes: [ 'id', 'reason' ],
|
attributes: [ 'id', 'reason' ],
|
||||||
model: () => VideoBlacklistModel,
|
model: VideoBlacklistModel,
|
||||||
required: false
|
required: false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -588,8 +588,7 @@ type AvailableForListIDsOptions = {
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: VideoFileModel.unscoped(),
|
model: VideoFileModel.unscoped(),
|
||||||
// FIXME: typings
|
separate: true, // We may have multiple files, having multiple redundancies so let's separate this join
|
||||||
[ 'separate' as any ]: true, // We may have multiple files, having multiple redundancies so let's separate this join
|
|
||||||
required: false,
|
required: false,
|
||||||
include: subInclude
|
include: subInclude
|
||||||
}
|
}
|
||||||
|
@ -613,8 +612,7 @@ type AvailableForListIDsOptions = {
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: VideoStreamingPlaylistModel.unscoped(),
|
model: VideoStreamingPlaylistModel.unscoped(),
|
||||||
// FIXME: typings
|
separate: true, // We may have multiple streaming playlists, having multiple redundancies so let's separate this join
|
||||||
[ 'separate' as any ]: true, // We may have multiple streaming playlists, having multiple redundancies so let's separate this join
|
|
||||||
required: false,
|
required: false,
|
||||||
include: subInclude
|
include: subInclude
|
||||||
}
|
}
|
||||||
|
@ -624,7 +622,7 @@ type AvailableForListIDsOptions = {
|
||||||
[ ScopeNames.WITH_SCHEDULED_UPDATE ]: {
|
[ ScopeNames.WITH_SCHEDULED_UPDATE ]: {
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: () => ScheduleVideoUpdateModel.unscoped(),
|
model: ScheduleVideoUpdateModel.unscoped(),
|
||||||
required: false
|
required: false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -643,7 +641,7 @@ type AvailableForListIDsOptions = {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}))
|
||||||
@Table({
|
@Table({
|
||||||
tableName: 'video',
|
tableName: 'video',
|
||||||
indexes
|
indexes
|
||||||
|
@ -1075,15 +1073,14 @@ export class VideoModel extends Model<VideoModel> {
|
||||||
}
|
}
|
||||||
|
|
||||||
return Bluebird.all([
|
return Bluebird.all([
|
||||||
// FIXME: typing issue
|
VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findAll(query),
|
||||||
VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findAll(query as any),
|
VideoModel.sequelize.query<{ total: string }>(rawCountQuery, { type: QueryTypes.SELECT })
|
||||||
VideoModel.sequelize.query<{ total: number }>(rawCountQuery, { type: QueryTypes.SELECT })
|
|
||||||
]).then(([ rows, totals ]) => {
|
]).then(([ rows, totals ]) => {
|
||||||
// totals: totalVideos + totalVideoShares
|
// totals: totalVideos + totalVideoShares
|
||||||
let totalVideos = 0
|
let totalVideos = 0
|
||||||
let totalVideoShares = 0
|
let totalVideoShares = 0
|
||||||
if (totals[ 0 ]) totalVideos = parseInt(totals[ 0 ].total + '', 10)
|
if (totals[ 0 ]) totalVideos = parseInt(totals[ 0 ].total, 10)
|
||||||
if (totals[ 1 ]) totalVideoShares = parseInt(totals[ 1 ].total + '', 10)
|
if (totals[ 1 ]) totalVideoShares = parseInt(totals[ 1 ].total, 10)
|
||||||
|
|
||||||
const total = totalVideos + totalVideoShares
|
const total = totalVideos + totalVideoShares
|
||||||
return {
|
return {
|
||||||
|
@ -1094,7 +1091,8 @@ export class VideoModel extends Model<VideoModel> {
|
||||||
}
|
}
|
||||||
|
|
||||||
static listUserVideosForApi (accountId: number, start: number, count: number, sort: string, withFiles = false) {
|
static listUserVideosForApi (accountId: number, start: number, count: number, sort: string, withFiles = false) {
|
||||||
const query: FindOptions = {
|
function buildBaseQuery (): FindOptions {
|
||||||
|
return {
|
||||||
offset: start,
|
offset: start,
|
||||||
limit: count,
|
limit: count,
|
||||||
order: getVideoSort(sort),
|
order: getVideoSort(sort),
|
||||||
|
@ -1111,28 +1109,35 @@ export class VideoModel extends Model<VideoModel> {
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
model: ScheduleVideoUpdateModel,
|
|
||||||
required: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
model: VideoBlacklistModel,
|
|
||||||
required: false
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const countQuery = buildBaseQuery()
|
||||||
|
const findQuery = buildBaseQuery()
|
||||||
|
|
||||||
|
findQuery.include.push({
|
||||||
|
model: ScheduleVideoUpdateModel,
|
||||||
|
required: false
|
||||||
|
})
|
||||||
|
|
||||||
|
findQuery.include.push({
|
||||||
|
model: VideoBlacklistModel,
|
||||||
|
required: false
|
||||||
|
})
|
||||||
|
|
||||||
if (withFiles === true) {
|
if (withFiles === true) {
|
||||||
query.include.push({
|
findQuery.include.push({
|
||||||
model: VideoFileModel.unscoped(),
|
model: VideoFileModel.unscoped(),
|
||||||
required: true
|
required: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return VideoModel.scope(ScopeNames.WITH_THUMBNAILS)
|
return Promise.all([
|
||||||
.findAndCountAll(query)
|
VideoModel.count(countQuery),
|
||||||
.then(({ rows, count }) => {
|
VideoModel.findAll(findQuery)
|
||||||
|
]).then(([ count, rows ]) => {
|
||||||
return {
|
return {
|
||||||
data: rows,
|
data: rows,
|
||||||
total: count
|
total: count
|
||||||
|
@ -1404,12 +1409,12 @@ export class VideoModel extends Model<VideoModel> {
|
||||||
const where = buildWhereIdOrUUID(id)
|
const where = buildWhereIdOrUUID(id)
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
order: [ [ 'Tags', 'name', 'ASC' ] ] as any, // FIXME: sequelize typings
|
order: [ [ 'Tags', 'name', 'ASC' ] ] as any,
|
||||||
where,
|
where,
|
||||||
transaction: t
|
transaction: t
|
||||||
}
|
}
|
||||||
|
|
||||||
const scopes = [
|
const scopes: (string | ScopeOptions)[] = [
|
||||||
ScopeNames.WITH_TAGS,
|
ScopeNames.WITH_TAGS,
|
||||||
ScopeNames.WITH_BLACKLISTED,
|
ScopeNames.WITH_BLACKLISTED,
|
||||||
ScopeNames.WITH_ACCOUNT_DETAILS,
|
ScopeNames.WITH_ACCOUNT_DETAILS,
|
||||||
|
@ -1420,7 +1425,7 @@ export class VideoModel extends Model<VideoModel> {
|
||||||
]
|
]
|
||||||
|
|
||||||
if (userId) {
|
if (userId) {
|
||||||
scopes.push({ method: [ ScopeNames.WITH_USER_HISTORY, userId ] } as any) // FIXME: typings
|
scopes.push({ method: [ ScopeNames.WITH_USER_HISTORY, userId ] })
|
||||||
}
|
}
|
||||||
|
|
||||||
return VideoModel
|
return VideoModel
|
||||||
|
@ -1437,18 +1442,18 @@ export class VideoModel extends Model<VideoModel> {
|
||||||
transaction: t
|
transaction: t
|
||||||
}
|
}
|
||||||
|
|
||||||
const scopes = [
|
const scopes: (string | ScopeOptions)[] = [
|
||||||
ScopeNames.WITH_TAGS,
|
ScopeNames.WITH_TAGS,
|
||||||
ScopeNames.WITH_BLACKLISTED,
|
ScopeNames.WITH_BLACKLISTED,
|
||||||
ScopeNames.WITH_ACCOUNT_DETAILS,
|
ScopeNames.WITH_ACCOUNT_DETAILS,
|
||||||
ScopeNames.WITH_SCHEDULED_UPDATE,
|
ScopeNames.WITH_SCHEDULED_UPDATE,
|
||||||
ScopeNames.WITH_THUMBNAILS,
|
ScopeNames.WITH_THUMBNAILS,
|
||||||
{ method: [ ScopeNames.WITH_FILES, true ] } as any, // FIXME: typings
|
{ method: [ ScopeNames.WITH_FILES, true ] },
|
||||||
{ method: [ ScopeNames.WITH_STREAMING_PLAYLISTS, true ] } as any // FIXME: typings
|
{ method: [ ScopeNames.WITH_STREAMING_PLAYLISTS, true ] }
|
||||||
]
|
]
|
||||||
|
|
||||||
if (userId) {
|
if (userId) {
|
||||||
scopes.push({ method: [ ScopeNames.WITH_USER_HISTORY, userId ] } as any) // FIXME: typings
|
scopes.push({ method: [ ScopeNames.WITH_USER_HISTORY, userId ] })
|
||||||
}
|
}
|
||||||
|
|
||||||
return VideoModel
|
return VideoModel
|
||||||
|
@ -1520,9 +1525,9 @@ export class VideoModel extends Model<VideoModel> {
|
||||||
attributes: [ field ],
|
attributes: [ field ],
|
||||||
limit: count,
|
limit: count,
|
||||||
group: field,
|
group: field,
|
||||||
having: Sequelize.where(Sequelize.fn('COUNT', Sequelize.col(field)), {
|
having: Sequelize.where(
|
||||||
[ Op.gte ]: threshold
|
Sequelize.fn('COUNT', Sequelize.col(field)), { [ Op.gte ]: threshold }
|
||||||
}) as any, // FIXME: typings
|
),
|
||||||
order: [ (this.sequelize as any).random() ]
|
order: [ (this.sequelize as any).random() ]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1594,16 +1599,10 @@ export class VideoModel extends Model<VideoModel> {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: typing
|
const apiScope: (string | ScopeOptions)[] = [ ScopeNames.WITH_THUMBNAILS ]
|
||||||
const apiScope: any[] = [ ScopeNames.WITH_THUMBNAILS ]
|
|
||||||
|
|
||||||
if (options.user) {
|
if (options.user) {
|
||||||
apiScope.push({ method: [ ScopeNames.WITH_USER_HISTORY, options.user.id ] })
|
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({
|
apiScope.push({
|
||||||
|
@ -1651,13 +1650,17 @@ export class VideoModel extends Model<VideoModel> {
|
||||||
return maxBy(this.VideoFiles, file => file.resolution)
|
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 = []
|
if (Array.isArray(this.Thumbnails) === false) this.Thumbnails = []
|
||||||
|
|
||||||
// Already have this thumbnail, skip
|
// 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) {
|
getVideoFilename (videoFile: VideoFileModel) {
|
||||||
|
@ -1668,10 +1671,10 @@ export class VideoModel extends Model<VideoModel> {
|
||||||
return this.uuid + '.jpg'
|
return this.uuid + '.jpg'
|
||||||
}
|
}
|
||||||
|
|
||||||
getThumbnail () {
|
getMiniature () {
|
||||||
if (Array.isArray(this.Thumbnails) === false) return undefined
|
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 () {
|
generatePreviewName () {
|
||||||
|
@ -1732,8 +1735,8 @@ export class VideoModel extends Model<VideoModel> {
|
||||||
return '/videos/embed/' + this.uuid
|
return '/videos/embed/' + this.uuid
|
||||||
}
|
}
|
||||||
|
|
||||||
getThumbnailStaticPath () {
|
getMiniatureStaticPath () {
|
||||||
const thumbnail = this.getThumbnail()
|
const thumbnail = this.getMiniature()
|
||||||
if (!thumbnail) return null
|
if (!thumbnail) return null
|
||||||
|
|
||||||
return join(STATIC_PATHS.THUMBNAILS, thumbnail.filename)
|
return join(STATIC_PATHS.THUMBNAILS, thumbnail.filename)
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -15,7 +15,6 @@ function getSequelize (serverNumber: number) {
|
||||||
dialect: 'postgres',
|
dialect: 'postgres',
|
||||||
host,
|
host,
|
||||||
port,
|
port,
|
||||||
operatorsAliases: false,
|
|
||||||
logging: false
|
logging: false
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export enum ThumbnailType {
|
export enum ThumbnailType {
|
||||||
THUMBNAIL = 1,
|
MINIATURE = 1,
|
||||||
PREVIEW = 2
|
PREVIEW = 2
|
||||||
}
|
}
|
||||||
|
|
24
yarn.lock
24
yarn.lock
|
@ -7461,17 +7461,17 @@ sequelize-pool@^1.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
bluebird "^3.5.3"
|
bluebird "^3.5.3"
|
||||||
|
|
||||||
sequelize-typescript@^1.0.0-beta.1:
|
sequelize-typescript@1.0.0-beta.2:
|
||||||
version "1.0.0-beta.1"
|
version "1.0.0-beta.2"
|
||||||
resolved "https://registry.yarnpkg.com/sequelize-typescript/-/sequelize-typescript-1.0.0-beta.1.tgz#402279fec52669cbd78ecbf50e189638483a7360"
|
resolved "https://registry.yarnpkg.com/sequelize-typescript/-/sequelize-typescript-1.0.0-beta.2.tgz#fd9ae47ecf8b159e32e19c1298426cc9773cebd8"
|
||||||
integrity sha512-xD28kqa1rIKujlmgA4hWQgtwFfRM6tLv1/mnZOrOFEZxvSWazUbTzqGB7OZydZDNj3iJnyrV1l6i6HOfvrpvEw==
|
integrity sha512-Iu67kF/RunoeBQBsU5llViJkxAHBVmeS9DBP+eC63hkEwxeDGZgxOkodyW5v5k3h2DJ0MBO+clRURXoDb+/OHg==
|
||||||
dependencies:
|
dependencies:
|
||||||
glob "7.1.2"
|
glob "7.1.2"
|
||||||
|
|
||||||
sequelize@5.6.1:
|
sequelize@5.7.4:
|
||||||
version "5.6.1"
|
version "5.7.4"
|
||||||
resolved "https://registry.yarnpkg.com/sequelize/-/sequelize-5.6.1.tgz#fc22306109fb2504a6573edfb3c469ec86fae873"
|
resolved "https://registry.yarnpkg.com/sequelize/-/sequelize-5.7.4.tgz#1631faadff65f3a345b9757fca60429c65ba8e57"
|
||||||
integrity sha512-QsXUDar6ow0HrF9BtnHRaNumu6qRYb97dfwvez/Z5guH3i6w6k8+bp6gP3VCiDC+2qX+jQIyrYohKg9evy8GFg==
|
integrity sha512-CaVYpAgZQEsGDuZ+Oq6uIZy4pxQxscotuh5UGIaFRa0VkTIgV0IiF7vAhSv+1Wn+NvhKCvgJJ85M34BP3AdGNg==
|
||||||
dependencies:
|
dependencies:
|
||||||
bluebird "^3.5.0"
|
bluebird "^3.5.0"
|
||||||
cls-bluebird "^2.1.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"
|
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||||
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
|
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
|
||||||
|
|
||||||
typescript@^3.1.6:
|
typescript@^3.4.3:
|
||||||
version "3.4.1"
|
version "3.4.3"
|
||||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.4.1.tgz#b6691be11a881ffa9a05765a205cb7383f3b63c6"
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.4.3.tgz#0eb320e4ace9b10eadf5bc6103286b0f8b7c224f"
|
||||||
integrity sha512-3NSMb2VzDQm8oBTLH6Nj55VVtUEpe/rgkIzMir0qVoLyjDZlnMBva0U6vDiV3IH+sl/Yu6oP5QwsAQtHPmDd2Q==
|
integrity sha512-FFgHdPt4T/duxx6Ndf7hwgMZZjZpB+U0nMNGVCYPq0rEzWKjEDobm4J6yb3CS7naZ0yURFqdw9Gwc7UOh/P9oQ==
|
||||||
|
|
||||||
uid-number@0.0.6:
|
uid-number@0.0.6:
|
||||||
version "0.0.6"
|
version "0.0.6"
|
||||||
|
|
Loading…
Reference in New Issue