Fix video upload and videos list

pull/128/head
Chocobozzz 2017-11-15 16:28:35 +01:00
parent 8e13fa7d09
commit 8e10cf1a5a
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
12 changed files with 59 additions and 44 deletions

View File

@ -108,7 +108,11 @@ async function follow (req: express.Request, res: express.Response, next: expres
tasks.push(p) tasks.push(p)
} }
await Promise.all(tasks) // Don't make the client wait the tasks
Promise.all(tasks)
.catch(err => {
logger.error('Error in follow.', err)
})
return res.status(204).end() return res.status(204).end()
} }

View File

@ -1,20 +1,17 @@
import * as validator from 'validator' import * as validator from 'validator'
import { ACTIVITY_PUB } from '../../../initializers'
import { exists, isDateValid, isUUIDValid } from '../misc'
import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../video-channels'
import { import {
ACTIVITY_PUB
} from '../../../initializers'
import { isDateValid, isUUIDValid } from '../misc'
import {
isVideoViewsValid,
isVideoNSFWValid,
isVideoTruncatedDescriptionValid,
isVideoDurationValid, isVideoDurationValid,
isVideoNameValid, isVideoNameValid,
isVideoNSFWValid,
isVideoTagValid, isVideoTagValid,
isVideoUrlValid isVideoTruncatedDescriptionValid,
isVideoUrlValid,
isVideoViewsValid
} from '../videos' } from '../videos'
import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../video-channels' import { isBaseActivityValid } from './misc'
import { isActivityPubUrlValid, isBaseActivityValid } from './misc'
function isVideoTorrentAddActivityValid (activity: any) { function isVideoTorrentAddActivityValid (activity: any) {
return isBaseActivityValid(activity, 'Add') && return isBaseActivityValid(activity, 'Add') &&
@ -30,10 +27,19 @@ function isVideoTorrentDeleteActivityValid (activity: any) {
return isBaseActivityValid(activity, 'Delete') return isBaseActivityValid(activity, 'Delete')
} }
function isActivityPubVideoDurationValid (value: string) {
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-duration
return exists(value) &&
typeof value === 'string' &&
value.startsWith('PT') &&
value.endsWith('S') &&
isVideoDurationValid(value.replace(/[^0-9]+/, ''))
}
function isVideoTorrentObjectValid (video: any) { function isVideoTorrentObjectValid (video: any) {
return video.type === 'Video' && return video.type === 'Video' &&
isVideoNameValid(video.name) && isVideoNameValid(video.name) &&
isVideoDurationValid(video.duration) && isActivityPubVideoDurationValid(video.duration) &&
isUUIDValid(video.uuid) && isUUIDValid(video.uuid) &&
setValidRemoteTags(video) && setValidRemoteTags(video) &&
isRemoteIdentifierValid(video.category) && isRemoteIdentifierValid(video.category) &&

View File

@ -69,6 +69,10 @@ function isVideoNSFWValid (value: any) {
return typeof value === 'boolean' || (typeof value === 'string' && validator.isBoolean(value)) return typeof value === 'boolean' || (typeof value === 'string' && validator.isBoolean(value))
} }
function isVideoDurationValid (value: string) {
return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.DURATION)
}
function isVideoTruncatedDescriptionValid (value: string) { function isVideoTruncatedDescriptionValid (value: string) {
return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.TRUNCATED_DESCRIPTION) return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.TRUNCATED_DESCRIPTION)
} }
@ -77,15 +81,6 @@ function isVideoDescriptionValid (value: string) {
return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.DESCRIPTION) return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.DESCRIPTION)
} }
function isVideoDurationValid (value: string) {
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-duration
return exists(value) &&
typeof value === 'string' &&
value.startsWith('PT') &&
value.endsWith('S') &&
validator.isInt(value.replace(/[^0-9]+/, ''), VIDEOS_CONSTRAINTS_FIELDS.DURATION)
}
function isVideoNameValid (value: string) { function isVideoNameValid (value: string) {
return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.NAME) return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.NAME)
} }
@ -197,7 +192,6 @@ export {
isVideoNSFWValid, isVideoNSFWValid,
isVideoTruncatedDescriptionValid, isVideoTruncatedDescriptionValid,
isVideoDescriptionValid, isVideoDescriptionValid,
isVideoDurationValid,
isVideoFileInfoHashValid, isVideoFileInfoHashValid,
isVideoNameValid, isVideoNameValid,
isVideoTagsValid, isVideoTagsValid,
@ -214,6 +208,7 @@ export {
isVideoFileSizeValid, isVideoFileSizeValid,
isVideoPrivacyValid, isVideoPrivacyValid,
isRemoteVideoPrivacyValid, isRemoteVideoPrivacyValid,
isVideoDurationValid,
isVideoFileResolutionValid, isVideoFileResolutionValid,
checkVideoExists, checkVideoExists,
isVideoTagValid, isVideoTagValid,

View File

@ -11,6 +11,7 @@ import { signObject, activityPubContextify } from '../../helpers'
import { Activity } from '../../../shared' import { Activity } from '../../../shared'
import { VideoAbuseInstance } from '../../models/video/video-abuse-interface' import { VideoAbuseInstance } from '../../models/video/video-abuse-interface'
import { getActivityPubUrl } from '../../helpers/activitypub' import { getActivityPubUrl } from '../../helpers/activitypub'
import { logger } from '../../helpers/logger'
async function sendCreateVideoChannel (videoChannel: VideoChannelInstance, t: Sequelize.Transaction) { async function sendCreateVideoChannel (videoChannel: VideoChannelInstance, t: Sequelize.Transaction) {
const videoChannelObject = videoChannel.toActivityPubObject() const videoChannelObject = videoChannel.toActivityPubObject()
@ -100,7 +101,11 @@ export {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
async function broadcastToFollowers (data: any, fromAccount: AccountInstance, t: Sequelize.Transaction) { async function broadcastToFollowers (data: any, fromAccount: AccountInstance, t: Sequelize.Transaction) {
const result = await db.AccountFollow.listAcceptedFollowerUrlsForApi(fromAccount.id, 0) const result = await db.AccountFollow.listAcceptedFollowerUrlsForApi(fromAccount.id)
if (result.data.length === 0) {
logger.info('Not broadcast because of 0 followers.')
return
}
const jobPayload = { const jobPayload = {
uris: result.data, uris: result.data,

View File

@ -22,8 +22,9 @@ function onError (err: Error, jobId: number) {
return Promise.resolve() return Promise.resolve()
} }
async function onSuccess (jobId: number) { function onSuccess (jobId: number) {
logger.info('Job %d is a success.', jobId) logger.info('Job %d is a success.', jobId)
return Promise.resolve()
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View File

@ -20,8 +20,9 @@ function onError (err: Error, jobId: number) {
return Promise.resolve() return Promise.resolve()
} }
async function onSuccess (jobId: number) { function onSuccess (jobId: number) {
logger.info('Job %d is a success.', jobId) logger.info('Job %d is a success.', jobId)
return Promise.resolve()
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View File

@ -9,7 +9,7 @@ import { error } from 'util'
export interface JobHandler<P, T> { export interface JobHandler<P, T> {
process (data: object, jobId: number): Promise<T> process (data: object, jobId: number): Promise<T>
onError (err: Error, jobId: number) onError (err: Error, jobId: number)
onSuccess (jobId: number, jobResult: T, jobScheduler: JobScheduler<P, T>) onSuccess (jobId: number, jobResult: T, jobScheduler: JobScheduler<P, T>): Promise<any>
} }
type JobQueueCallback = (err: Error) => void type JobQueueCallback = (err: Error) => void
@ -127,7 +127,7 @@ class JobScheduler<P, T> {
try { try {
await job.save() await job.save()
jobHandler.onSuccess(job.id, jobResult, this) await jobHandler.onSuccess(job.id, jobResult, this)
} catch (err) { } catch (err) {
this.cannotSaveJobError(err) this.cannotSaveJobError(err)
} }

View File

@ -39,8 +39,8 @@ async function onSuccess (jobId: number, video: VideoInstance, jobScheduler: Job
await sendAddVideo(video, undefined) await sendAddVideo(video, undefined)
const originalFileHeight = await videoDatabase.getOriginalFileHeight() const originalFileHeight = await videoDatabase.getOriginalFileHeight()
// Create transcoding jobs if there are enabled resolutions
// Create transcoding jobs if there are enabled resolutions
const resolutionsEnabled = computeResolutionsToTranscode(originalFileHeight) const resolutionsEnabled = computeResolutionsToTranscode(originalFileHeight)
logger.info( logger.info(
'Resolutions computed for video %s and origin file height of %d.', videoDatabase.uuid, originalFileHeight, 'Resolutions computed for video %s and origin file height of %d.', videoDatabase.uuid, originalFileHeight,

View File

@ -10,8 +10,8 @@ export namespace AccountFollowMethods {
export type ListFollowingForApi = (id: number, start: number, count: number, sort: string) => Bluebird< ResultList<AccountInstance> > export type ListFollowingForApi = (id: number, start: number, count: number, sort: string) => Bluebird< ResultList<AccountInstance> >
export type ListFollowersForApi = (id: number, start: number, count: number, sort: string) => Bluebird< ResultList<AccountInstance> > export type ListFollowersForApi = (id: number, start: number, count: number, sort: string) => Bluebird< ResultList<AccountInstance> >
export type ListAcceptedFollowerUrlsForApi = (id: number, start: number, count?: number) => Promise< ResultList<string> > export type ListAcceptedFollowerUrlsForApi = (id: number, start?: number, count?: number) => Promise< ResultList<string> >
export type ListAcceptedFollowingUrlsForApi = (id: number, start: number, count?: number) => Promise< ResultList<string> > export type ListAcceptedFollowingUrlsForApi = (id: number, start?: number, count?: number) => Promise< ResultList<string> >
} }
export interface AccountFollowClass { export interface AccountFollowClass {

View File

@ -146,17 +146,17 @@ listFollowersForApi = function (id: number, start: number, count: number, sort:
}) })
} }
listAcceptedFollowerUrlsForApi = function (id: number, start: number, count?: number) { listAcceptedFollowerUrlsForApi = function (accountId: number, start?: number, count?: number) {
return createListAcceptedFollowForApiQuery('followers', id, start, count) return createListAcceptedFollowForApiQuery('followers', accountId, start, count)
} }
listAcceptedFollowingUrlsForApi = function (id: number, start: number, count?: number) { listAcceptedFollowingUrlsForApi = function (accountId: number, start?: number, count?: number) {
return createListAcceptedFollowForApiQuery('following', id, start, count) return createListAcceptedFollowForApiQuery('following', accountId, start, count)
} }
// ------------------------------ UTILS ------------------------------ // ------------------------------ UTILS ------------------------------
async function createListAcceptedFollowForApiQuery (type: 'followers' | 'following', id: number, start: number, count?: number) { async function createListAcceptedFollowForApiQuery (type: 'followers' | 'following', accountId: number, start?: number, count?: number) {
let firstJoin: string let firstJoin: string
let secondJoin: string let secondJoin: string
@ -168,20 +168,20 @@ async function createListAcceptedFollowForApiQuery (type: 'followers' | 'followi
secondJoin = 'targetAccountId' secondJoin = 'targetAccountId'
} }
const selections = [ '"Followers"."url" AS "url"', 'COUNT(*) AS "total"' ] const selections = [ '"Follows"."url" AS "url"', 'COUNT(*) AS "total"' ]
const tasks: Promise<any>[] = [] const tasks: Promise<any>[] = []
for (const selection of selections) { for (const selection of selections) {
let query = 'SELECT ' + selection + ' FROM "Account" ' + let query = 'SELECT ' + selection + ' FROM "Accounts" ' +
'INNER JOIN "AccountFollow" ON "AccountFollow"."' + firstJoin + '" = "Account"."id" ' + 'INNER JOIN "AccountFollows" ON "AccountFollows"."' + firstJoin + '" = "Accounts"."id" ' +
'INNER JOIN "Account" AS "Follows" ON "Followers"."id" = "Follows"."' + secondJoin + '" ' + 'INNER JOIN "Accounts" AS "Follows" ON "AccountFollows"."' + secondJoin + '" = "Follows"."id" ' +
'WHERE "Account"."id" = $id AND "AccountFollow"."state" = \'accepted\' ' + 'WHERE "Accounts"."id" = $accountId AND "AccountFollows"."state" = \'accepted\' '
'LIMIT ' + start
if (start !== undefined) query += 'LIMIT ' + start
if (count !== undefined) query += ', ' + count if (count !== undefined) query += ', ' + count
const options = { const options = {
bind: { id }, bind: { accountId },
type: Sequelize.QueryTypes.SELECT type: Sequelize.QueryTypes.SELECT
} }
tasks.push(AccountFollow['sequelize'].query(query, options)) tasks.push(AccountFollow['sequelize'].query(query, options))

View File

@ -263,6 +263,7 @@ function associate (models) {
name: 'targetAccountId', name: 'targetAccountId',
allowNull: false allowNull: false
}, },
as: 'followers',
onDelete: 'cascade' onDelete: 'cascade'
}) })
} }

View File

@ -329,7 +329,7 @@ function associate (models) {
onDelete: 'cascade' onDelete: 'cascade'
}) })
Video.belongsTo(models.VideoChannel, { Video.belongsTo(models.Video, {
foreignKey: { foreignKey: {
name: 'parentId', name: 'parentId',
allowNull: true allowNull: true
@ -825,9 +825,11 @@ listForApi = function (start: number, count: number, sort: string) {
include: [ include: [
{ {
model: Video['sequelize'].models.VideoChannel, model: Video['sequelize'].models.VideoChannel,
required: true,
include: [ include: [
{ {
model: Video['sequelize'].models.Account, model: Video['sequelize'].models.Account,
required: true,
include: [ include: [
{ {
model: Video['sequelize'].models.Server, model: Video['sequelize'].models.Server,