Add ability to disable video comments

This commit is contained in:
Chocobozzz 2018-01-03 10:12:36 +01:00
parent c5911fd347
commit 47564bbe2e
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
37 changed files with 269 additions and 112 deletions

View File

@ -1,14 +1,10 @@
import { Account } from '../../../../../shared/models/actors'
import { Video } from '../../shared/video/video.model'
import { AuthUser } from '../../core'
import { import {
VideoDetails as VideoDetailsServerModel, UserRight, VideoChannel, VideoDetails as VideoDetailsServerModel, VideoFile, VideoPrivacy,
VideoFile, VideoResolution
VideoChannel,
VideoResolution,
UserRight,
VideoPrivacy
} from '../../../../../shared' } from '../../../../../shared'
import { Account } from '../../../../../shared/models/actors'
import { AuthUser } from '../../core'
import { Video } from '../../shared/video/video.model'
export class VideoDetails extends Video implements VideoDetailsServerModel { export class VideoDetails extends Video implements VideoDetailsServerModel {
accountName: string accountName: string
@ -48,6 +44,7 @@ export class VideoDetails extends Video implements VideoDetailsServerModel {
account: Account account: Account
likesPercent: number likesPercent: number
dislikesPercent: number dislikesPercent: number
commentsEnabled: boolean
constructor (hash: VideoDetailsServerModel) { constructor (hash: VideoDetailsServerModel) {
super(hash) super(hash)
@ -59,6 +56,7 @@ export class VideoDetails extends Video implements VideoDetailsServerModel {
this.channel = hash.channel this.channel = hash.channel
this.account = hash.account this.account = hash.account
this.tags = hash.tags this.tags = hash.tags
this.commentsEnabled = hash.commentsEnabled
this.likesPercent = (this.likes / (this.likes + this.dislikes)) * 100 this.likesPercent = (this.likes / (this.likes + this.dislikes)) * 100
this.dislikesPercent = (this.dislikes / (this.likes + this.dislikes)) * 100 this.dislikesPercent = (this.dislikes / (this.likes + this.dislikes)) * 100

View File

@ -9,6 +9,7 @@ export class VideoEdit {
name: string name: string
tags: string[] tags: string[]
nsfw: boolean nsfw: boolean
commentsEnabled: boolean
channel: number channel: number
privacy: VideoPrivacy privacy: VideoPrivacy
uuid?: string uuid?: string
@ -25,6 +26,7 @@ export class VideoEdit {
this.name = videoDetails.name this.name = videoDetails.name
this.tags = videoDetails.tags this.tags = videoDetails.tags
this.nsfw = videoDetails.nsfw this.nsfw = videoDetails.nsfw
this.commentsEnabled = videoDetails.commentsEnabled
this.channel = videoDetails.channel.id this.channel = videoDetails.channel.id
this.privacy = videoDetails.privacy this.privacy = videoDetails.privacy
} }
@ -45,6 +47,7 @@ export class VideoEdit {
name: this.name, name: this.name,
tags: this.tags, tags: this.tags,
nsfw: this.nsfw, nsfw: this.nsfw,
commentsEnabled: this.commentsEnabled,
channelId: this.channel, channelId: this.channel,
privacy: this.privacy privacy: this.privacy
} }

View File

@ -55,7 +55,8 @@ export class VideoService {
description, description,
privacy: video.privacy, privacy: video.privacy,
tags: video.tags, tags: video.tags,
nsfw: video.nsfw nsfw: video.nsfw,
commentsEnabled: video.commentsEnabled
} }
return this.authHttp.put(VideoService.BASE_VIDEO_URL + video.id, body) return this.authHttp.put(VideoService.BASE_VIDEO_URL + video.id, body)

View File

@ -99,5 +99,11 @@
<label for="nsfw">This video contains mature or explicit content</label> <label for="nsfw">This video contains mature or explicit content</label>
</div> </div>
<div class="form-group form-group-checkbox">
<input type="checkbox" id="commentsEnabled" formControlName="commentsEnabled" />
<label for="commentsEnabled"></label>
<label for="commentsEnabled">Enable video comments</label>
</div>
</div> </div>
</div> </div>

View File

@ -70,6 +70,7 @@ export class VideoEditComponent implements OnInit {
this.form.addControl('privacy', new FormControl('', VIDEO_PRIVACY.VALIDATORS)) this.form.addControl('privacy', new FormControl('', VIDEO_PRIVACY.VALIDATORS))
this.form.addControl('channelId', new FormControl({ value: '', disabled: true })) this.form.addControl('channelId', new FormControl({ value: '', disabled: true }))
this.form.addControl('nsfw', new FormControl(false)) this.form.addControl('nsfw', new FormControl(false))
this.form.addControl('commentsEnabled', new FormControl(true))
this.form.addControl('category', new FormControl('', VIDEO_CATEGORY.VALIDATORS)) this.form.addControl('category', new FormControl('', VIDEO_CATEGORY.VALIDATORS))
this.form.addControl('licence', new FormControl('', VIDEO_LICENCE.VALIDATORS)) this.form.addControl('licence', new FormControl('', VIDEO_LICENCE.VALIDATORS))
this.form.addControl('language', new FormControl('', VIDEO_LANGUAGE.VALIDATORS)) this.form.addControl('language', new FormControl('', VIDEO_LANGUAGE.VALIDATORS))

View File

@ -88,6 +88,7 @@ export class VideoAddComponent extends FormReactive implements OnInit {
const name = videofile.name.replace(/\.[^/.]+$/, '') const name = videofile.name.replace(/\.[^/.]+$/, '')
const privacy = this.firstStepPrivacyId.toString() const privacy = this.firstStepPrivacyId.toString()
const nsfw = false const nsfw = false
const commentsEnabled = true
const channelId = this.firstStepChannelId.toString() const channelId = this.firstStepChannelId.toString()
const formData = new FormData() const formData = new FormData()
@ -95,6 +96,7 @@ export class VideoAddComponent extends FormReactive implements OnInit {
// Put the video "private" -> we wait he validates the second step // Put the video "private" -> we wait he validates the second step
formData.append('privacy', VideoPrivacy.PRIVATE.toString()) formData.append('privacy', VideoPrivacy.PRIVATE.toString())
formData.append('nsfw', '' + nsfw) formData.append('nsfw', '' + nsfw)
formData.append('commentsEnabled', '' + commentsEnabled)
formData.append('channelId', '' + channelId) formData.append('channelId', '' + channelId)
formData.append('videofile', videofile) formData.append('videofile', videofile)

View File

@ -3,35 +3,43 @@
Comments Comments
</div> </div>
<my-video-comment-add <ng-template [ngIf]="video.commentsEnabled === true">
*ngIf="isUserLoggedIn()" <my-video-comment-add
[video]="video" *ngIf="isUserLoggedIn()"
(commentCreated)="onCommentThreadCreated($event)" [video]="video"
></my-video-comment-add> (commentCreated)="onCommentThreadCreated($event)"
></my-video-comment-add>
<div <div *ngIf="componentPagination.totalItems === 0 && comments.length === 0">No comments.</div>
class="comment-threads"
infiniteScroll
[infiniteScrollUpDistance]="1.5"
[infiniteScrollDistance]="0.5"
(scrolled)="onNearOfBottom()"
>
<div *ngFor="let comment of comments">
<my-video-comment
[comment]="comment"
[video]="video"
[inReplyToCommentId]="inReplyToCommentId"
[commentTree]="threadComments[comment.id]"
(wantedToReply)="onWantedToReply($event)"
(resetReply)="onResetReply()"
></my-video-comment>
<div *ngIf="comment.totalReplies !== 0 && !threadComments[comment.id]" (click)="viewReplies(comment)" class="view-replies"> <div
View all {{ comment.totalReplies }} replies class="comment-threads"
infiniteScroll
[infiniteScrollUpDistance]="1.5"
[infiniteScrollDistance]="0.5"
(scrolled)="onNearOfBottom()"
>
<div *ngFor="let comment of comments">
<my-video-comment
[comment]="comment"
[video]="video"
[inReplyToCommentId]="inReplyToCommentId"
[commentTree]="threadComments[comment.id]"
(wantedToReply)="onWantedToReply($event)"
(resetReply)="onResetReply()"
></my-video-comment>
<span *ngIf="!threadLoading[comment.id]" class="glyphicon glyphicon-menu-down"></span> <div *ngIf="comment.totalReplies !== 0 && !threadComments[comment.id]" (click)="viewReplies(comment)" class="view-replies">
<my-loader class="comment-thread-loading" [loading]="threadLoading[comment.id]"></my-loader> View all {{ comment.totalReplies }} replies
<span *ngIf="!threadLoading[comment.id]" class="glyphicon glyphicon-menu-down"></span>
<my-loader class="comment-thread-loading" [loading]="threadLoading[comment.id]"></my-loader>
</div>
</div> </div>
</div> </div>
</ng-template>
<div *ngIf="video.commentsEnabled === false">
Comments are disabled.
</div> </div>
</div> </div>

View File

@ -5,6 +5,7 @@ import { AuthService } from '../../../core/auth'
import { ComponentPagination } from '../../../shared/rest/component-pagination.model' import { ComponentPagination } from '../../../shared/rest/component-pagination.model'
import { User } from '../../../shared/users' import { User } from '../../../shared/users'
import { SortField } from '../../../shared/video/sort-field.type' import { SortField } from '../../../shared/video/sort-field.type'
import { VideoDetails } from '../../../shared/video/video-details.model'
import { Video } from '../../../shared/video/video.model' import { Video } from '../../../shared/video/video.model'
import { VideoComment } from './video-comment.model' import { VideoComment } from './video-comment.model'
import { VideoCommentService } from './video-comment.service' import { VideoCommentService } from './video-comment.service'
@ -15,7 +16,7 @@ import { VideoCommentService } from './video-comment.service'
styleUrls: ['./video-comments.component.scss'] styleUrls: ['./video-comments.component.scss']
}) })
export class VideoCommentsComponent implements OnInit { export class VideoCommentsComponent implements OnInit {
@Input() video: Video @Input() video: VideoDetails
@Input() user: User @Input() user: User
comments: VideoComment[] = [] comments: VideoComment[] = []
@ -36,7 +37,9 @@ export class VideoCommentsComponent implements OnInit {
) {} ) {}
ngOnInit () { ngOnInit () {
this.loadMoreComments() if (this.video.commentsEnabled === true) {
this.loadMoreComments()
}
} }
viewReplies (comment: VideoComment) { viewReplies (comment: VideoComment) {

View File

@ -1,4 +1,5 @@
import * as express from 'express' import * as express from 'express'
import { ResultList } from '../../../../shared/models'
import { VideoCommentCreate } from '../../../../shared/models/videos/video-comment.model' import { VideoCommentCreate } from '../../../../shared/models/videos/video-comment.model'
import { retryTransactionWrapper } from '../../../helpers/database-utils' import { retryTransactionWrapper } from '../../../helpers/database-utils'
import { getFormattedObjects } from '../../../helpers/utils' import { getFormattedObjects } from '../../../helpers/utils'
@ -10,6 +11,7 @@ import {
addVideoCommentReplyValidator, addVideoCommentThreadValidator, listVideoCommentThreadsValidator, addVideoCommentReplyValidator, addVideoCommentThreadValidator, listVideoCommentThreadsValidator,
listVideoThreadCommentsValidator listVideoThreadCommentsValidator
} from '../../../middlewares/validators/video-comments' } from '../../../middlewares/validators/video-comments'
import { VideoModel } from '../../../models/video/video'
import { VideoCommentModel } from '../../../models/video/video-comment' import { VideoCommentModel } from '../../../models/video/video-comment'
const videoCommentRouter = express.Router() const videoCommentRouter = express.Router()
@ -47,13 +49,33 @@ export {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
async function listVideoThreads (req: express.Request, res: express.Response, next: express.NextFunction) { async function listVideoThreads (req: express.Request, res: express.Response, next: express.NextFunction) {
const resultList = await VideoCommentModel.listThreadsForApi(res.locals.video.id, req.query.start, req.query.count, req.query.sort) const video = res.locals.video as VideoModel
let resultList: ResultList<VideoCommentModel>
if (video.commentsEnabled === true) {
resultList = await VideoCommentModel.listThreadsForApi(video.id, req.query.start, req.query.count, req.query.sort)
} else {
resultList = {
total: 0,
data: []
}
}
return res.json(getFormattedObjects(resultList.data, resultList.total)) return res.json(getFormattedObjects(resultList.data, resultList.total))
} }
async function listVideoThreadComments (req: express.Request, res: express.Response, next: express.NextFunction) { async function listVideoThreadComments (req: express.Request, res: express.Response, next: express.NextFunction) {
const resultList = await VideoCommentModel.listThreadCommentsForApi(res.locals.video.id, res.locals.videoCommentThread.id) const video = res.locals.video as VideoModel
let resultList: ResultList<VideoCommentModel>
if (video.commentsEnabled === true) {
resultList = await VideoCommentModel.listThreadCommentsForApi(res.locals.video.id, res.locals.videoCommentThread.id)
} else {
resultList = {
total: 0,
data: []
}
}
return res.json(buildFormattedCommentTree(resultList)) return res.json(buildFormattedCommentTree(resultList))
} }

View File

@ -1,12 +1,11 @@
import * as express from 'express' import * as express from 'express'
import * as multer from 'multer'
import { extname, join } from 'path' import { extname, join } from 'path'
import { VideoCreate, VideoPrivacy, VideoUpdate } from '../../../../shared' import { VideoCreate, VideoPrivacy, VideoUpdate } from '../../../../shared'
import { renamePromise } from '../../../helpers/core-utils' import { renamePromise } from '../../../helpers/core-utils'
import { retryTransactionWrapper } from '../../../helpers/database-utils' import { retryTransactionWrapper } from '../../../helpers/database-utils'
import { getVideoFileHeight } from '../../../helpers/ffmpeg-utils' import { getVideoFileHeight } from '../../../helpers/ffmpeg-utils'
import { logger } from '../../../helpers/logger' import { logger } from '../../../helpers/logger'
import { createReqFiles, generateRandomString, getFormattedObjects, getServerActor, resetSequelizeInstance } from '../../../helpers/utils' import { createReqFiles, getFormattedObjects, getServerActor, resetSequelizeInstance } from '../../../helpers/utils'
import { import {
CONFIG, sequelizeTypescript, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_MIMETYPE_EXT, CONFIG, sequelizeTypescript, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_MIMETYPE_EXT,
VIDEO_PRIVACIES VIDEO_PRIVACIES
@ -141,6 +140,7 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi
category: videoInfo.category, category: videoInfo.category,
licence: videoInfo.licence, licence: videoInfo.licence,
language: videoInfo.language, language: videoInfo.language,
commentsEnabled: videoInfo.commentsEnabled,
nsfw: videoInfo.nsfw, nsfw: videoInfo.nsfw,
description: videoInfo.description, description: videoInfo.description,
privacy: videoInfo.privacy, privacy: videoInfo.privacy,
@ -248,6 +248,7 @@ async function updateVideo (req: express.Request, res: express.Response) {
if (videoInfoToUpdate.nsfw !== undefined) videoInstance.set('nsfw', videoInfoToUpdate.nsfw) if (videoInfoToUpdate.nsfw !== undefined) videoInstance.set('nsfw', videoInfoToUpdate.nsfw)
if (videoInfoToUpdate.privacy !== undefined) videoInstance.set('privacy', parseInt(videoInfoToUpdate.privacy.toString(), 10)) if (videoInfoToUpdate.privacy !== undefined) videoInstance.set('privacy', parseInt(videoInfoToUpdate.privacy.toString(), 10))
if (videoInfoToUpdate.description !== undefined) videoInstance.set('description', videoInfoToUpdate.description) if (videoInfoToUpdate.description !== undefined) videoInstance.set('description', videoInfoToUpdate.description)
if (videoInfoToUpdate.commentsEnabled !== undefined) videoInstance.set('commentsEnabled', videoInfoToUpdate.commentsEnabled)
const videoInstanceUpdated = await videoInstance.save(sequelizeOptions) const videoInstanceUpdated = await videoInstance.save(sequelizeOptions)

View File

@ -5,7 +5,6 @@ import { isAnnounceActivityValid } from './announce'
import { isActivityPubUrlValid } from './misc' import { isActivityPubUrlValid } from './misc'
import { isDislikeActivityValid, isLikeActivityValid } from './rate' import { isDislikeActivityValid, isLikeActivityValid } from './rate'
import { isUndoActivityValid } from './undo' import { isUndoActivityValid } from './undo'
import { isVideoChannelDeleteActivityValid, isVideoChannelUpdateActivityValid } from './video-channels'
import { isVideoCommentCreateActivityValid } from './video-comments' import { isVideoCommentCreateActivityValid } from './video-comments'
import { import {
isVideoFlagValid, isVideoFlagValid,
@ -65,13 +64,11 @@ function checkCreateActivity (activity: any) {
} }
function checkUpdateActivity (activity: any) { function checkUpdateActivity (activity: any) {
return isVideoTorrentUpdateActivityValid(activity) || return isVideoTorrentUpdateActivityValid(activity)
isVideoChannelUpdateActivityValid(activity)
} }
function checkDeleteActivity (activity: any) { function checkDeleteActivity (activity: any) {
return isVideoTorrentDeleteActivityValid(activity) || return isVideoTorrentDeleteActivityValid(activity) ||
isVideoChannelDeleteActivityValid(activity) ||
isActorDeleteActivityValid(activity) isActorDeleteActivityValid(activity)
} }

View File

@ -1,30 +0,0 @@
import { isDateValid, isUUIDValid } from '../misc'
import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../video-channels'
import { isActivityPubUrlValid, isBaseActivityValid } from './misc'
function isVideoChannelUpdateActivityValid (activity: any) {
return isBaseActivityValid(activity, 'Update') &&
isVideoChannelObjectValid(activity.object)
}
function isVideoChannelDeleteActivityValid (activity: any) {
return isBaseActivityValid(activity, 'Delete')
}
function isVideoChannelObjectValid (videoChannel: any) {
return videoChannel.type === 'VideoChannel' &&
isActivityPubUrlValid(videoChannel.id) &&
isVideoChannelNameValid(videoChannel.name) &&
isVideoChannelDescriptionValid(videoChannel.content) &&
isDateValid(videoChannel.published) &&
isDateValid(videoChannel.updated) &&
isUUIDValid(videoChannel.uuid)
}
// ---------------------------------------------------------------------------
export {
isVideoChannelUpdateActivityValid,
isVideoChannelDeleteActivityValid,
isVideoChannelObjectValid
}

View File

@ -1,11 +1,10 @@
import * as validator from 'validator' import * as validator from 'validator'
import { ACTIVITY_PUB } from '../../../initializers' import { ACTIVITY_PUB } from '../../../initializers'
import { exists, isDateValid, isUUIDValid } from '../misc' import { exists, isBooleanValid, isDateValid, isUUIDValid } from '../misc'
import { import {
isVideoAbuseReasonValid, isVideoAbuseReasonValid,
isVideoDurationValid, isVideoDurationValid,
isVideoNameValid, isVideoNameValid,
isVideoNSFWValid,
isVideoTagValid, isVideoTagValid,
isVideoTruncatedDescriptionValid, isVideoTruncatedDescriptionValid,
isVideoViewsValid isVideoViewsValid
@ -53,7 +52,8 @@ function isVideoTorrentObjectValid (video: any) {
(!video.licence || isRemoteIdentifierValid(video.licence)) && (!video.licence || isRemoteIdentifierValid(video.licence)) &&
(!video.language || isRemoteIdentifierValid(video.language)) && (!video.language || isRemoteIdentifierValid(video.language)) &&
isVideoViewsValid(video.views) && isVideoViewsValid(video.views) &&
isVideoNSFWValid(video.nsfw) && isBooleanValid(video.nsfw) &&
isBooleanValid(video.commentsEnabled) &&
isDateValid(video.published) && isDateValid(video.published) &&
isDateValid(video.updated) && isDateValid(video.updated) &&
(!video.content || isRemoteVideoContentValid(video.mediaType, video.content)) && (!video.content || isRemoteVideoContentValid(video.mediaType, video.content)) &&

View File

@ -24,6 +24,10 @@ function isIdOrUUIDValid (value: string) {
return isIdValid(value) || isUUIDValid(value) return isIdValid(value) || isUUIDValid(value)
} }
function isBooleanValid (value: string) {
return typeof value === 'boolean' || (typeof value === 'string' && validator.isBoolean(value))
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
export { export {
@ -32,5 +36,6 @@ export {
isIdValid, isIdValid,
isUUIDValid, isUUIDValid,
isIdOrUUIDValid, isIdOrUUIDValid,
isDateValid isDateValid,
isBooleanValid
} }

View File

@ -30,10 +30,6 @@ function isVideoLanguageValid (value: number) {
return value === null || VIDEO_LANGUAGES[value] !== undefined return value === null || VIDEO_LANGUAGES[value] !== undefined
} }
function isVideoNSFWValid (value: any) {
return typeof value === 'boolean' || (typeof value === 'string' && validator.isBoolean(value))
}
function isVideoDurationValid (value: string) { function isVideoDurationValid (value: string) {
return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.DURATION) return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.DURATION)
} }
@ -131,7 +127,6 @@ export {
isVideoCategoryValid, isVideoCategoryValid,
isVideoLicenceValid, isVideoLicenceValid,
isVideoLanguageValid, isVideoLanguageValid,
isVideoNSFWValid,
isVideoTruncatedDescriptionValid, isVideoTruncatedDescriptionValid,
isVideoDescriptionValid, isVideoDescriptionValid,
isVideoFileInfoHashValid, isVideoFileInfoHashValid,

View File

@ -3,7 +3,7 @@ import * as multer from 'multer'
import { Model } from 'sequelize-typescript' import { Model } from 'sequelize-typescript'
import { ResultList } from '../../shared' import { ResultList } from '../../shared'
import { VideoResolution } from '../../shared/models/videos' import { VideoResolution } from '../../shared/models/videos'
import { CONFIG, REMOTE_SCHEME, VIDEO_MIMETYPE_EXT } from '../initializers' import { CONFIG, REMOTE_SCHEME } from '../initializers'
import { UserModel } from '../models/account/user' import { UserModel } from '../models/account/user'
import { ActorModel } from '../models/activitypub/actor' import { ActorModel } from '../models/activitypub/actor'
import { ApplicationModel } from '../models/application/application' import { ApplicationModel } from '../models/application/application'

View File

@ -9,7 +9,7 @@ import { isTestInstance, root, sanitizeHost, sanitizeUrl } from '../helpers/core
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
const LAST_MIGRATION_VERSION = 150 const LAST_MIGRATION_VERSION = 155
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View File

@ -0,0 +1,26 @@
import * as Sequelize from 'sequelize'
async function up (utils: {
transaction: Sequelize.Transaction,
queryInterface: Sequelize.QueryInterface,
sequelize: Sequelize.Sequelize
}): Promise<void> {
const data = {
type: Sequelize.BOOLEAN,
allowNull: false,
defaultValue: true
}
await utils.queryInterface.addColumn('video', 'commentsEnabled', data)
data.defaultValue = null
return utils.queryInterface.changeColumn('video', 'commentsEnabled', data)
}
function down (options) {
throw new Error('Not implemented.')
}
export {
up,
down
}

View File

@ -53,6 +53,7 @@ async function videoActivityObjectToDBAttributes (
language, language,
description, description,
nsfw: videoObject.nsfw, nsfw: videoObject.nsfw,
commentsEnabled: videoObject.commentsEnabled,
channelId: videoChannel.id, channelId: videoChannel.id,
duration: parseInt(duration, 10), duration: parseInt(duration, 10),
createdAt: new Date(videoObject.published), createdAt: new Date(videoObject.published),

View File

@ -3,11 +3,10 @@ import 'express-validator'
import { body, param } from 'express-validator/check' import { body, param } from 'express-validator/check'
import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc' import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc'
import { import {
isAvatarFile, isAvatarFile, isUserAutoPlayVideoValid, isUserDisplayNSFWValid, isUserPasswordValid, isUserRoleValid, isUserUsernameValid,
isUserAutoPlayVideoValid, isUserDisplayNSFWValid, isUserPasswordValid, isUserRoleValid, isUserUsernameValid,
isUserVideoQuotaValid isUserVideoQuotaValid
} from '../../helpers/custom-validators/users' } from '../../helpers/custom-validators/users'
import { isVideoExist, isVideoFile } from '../../helpers/custom-validators/videos' import { isVideoExist } from '../../helpers/custom-validators/videos'
import { logger } from '../../helpers/logger' import { logger } from '../../helpers/logger'
import { isSignupAllowed } from '../../helpers/utils' import { isSignupAllowed } from '../../helpers/utils'
import { CONSTRAINTS_FIELDS } from '../../initializers' import { CONSTRAINTS_FIELDS } from '../../initializers'

View File

@ -45,6 +45,7 @@ const addVideoCommentThreadValidator = [
if (areValidationErrors(req, res)) return if (areValidationErrors(req, res)) return
if (!await isVideoExist(req.params.videoId, res)) return if (!await isVideoExist(req.params.videoId, res)) return
if (!isVideoCommentsEnabled(res.locals.video, res)) return
return next() return next()
} }
@ -60,6 +61,7 @@ const addVideoCommentReplyValidator = [
if (areValidationErrors(req, res)) return if (areValidationErrors(req, res)) return
if (!await isVideoExist(req.params.videoId, res)) return if (!await isVideoExist(req.params.videoId, res)) return
if (!isVideoCommentsEnabled(res.locals.video, res)) return
if (!await isVideoCommentExist(req.params.commentId, res.locals.video, res)) return if (!await isVideoCommentExist(req.params.commentId, res.locals.video, res)) return
return next() return next()
@ -146,3 +148,15 @@ async function isVideoCommentExist (id: number, video: VideoModel, res: express.
res.locals.videoComment = videoComment res.locals.videoComment = videoComment
return true return true
} }
function isVideoCommentsEnabled (video: VideoModel, res: express.Response) {
if (video.commentsEnabled !== true) {
res.status(409)
.json({ error: 'Video comments are disabled for this video.' })
.end()
return false
}
return true
}

View File

@ -2,10 +2,10 @@ import * as express from 'express'
import 'express-validator' import 'express-validator'
import { body, param, query } from 'express-validator/check' import { body, param, query } from 'express-validator/check'
import { UserRight, VideoPrivacy } from '../../../shared' import { UserRight, VideoPrivacy } from '../../../shared'
import { isIdOrUUIDValid, isIdValid } from '../../helpers/custom-validators/misc' import { isBooleanValid, isIdOrUUIDValid, isIdValid } from '../../helpers/custom-validators/misc'
import { import {
isVideoAbuseReasonValid, isVideoCategoryValid, isVideoDescriptionValid, isVideoExist, isVideoFile, isVideoLanguageValid, isVideoAbuseReasonValid, isVideoCategoryValid, isVideoDescriptionValid, isVideoExist, isVideoFile, isVideoLanguageValid,
isVideoLicenceValid, isVideoNameValid, isVideoNSFWValid, isVideoPrivacyValid, isVideoRatingTypeValid, isVideoTagsValid isVideoLicenceValid, isVideoNameValid, isVideoPrivacyValid, isVideoRatingTypeValid, isVideoTagsValid
} from '../../helpers/custom-validators/videos' } from '../../helpers/custom-validators/videos'
import { getDurationFromVideoFile } from '../../helpers/ffmpeg-utils' import { getDurationFromVideoFile } from '../../helpers/ffmpeg-utils'
import { logger } from '../../helpers/logger' import { logger } from '../../helpers/logger'
@ -26,11 +26,12 @@ const videosAddValidator = [
body('category').optional().custom(isVideoCategoryValid).withMessage('Should have a valid category'), body('category').optional().custom(isVideoCategoryValid).withMessage('Should have a valid category'),
body('licence').optional().custom(isVideoLicenceValid).withMessage('Should have a valid licence'), body('licence').optional().custom(isVideoLicenceValid).withMessage('Should have a valid licence'),
body('language').optional().custom(isVideoLanguageValid).withMessage('Should have a valid language'), body('language').optional().custom(isVideoLanguageValid).withMessage('Should have a valid language'),
body('nsfw').custom(isVideoNSFWValid).withMessage('Should have a valid NSFW attribute'), body('nsfw').custom(isBooleanValid).withMessage('Should have a valid NSFW attribute'),
body('description').optional().custom(isVideoDescriptionValid).withMessage('Should have a valid description'), body('description').optional().custom(isVideoDescriptionValid).withMessage('Should have a valid description'),
body('channelId').custom(isIdValid).withMessage('Should have correct video channel id'), body('channelId').custom(isIdValid).withMessage('Should have correct video channel id'),
body('privacy').custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'), body('privacy').custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'),
body('tags').optional().custom(isVideoTagsValid).withMessage('Should have correct tags'), body('tags').optional().custom(isVideoTagsValid).withMessage('Should have correct tags'),
body('commentsEnabled').custom(isBooleanValid).withMessage('Should have comments enabled boolean'),
async (req: express.Request, res: express.Response, next: express.NextFunction) => { async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking videosAdd parameters', { parameters: req.body, files: req.files }) logger.debug('Checking videosAdd parameters', { parameters: req.body, files: req.files })
@ -85,10 +86,11 @@ const videosUpdateValidator = [
body('category').optional().custom(isVideoCategoryValid).withMessage('Should have a valid category'), body('category').optional().custom(isVideoCategoryValid).withMessage('Should have a valid category'),
body('licence').optional().custom(isVideoLicenceValid).withMessage('Should have a valid licence'), body('licence').optional().custom(isVideoLicenceValid).withMessage('Should have a valid licence'),
body('language').optional().custom(isVideoLanguageValid).withMessage('Should have a valid language'), body('language').optional().custom(isVideoLanguageValid).withMessage('Should have a valid language'),
body('nsfw').optional().custom(isVideoNSFWValid).withMessage('Should have a valid NSFW attribute'), body('nsfw').optional().custom(isBooleanValid).withMessage('Should have a valid NSFW attribute'),
body('privacy').optional().custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'), body('privacy').optional().custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'),
body('description').optional().custom(isVideoDescriptionValid).withMessage('Should have a valid description'), body('description').optional().custom(isVideoDescriptionValid).withMessage('Should have a valid description'),
body('tags').optional().custom(isVideoTagsValid).withMessage('Should have correct tags'), body('tags').optional().custom(isVideoTagsValid).withMessage('Should have correct tags'),
body('commentsEnabled').optional().custom(isBooleanValid).withMessage('Should have comments enabled boolean'),
async (req: express.Request, res: express.Response, next: express.NextFunction) => { async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking videosUpdate parameters', { parameters: req.body }) logger.debug('Checking videosUpdate parameters', { parameters: req.body })

View File

@ -1,5 +1,5 @@
import { values } from 'lodash' import { values } from 'lodash'
import { extname, join } from 'path' import { extname } from 'path'
import * as Sequelize from 'sequelize' import * as Sequelize from 'sequelize'
import { import {
AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, DefaultScope, ForeignKey, HasMany, HasOne, Is, IsUUID, Model, Scopes, AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, DefaultScope, ForeignKey, HasMany, HasOne, Is, IsUUID, Model, Scopes,
@ -13,7 +13,7 @@ import {
isActorPublicKeyValid isActorPublicKeyValid
} from '../../helpers/custom-validators/activitypub/actor' } from '../../helpers/custom-validators/activitypub/actor'
import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
import { ACTIVITY_PUB_ACTOR_TYPES, AVATARS_DIR, CONFIG, CONSTRAINTS_FIELDS } from '../../initializers' import { ACTIVITY_PUB_ACTOR_TYPES, CONFIG, CONSTRAINTS_FIELDS } from '../../initializers'
import { AccountModel } from '../account/account' import { AccountModel } from '../account/account'
import { AvatarModel } from '../avatar/avatar' import { AvatarModel } from '../avatar/avatar'
import { ServerModel } from '../server/server' import { ServerModel } from '../server/server'

View File

@ -2,9 +2,7 @@ import { join } from 'path'
import { AfterDestroy, AllowNull, Column, CreatedAt, Model, Table, UpdatedAt } from 'sequelize-typescript' import { AfterDestroy, AllowNull, Column, CreatedAt, Model, Table, UpdatedAt } from 'sequelize-typescript'
import { Avatar } from '../../../shared/models/avatars/avatar.model' import { Avatar } from '../../../shared/models/avatars/avatar.model'
import { unlinkPromise } from '../../helpers/core-utils' import { unlinkPromise } from '../../helpers/core-utils'
import { logger } from '../../helpers/logger'
import { CONFIG, STATIC_PATHS } from '../../initializers' import { CONFIG, STATIC_PATHS } from '../../initializers'
import { sendDeleteVideo } from '../../lib/activitypub/send'
@Table({ @Table({
tableName: 'avatar' tableName: 'avatar'

View File

@ -15,9 +15,10 @@ import { Video, VideoDetails } from '../../../shared/models/videos'
import { activityPubCollection } from '../../helpers/activitypub' import { activityPubCollection } from '../../helpers/activitypub'
import { createTorrentPromise, renamePromise, statPromise, unlinkPromise, writeFilePromise } from '../../helpers/core-utils' import { createTorrentPromise, renamePromise, statPromise, unlinkPromise, writeFilePromise } from '../../helpers/core-utils'
import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
import { isBooleanValid } from '../../helpers/custom-validators/misc'
import { import {
isVideoCategoryValid, isVideoDescriptionValid, isVideoDurationValid, isVideoLanguageValid, isVideoLicenceValid, isVideoNameValid, isVideoCategoryValid, isVideoDescriptionValid, isVideoDurationValid, isVideoLanguageValid, isVideoLicenceValid, isVideoNameValid,
isVideoNSFWValid, isVideoPrivacyValid isVideoPrivacyValid
} from '../../helpers/custom-validators/videos' } from '../../helpers/custom-validators/videos'
import { generateImageFromVideoFile, getVideoFileHeight, transcode } from '../../helpers/ffmpeg-utils' import { generateImageFromVideoFile, getVideoFileHeight, transcode } from '../../helpers/ffmpeg-utils'
import { logger } from '../../helpers/logger' import { logger } from '../../helpers/logger'
@ -185,7 +186,7 @@ export class VideoModel extends Model<VideoModel> {
privacy: number privacy: number
@AllowNull(false) @AllowNull(false)
@Is('VideoNSFW', value => throwIfNotValid(value, isVideoNSFWValid, 'NSFW boolean')) @Is('VideoNSFW', value => throwIfNotValid(value, isBooleanValid, 'NSFW boolean'))
@Column @Column
nsfw: boolean nsfw: boolean
@ -230,6 +231,10 @@ export class VideoModel extends Model<VideoModel> {
@Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEOS.URL.max)) @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEOS.URL.max))
url: string url: string
@AllowNull(false)
@Column
commentsEnabled: boolean
@CreatedAt @CreatedAt
createdAt: Date createdAt: Date
@ -773,6 +778,7 @@ export class VideoModel extends Model<VideoModel> {
channel: this.VideoChannel.toFormattedJSON(), channel: this.VideoChannel.toFormattedJSON(),
account: this.VideoChannel.Account.toFormattedJSON(), account: this.VideoChannel.Account.toFormattedJSON(),
tags: map<TagModel, string>(this.Tags, 'name'), tags: map<TagModel, string>(this.Tags, 'name'),
commentsEnabled: this.commentsEnabled,
files: [] files: []
} }
@ -920,6 +926,7 @@ export class VideoModel extends Model<VideoModel> {
language, language,
views: this.views, views: this.views,
nsfw: this.nsfw, nsfw: this.nsfw,
commentsEnabled: this.commentsEnabled,
published: this.createdAt.toISOString(), published: this.createdAt.toISOString(),
updated: this.updatedAt.toISOString(), updated: this.updatedAt.toISOString(),
mediaType: 'text/markdown', mediaType: 'text/markdown',

View File

@ -20,11 +20,11 @@ describe('Test activitypub', function () {
}) })
it('Should return the account object', async function () { it('Should return the account object', async function () {
const res = await makeActivityPubGetRequest(server.url, '/account/root') const res = await makeActivityPubGetRequest(server.url, '/accounts/root')
const object = res.body const object = res.body
expect(object.type).to.equal('Person') expect(object.type).to.equal('Person')
expect(object.id).to.equal('http://localhost:9001/account/root') expect(object.id).to.equal('http://localhost:9001/accounts/root')
expect(object.name).to.equal('root') expect(object.name).to.equal('root')
expect(object.preferredUsername).to.equal('root') expect(object.preferredUsername).to.equal('root')
}) })

View File

@ -2,14 +2,13 @@
import { omit } from 'lodash' import { omit } from 'lodash'
import 'mocha' import 'mocha'
import { join } from "path" import { join } from 'path'
import { UserRole } from '../../../../shared' import { UserRole } from '../../../../shared'
import { import {
createUser, flushTests, getMyUserInformation, getMyUserVideoRating, getUsersList, immutableAssign, killallServers, makeGetRequest, createUser, flushTests, getMyUserInformation, getMyUserVideoRating, getUsersList, immutableAssign, killallServers, makeGetRequest,
makePostBodyRequest, makePostUploadRequest, makePutBodyRequest, registerUser, removeUser, runServer, ServerInfo, setAccessTokensToServers, makePostBodyRequest, makePostUploadRequest, makePutBodyRequest, registerUser, removeUser, runServer, ServerInfo, setAccessTokensToServers,
updateUser, updateUser, uploadVideo, userLogin
uploadVideo, userLogin
} from '../../utils' } from '../../utils'
import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params' import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params'
@ -25,7 +24,7 @@ describe('Test users API validators', function () {
// --------------------------------------------------------------- // ---------------------------------------------------------------
before(async function () { before(async function () {
this.timeout(120000) this.timeout(20000)
await flushTests() await flushTests()
@ -282,7 +281,14 @@ describe('Test users API validators', function () {
const attaches = { const attaches = {
'avatarfile': join(__dirname, '..', 'fixtures', 'avatar.png') 'avatarfile': join(__dirname, '..', 'fixtures', 'avatar.png')
} }
await makePostUploadRequest({ url: server.url, path: path + '/me/avatar/pick', token: server.accessToken, fields, attaches }) await makePostUploadRequest({
url: server.url,
path: path + '/me/avatar/pick',
token: server.accessToken,
fields,
attaches,
statusCodeExpected: 200
})
}) })
}) })

View File

@ -1,5 +1,6 @@
/* tslint:disable:no-unused-expression */ /* tslint:disable:no-unused-expression */
import * as chai from 'chai'
import 'mocha' import 'mocha'
import { import {
flushTests, killallServers, makeGetRequest, makePostBodyRequest, runServer, ServerInfo, setAccessTokensToServers, flushTests, killallServers, makeGetRequest, makePostBodyRequest, runServer, ServerInfo, setAccessTokensToServers,
@ -8,6 +9,8 @@ import {
import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params' import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params'
import { addVideoCommentThread } from '../../utils/videos/video-comments' import { addVideoCommentThread } from '../../utils/videos/video-comments'
const expect = chai.expect
describe('Test video comments API validator', function () { describe('Test video comments API validator', function () {
let pathThread: string let pathThread: string
let pathComment: string let pathComment: string
@ -42,17 +45,14 @@ describe('Test video comments API validator', function () {
describe('When listing video comment threads', function () { describe('When listing video comment threads', function () {
it('Should fail with a bad start pagination', async function () { it('Should fail with a bad start pagination', async function () {
await checkBadStartPagination(server.url, pathThread, server.accessToken) await checkBadStartPagination(server.url, pathThread, server.accessToken)
}) })
it('Should fail with a bad count pagination', async function () { it('Should fail with a bad count pagination', async function () {
await checkBadCountPagination(server.url, pathThread, server.accessToken) await checkBadCountPagination(server.url, pathThread, server.accessToken)
}) })
it('Should fail with an incorrect sort', async function () { it('Should fail with an incorrect sort', async function () {
await checkBadSortPagination(server.url, pathThread, server.accessToken) await checkBadSortPagination(server.url, pathThread, server.accessToken)
}) })
it('Should fail with an incorrect video', async function () { it('Should fail with an incorrect video', async function () {
@ -185,6 +185,35 @@ describe('Test video comments API validator', function () {
}) })
}) })
describe('When a video has comments disabled', function () {
before(async function () {
const res = await uploadVideo(server.url, server.accessToken, { commentsEnabled: false })
videoUUID = res.body.video.uuid
pathThread = '/api/v1/videos/' + videoUUID + '/comment-threads'
})
it('Should return an empty thread list', async function () {
const res = await makeGetRequest({
url: server.url,
path: pathThread,
statusCodeExpected: 200
})
expect(res.body.total).to.equal(0)
expect(res.body.data).to.have.lengthOf(0)
})
it('Should return an thread comments list')
it('Should return conflict on thread add', async function () {
const fields = {
text: 'super comment'
}
await makePostBodyRequest({ url: server.url, path: pathThread, token: server.accessToken, fields, statusCodeExpected: 409 })
})
it('Should return conflict on comment thread add')
})
after(async function () { after(async function () {
killallServers([ server ]) killallServers([ server ])

View File

@ -100,6 +100,7 @@ describe('Test videos API validator', function () {
licence: 1, licence: 1,
language: 6, language: 6,
nsfw: false, nsfw: false,
commentsEnabled: true,
description: 'my super description', description: 'my super description',
tags: [ 'tag1', 'tag2' ], tags: [ 'tag1', 'tag2' ],
privacy: VideoPrivacy.PUBLIC, privacy: VideoPrivacy.PUBLIC,
@ -162,6 +163,20 @@ describe('Test videos API validator', function () {
await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
}) })
it('Should fail without commentsEnabled attribute', async function () {
const fields = omit(baseCorrectParams, 'commentsEnabled')
const attaches = baseCorrectAttaches
await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
})
it('Should fail with a bad commentsEnabled attribute', async function () {
const fields = immutableAssign(baseCorrectParams, { commentsEnabled: 2 })
const attaches = baseCorrectAttaches
await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
})
it('Should fail with a long description', async function () { it('Should fail with a long description', async function () {
const fields = immutableAssign(baseCorrectParams, { description: 'super'.repeat(1500) }) const fields = immutableAssign(baseCorrectParams, { description: 'super'.repeat(1500) })
const attaches = baseCorrectAttaches const attaches = baseCorrectAttaches
@ -291,6 +306,7 @@ describe('Test videos API validator', function () {
licence: 2, licence: 2,
language: 6, language: 6,
nsfw: false, nsfw: false,
commentsEnabled: false,
description: 'my super description', description: 'my super description',
privacy: VideoPrivacy.PUBLIC, privacy: VideoPrivacy.PUBLIC,
tags: [ 'tag1', 'tag2' ] tags: [ 'tag1', 'tag2' ]
@ -354,6 +370,12 @@ describe('Test videos API validator', function () {
await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields }) await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields })
}) })
it('Should fail with a bad commentsEnabled attribute', async function () {
const fields = immutableAssign(baseCorrectParams, { commentsEnabled: 2 })
await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields })
})
it('Should fail with a long description', async function () { it('Should fail with a long description', async function () {
const fields = immutableAssign(baseCorrectParams, { description: 'super'.repeat(1500) }) const fields = immutableAssign(baseCorrectParams, { description: 'super'.repeat(1500) })

View File

@ -246,6 +246,7 @@ describe('Test follows', function () {
host: 'localhost:9003', host: 'localhost:9003',
account: 'root', account: 'root',
isLocal, isLocal,
commentsEnabled: true,
duration: 5, duration: 5,
tags: [ 'tag1', 'tag2', 'tag3' ], tags: [ 'tag1', 'tag2', 'tag3' ],
privacy: VideoPrivacy.PUBLIC, privacy: VideoPrivacy.PUBLIC,

View File

@ -93,6 +93,7 @@ describe('Test multiple servers', function () {
duration: 10, duration: 10,
tags: [ 'tag1p1', 'tag2p1' ], tags: [ 'tag1p1', 'tag2p1' ],
privacy: VideoPrivacy.PUBLIC, privacy: VideoPrivacy.PUBLIC,
commentsEnabled: true,
channel: { channel: {
name: 'my channel', name: 'my channel',
description: 'super channel', description: 'super channel',
@ -155,6 +156,7 @@ describe('Test multiple servers', function () {
host: 'localhost:9002', host: 'localhost:9002',
account: 'user1', account: 'user1',
isLocal, isLocal,
commentsEnabled: true,
duration: 5, duration: 5,
tags: [ 'tag1p2', 'tag2p2', 'tag3p2' ], tags: [ 'tag1p2', 'tag2p2', 'tag3p2' ],
privacy: VideoPrivacy.PUBLIC, privacy: VideoPrivacy.PUBLIC,
@ -254,6 +256,7 @@ describe('Test multiple servers', function () {
account: 'root', account: 'root',
isLocal, isLocal,
duration: 5, duration: 5,
commentsEnabled: true,
tags: [ 'tag1p3' ], tags: [ 'tag1p3' ],
privacy: VideoPrivacy.PUBLIC, privacy: VideoPrivacy.PUBLIC,
channel: { channel: {
@ -280,6 +283,7 @@ describe('Test multiple servers', function () {
description: 'my super description for server 3-2', description: 'my super description for server 3-2',
host: 'localhost:9003', host: 'localhost:9003',
account: 'root', account: 'root',
commentsEnabled: true,
isLocal, isLocal,
duration: 5, duration: 5,
tags: [ 'tag2p3', 'tag3p3', 'tag4p3' ], tags: [ 'tag2p3', 'tag3p3', 'tag4p3' ],
@ -545,6 +549,7 @@ describe('Test multiple servers', function () {
account: 'root', account: 'root',
isLocal, isLocal,
duration: 5, duration: 5,
commentsEnabled: true,
tags: [ 'tag_up_1', 'tag_up_2' ], tags: [ 'tag_up_1', 'tag_up_2' ],
privacy: VideoPrivacy.PUBLIC, privacy: VideoPrivacy.PUBLIC,
channel: { channel: {
@ -732,6 +737,26 @@ describe('Test multiple servers', function () {
expect(secondChild.children).to.have.lengthOf(0) expect(secondChild.children).to.have.lengthOf(0)
} }
}) })
it('Should disable comments', async function () {
this.timeout(20000)
const attributes = {
commentsEnabled: false
}
await updateVideo(servers[0].url, servers[0].accessToken, videoUUID, attributes)
await wait(5000)
for (const server of servers) {
const res = await getVideo(server.url, videoUUID)
expect(res.body.commentsEnabled).to.be.false
const text = 'my super forbidden comment'
await addVideoCommentThread(server.url, server.accessToken, videoUUID, text, 409)
}
})
}) })
describe('With minimum parameters', function () { describe('With minimum parameters', function () {
@ -748,6 +773,7 @@ describe('Test multiple servers', function () {
.field('privacy', '1') .field('privacy', '1')
.field('nsfw', 'false') .field('nsfw', 'false')
.field('channelId', '1') .field('channelId', '1')
.field('commentsEnabled', 'true')
const filePath = join(__dirname, '..', '..', 'api', 'fixtures', 'video_short.webm') const filePath = join(__dirname, '..', '..', 'api', 'fixtures', 'video_short.webm')
@ -772,6 +798,7 @@ describe('Test multiple servers', function () {
account: 'root', account: 'root',
isLocal, isLocal,
duration: 5, duration: 5,
commentsEnabled: true,
tags: [ ], tags: [ ],
privacy: VideoPrivacy.PUBLIC, privacy: VideoPrivacy.PUBLIC,
channel: { channel: {

View File

@ -32,6 +32,7 @@ describe('Test a single server', function () {
duration: 5, duration: 5,
tags: [ 'tag1', 'tag2', 'tag3' ], tags: [ 'tag1', 'tag2', 'tag3' ],
privacy: VideoPrivacy.PUBLIC, privacy: VideoPrivacy.PUBLIC,
commentsEnabled: true,
channel: { channel: {
name: 'Default root channel', name: 'Default root channel',
description: '', description: '',
@ -51,7 +52,7 @@ describe('Test a single server', function () {
category: 4, category: 4,
licence: 2, licence: 2,
language: 5, language: 5,
nsfw: true, nsfw: false,
description: 'my super description updated', description: 'my super description updated',
host: 'localhost:9001', host: 'localhost:9001',
account: 'root', account: 'root',
@ -59,6 +60,7 @@ describe('Test a single server', function () {
tags: [ 'tagup1', 'tagup2' ], tags: [ 'tagup1', 'tagup2' ],
privacy: VideoPrivacy.PUBLIC, privacy: VideoPrivacy.PUBLIC,
duration: 5, duration: 5,
commentsEnabled: false,
channel: { channel: {
name: 'Default root channel', name: 'Default root channel',
description: '', description: '',
@ -475,6 +477,7 @@ describe('Test a single server', function () {
language: 5, language: 5,
nsfw: false, nsfw: false,
description: 'my super description updated', description: 'my super description updated',
commentsEnabled: false,
tags: [ 'tagup1', 'tagup2' ] tags: [ 'tagup1', 'tagup2' ]
} }
await updateVideo(server.url, server.accessToken, videoId, attributes) await updateVideo(server.url, server.accessToken, videoId, attributes)

View File

@ -16,6 +16,7 @@ type VideoAttributes = {
licence?: number licence?: number
language?: number language?: number
nsfw?: boolean nsfw?: boolean
commentsEnabled?: boolean
description?: string description?: string
tags?: string[] tags?: string[]
channelId?: number channelId?: number
@ -238,6 +239,7 @@ async function uploadVideo (url: string, accessToken: string, videoAttributesArg
description: 'my super description', description: 'my super description',
tags: [ 'tag' ], tags: [ 'tag' ],
privacy: VideoPrivacy.PUBLIC, privacy: VideoPrivacy.PUBLIC,
commentsEnabled: true,
fixture: 'video_short.webm' fixture: 'video_short.webm'
} }
attributes = Object.assign(attributes, videoAttributesArg) attributes = Object.assign(attributes, videoAttributesArg)
@ -250,6 +252,7 @@ async function uploadVideo (url: string, accessToken: string, videoAttributesArg
.field('category', attributes.category.toString()) .field('category', attributes.category.toString())
.field('licence', attributes.licence.toString()) .field('licence', attributes.licence.toString())
.field('nsfw', JSON.stringify(attributes.nsfw)) .field('nsfw', JSON.stringify(attributes.nsfw))
.field('commentsEnabled', JSON.stringify(attributes.commentsEnabled))
.field('description', attributes.description) .field('description', attributes.description)
.field('privacy', attributes.privacy.toString()) .field('privacy', attributes.privacy.toString())
.field('channelId', attributes.channelId) .field('channelId', attributes.channelId)
@ -273,7 +276,7 @@ async function uploadVideo (url: string, accessToken: string, videoAttributesArg
.expect(specialStatus) .expect(specialStatus)
} }
function updateVideo (url: string, accessToken: string, id: number, attributes: VideoAttributes, specialStatus = 204) { function updateVideo (url: string, accessToken: string, id: number | string, attributes: VideoAttributes, specialStatus = 204) {
const path = '/api/v1/videos/' + id const path = '/api/v1/videos/' + id
const body = {} const body = {}
@ -281,7 +284,8 @@ function updateVideo (url: string, accessToken: string, id: number, attributes:
if (attributes.category) body['category'] = attributes.category if (attributes.category) body['category'] = attributes.category
if (attributes.licence) body['licence'] = attributes.licence if (attributes.licence) body['licence'] = attributes.licence
if (attributes.language) body['language'] = attributes.language if (attributes.language) body['language'] = attributes.language
if (attributes.nsfw) body['nsfw'] = attributes.nsfw if (attributes.nsfw !== undefined) body['nsfw'] = JSON.stringify(attributes.nsfw)
if (attributes.commentsEnabled !== undefined) body['commentsEnabled'] = JSON.stringify(attributes.commentsEnabled)
if (attributes.description) body['description'] = attributes.description if (attributes.description) body['description'] = attributes.description
if (attributes.tags) body['tags'] = attributes.tags if (attributes.tags) body['tags'] = attributes.tags
if (attributes.privacy) body['privacy'] = attributes.privacy if (attributes.privacy) body['privacy'] = attributes.privacy
@ -326,6 +330,7 @@ async function completeVideoCheck (
licence: number licence: number
language: number language: number
nsfw: boolean nsfw: boolean
commentsEnabled: boolean
description: string description: string
host: string host: string
account: string account: string
@ -376,6 +381,7 @@ async function completeVideoCheck (
expect(videoDetails.privacy).to.deep.equal(attributes.privacy) expect(videoDetails.privacy).to.deep.equal(attributes.privacy)
expect(videoDetails.privacyLabel).to.deep.equal(VIDEO_PRIVACIES[attributes.privacy]) expect(videoDetails.privacyLabel).to.deep.equal(VIDEO_PRIVACIES[attributes.privacy])
expect(videoDetails.account.name).to.equal(attributes.account) expect(videoDetails.account.name).to.equal(attributes.account)
expect(videoDetails.commentsEnabled).to.equal(attributes.commentsEnabled)
expect(videoDetails.channel.name).to.equal(attributes.channel.name) expect(videoDetails.channel.name).to.equal(attributes.channel.name)
expect(videoDetails.channel.isLocal).to.equal(attributes.channel.isLocal) expect(videoDetails.channel.isLocal).to.equal(attributes.channel.isLocal)

View File

@ -18,6 +18,7 @@ export interface VideoTorrentObject {
language: ActivityIdentifierObject language: ActivityIdentifierObject
views: number views: number
nsfw: boolean nsfw: boolean
commentsEnabled: boolean
published: string published: string
updated: string updated: string
mediaType: 'text/markdown' mediaType: 'text/markdown'

View File

@ -9,5 +9,6 @@ export interface VideoCreate {
nsfw: boolean nsfw: boolean
name: string name: string
tags?: string[] tags?: string[]
commentsEnabled?: boolean
privacy: VideoPrivacy privacy: VideoPrivacy
} }

View File

@ -8,5 +8,6 @@ export interface VideoUpdate {
description?: string description?: string
privacy?: VideoPrivacy privacy?: VideoPrivacy
tags?: string[] tags?: string[]
commentsEnabled?: boolean
nsfw?: boolean nsfw?: boolean
} }

View File

@ -45,4 +45,5 @@ export interface VideoDetails extends Video {
tags: string[] tags: string[]
files: VideoFile[] files: VideoFile[]
account: Account account: Account
commentsEnabled: boolean
} }