mirror of https://github.com/Chocobozzz/PeerTube
Fix video upload and videos list
parent
8e13fa7d09
commit
8e10cf1a5a
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) &&
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -263,6 +263,7 @@ function associate (models) {
|
||||||
name: 'targetAccountId',
|
name: 'targetAccountId',
|
||||||
allowNull: false
|
allowNull: false
|
||||||
},
|
},
|
||||||
|
as: 'followers',
|
||||||
onDelete: 'cascade'
|
onDelete: 'cascade'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue