Add ability to disable video comments

pull/175/head
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 {
VideoDetails as VideoDetailsServerModel,
VideoFile,
VideoChannel,
VideoResolution,
UserRight,
VideoPrivacy
UserRight, VideoChannel, VideoDetails as VideoDetailsServerModel, VideoFile, VideoPrivacy,
VideoResolution
} 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 {
accountName: string
@ -48,6 +44,7 @@ export class VideoDetails extends Video implements VideoDetailsServerModel {
account: Account
likesPercent: number
dislikesPercent: number
commentsEnabled: boolean
constructor (hash: VideoDetailsServerModel) {
super(hash)
@ -59,6 +56,7 @@ export class VideoDetails extends Video implements VideoDetailsServerModel {
this.channel = hash.channel
this.account = hash.account
this.tags = hash.tags
this.commentsEnabled = hash.commentsEnabled
this.likesPercent = (this.likes / (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
tags: string[]
nsfw: boolean
commentsEnabled: boolean
channel: number
privacy: VideoPrivacy
uuid?: string
@ -25,6 +26,7 @@ export class VideoEdit {
this.name = videoDetails.name
this.tags = videoDetails.tags
this.nsfw = videoDetails.nsfw
this.commentsEnabled = videoDetails.commentsEnabled
this.channel = videoDetails.channel.id
this.privacy = videoDetails.privacy
}
@ -45,6 +47,7 @@ export class VideoEdit {
name: this.name,
tags: this.tags,
nsfw: this.nsfw,
commentsEnabled: this.commentsEnabled,
channelId: this.channel,
privacy: this.privacy
}

View File

@ -55,7 +55,8 @@ export class VideoService {
description,
privacy: video.privacy,
tags: video.tags,
nsfw: video.nsfw
nsfw: video.nsfw,
commentsEnabled: video.commentsEnabled
}
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>
</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>

View File

@ -70,6 +70,7 @@ export class VideoEditComponent implements OnInit {
this.form.addControl('privacy', new FormControl('', VIDEO_PRIVACY.VALIDATORS))
this.form.addControl('channelId', new FormControl({ value: '', disabled: true }))
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('licence', new FormControl('', VIDEO_LICENCE.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 privacy = this.firstStepPrivacyId.toString()
const nsfw = false
const commentsEnabled = true
const channelId = this.firstStepChannelId.toString()
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
formData.append('privacy', VideoPrivacy.PRIVATE.toString())
formData.append('nsfw', '' + nsfw)
formData.append('commentsEnabled', '' + commentsEnabled)
formData.append('channelId', '' + channelId)
formData.append('videofile', videofile)

View File

@ -3,35 +3,43 @@
Comments
</div>
<my-video-comment-add
*ngIf="isUserLoggedIn()"
[video]="video"
(commentCreated)="onCommentThreadCreated($event)"
></my-video-comment-add>
<ng-template [ngIf]="video.commentsEnabled === true">
<my-video-comment-add
*ngIf="isUserLoggedIn()"
[video]="video"
(commentCreated)="onCommentThreadCreated($event)"
></my-video-comment-add>
<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="componentPagination.totalItems === 0 && comments.length === 0">No comments.</div>
<div *ngIf="comment.totalReplies !== 0 && !threadComments[comment.id]" (click)="viewReplies(comment)" class="view-replies">
View all {{ comment.totalReplies }} replies
<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>
<span *ngIf="!threadLoading[comment.id]" class="glyphicon glyphicon-menu-down"></span>
<my-loader class="comment-thread-loading" [loading]="threadLoading[comment.id]"></my-loader>
<div *ngIf="comment.totalReplies !== 0 && !threadComments[comment.id]" (click)="viewReplies(comment)" class="view-replies">
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>
</ng-template>
<div *ngIf="video.commentsEnabled === false">
Comments are disabled.
</div>
</div>

View File

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

View File

@ -1,4 +1,5 @@
import * as express from 'express'
import { ResultList } from '../../../../shared/models'
import { VideoCommentCreate } from '../../../../shared/models/videos/video-comment.model'
import { retryTransactionWrapper } from '../../../helpers/database-utils'
import { getFormattedObjects } from '../../../helpers/utils'
@ -10,6 +11,7 @@ import {
addVideoCommentReplyValidator, addVideoCommentThreadValidator, listVideoCommentThreadsValidator,
listVideoThreadCommentsValidator
} from '../../../middlewares/validators/video-comments'
import { VideoModel } from '../../../models/video/video'
import { VideoCommentModel } from '../../../models/video/video-comment'
const videoCommentRouter = express.Router()
@ -47,13 +49,33 @@ export {
// ---------------------------------------------------------------------------
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))
}
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))
}

View File

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

View File

@ -5,7 +5,6 @@ import { isAnnounceActivityValid } from './announce'
import { isActivityPubUrlValid } from './misc'
import { isDislikeActivityValid, isLikeActivityValid } from './rate'
import { isUndoActivityValid } from './undo'
import { isVideoChannelDeleteActivityValid, isVideoChannelUpdateActivityValid } from './video-channels'
import { isVideoCommentCreateActivityValid } from './video-comments'
import {
isVideoFlagValid,
@ -65,13 +64,11 @@ function checkCreateActivity (activity: any) {
}
function checkUpdateActivity (activity: any) {
return isVideoTorrentUpdateActivityValid(activity) ||
isVideoChannelUpdateActivityValid(activity)
return isVideoTorrentUpdateActivityValid(activity)
}
function checkDeleteActivity (activity: any) {
return isVideoTorrentDeleteActivityValid(activity) ||
isVideoChannelDeleteActivityValid(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 { ACTIVITY_PUB } from '../../../initializers'
import { exists, isDateValid, isUUIDValid } from '../misc'
import { exists, isBooleanValid, isDateValid, isUUIDValid } from '../misc'
import {
isVideoAbuseReasonValid,
isVideoDurationValid,
isVideoNameValid,
isVideoNSFWValid,
isVideoTagValid,
isVideoTruncatedDescriptionValid,
isVideoViewsValid
@ -53,7 +52,8 @@ function isVideoTorrentObjectValid (video: any) {
(!video.licence || isRemoteIdentifierValid(video.licence)) &&
(!video.language || isRemoteIdentifierValid(video.language)) &&
isVideoViewsValid(video.views) &&
isVideoNSFWValid(video.nsfw) &&
isBooleanValid(video.nsfw) &&
isBooleanValid(video.commentsEnabled) &&
isDateValid(video.published) &&
isDateValid(video.updated) &&
(!video.content || isRemoteVideoContentValid(video.mediaType, video.content)) &&

View File

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

View File

@ -30,10 +30,6 @@ function isVideoLanguageValid (value: number) {
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) {
return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.DURATION)
}
@ -131,7 +127,6 @@ export {
isVideoCategoryValid,
isVideoLicenceValid,
isVideoLanguageValid,
isVideoNSFWValid,
isVideoTruncatedDescriptionValid,
isVideoDescriptionValid,
isVideoFileInfoHashValid,

View File

@ -3,7 +3,7 @@ import * as multer from 'multer'
import { Model } from 'sequelize-typescript'
import { ResultList } from '../../shared'
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 { ActorModel } from '../models/activitypub/actor'
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,
description,
nsfw: videoObject.nsfw,
commentsEnabled: videoObject.commentsEnabled,
channelId: videoChannel.id,
duration: parseInt(duration, 10),
createdAt: new Date(videoObject.published),

View File

@ -3,11 +3,10 @@ import 'express-validator'
import { body, param } from 'express-validator/check'
import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc'
import {
isAvatarFile,
isUserAutoPlayVideoValid, isUserDisplayNSFWValid, isUserPasswordValid, isUserRoleValid, isUserUsernameValid,
isAvatarFile, isUserAutoPlayVideoValid, isUserDisplayNSFWValid, isUserPasswordValid, isUserRoleValid, isUserUsernameValid,
isUserVideoQuotaValid
} 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 { isSignupAllowed } from '../../helpers/utils'
import { CONSTRAINTS_FIELDS } from '../../initializers'

View File

@ -45,6 +45,7 @@ const addVideoCommentThreadValidator = [
if (areValidationErrors(req, res)) return
if (!await isVideoExist(req.params.videoId, res)) return
if (!isVideoCommentsEnabled(res.locals.video, res)) return
return next()
}
@ -60,6 +61,7 @@ const addVideoCommentReplyValidator = [
if (areValidationErrors(req, 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
return next()
@ -146,3 +148,15 @@ async function isVideoCommentExist (id: number, video: VideoModel, res: express.
res.locals.videoComment = videoComment
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 { body, param, query } from 'express-validator/check'
import { UserRight, VideoPrivacy } from '../../../shared'
import { isIdOrUUIDValid, isIdValid } from '../../helpers/custom-validators/misc'
import { isBooleanValid, isIdOrUUIDValid, isIdValid } from '../../helpers/custom-validators/misc'
import {
isVideoAbuseReasonValid, isVideoCategoryValid, isVideoDescriptionValid, isVideoExist, isVideoFile, isVideoLanguageValid,
isVideoLicenceValid, isVideoNameValid, isVideoNSFWValid, isVideoPrivacyValid, isVideoRatingTypeValid, isVideoTagsValid
isVideoLicenceValid, isVideoNameValid, isVideoPrivacyValid, isVideoRatingTypeValid, isVideoTagsValid
} from '../../helpers/custom-validators/videos'
import { getDurationFromVideoFile } from '../../helpers/ffmpeg-utils'
import { logger } from '../../helpers/logger'
@ -26,11 +26,12 @@ const videosAddValidator = [
body('category').optional().custom(isVideoCategoryValid).withMessage('Should have a valid category'),
body('licence').optional().custom(isVideoLicenceValid).withMessage('Should have a valid licence'),
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('channelId').custom(isIdValid).withMessage('Should have correct video channel id'),
body('privacy').custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'),
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) => {
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('licence').optional().custom(isVideoLicenceValid).withMessage('Should have a valid licence'),
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('description').optional().custom(isVideoDescriptionValid).withMessage('Should have a valid description'),
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) => {
logger.debug('Checking videosUpdate parameters', { parameters: req.body })

View File

@ -1,5 +1,5 @@
import { values } from 'lodash'
import { extname, join } from 'path'
import { extname } from 'path'
import * as Sequelize from 'sequelize'
import {
AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, DefaultScope, ForeignKey, HasMany, HasOne, Is, IsUUID, Model, Scopes,
@ -13,7 +13,7 @@ import {
isActorPublicKeyValid
} from '../../helpers/custom-validators/activitypub/actor'
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 { AvatarModel } from '../avatar/avatar'
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 { Avatar } from '../../../shared/models/avatars/avatar.model'
import { unlinkPromise } from '../../helpers/core-utils'
import { logger } from '../../helpers/logger'
import { CONFIG, STATIC_PATHS } from '../../initializers'
import { sendDeleteVideo } from '../../lib/activitypub/send'
@Table({
tableName: 'avatar'

View File

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

View File

@ -20,11 +20,11 @@ describe('Test activitypub', 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
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.preferredUsername).to.equal('root')
})

View File

@ -2,14 +2,13 @@
import { omit } from 'lodash'
import 'mocha'
import { join } from "path"
import { join } from 'path'
import { UserRole } from '../../../../shared'
import {
createUser, flushTests, getMyUserInformation, getMyUserVideoRating, getUsersList, immutableAssign, killallServers, makeGetRequest,
makePostBodyRequest, makePostUploadRequest, makePutBodyRequest, registerUser, removeUser, runServer, ServerInfo, setAccessTokensToServers,
updateUser,
uploadVideo, userLogin
updateUser, uploadVideo, userLogin
} from '../../utils'
import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params'
@ -25,7 +24,7 @@ describe('Test users API validators', function () {
// ---------------------------------------------------------------
before(async function () {
this.timeout(120000)
this.timeout(20000)
await flushTests()
@ -282,7 +281,14 @@ describe('Test users API validators', function () {
const attaches = {
'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 */
import * as chai from 'chai'
import 'mocha'
import {
flushTests, killallServers, makeGetRequest, makePostBodyRequest, runServer, ServerInfo, setAccessTokensToServers,
@ -8,6 +9,8 @@ import {
import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params'
import { addVideoCommentThread } from '../../utils/videos/video-comments'
const expect = chai.expect
describe('Test video comments API validator', function () {
let pathThread: string
let pathComment: string
@ -42,17 +45,14 @@ describe('Test video comments API validator', function () {
describe('When listing video comment threads', function () {
it('Should fail with a bad start pagination', async function () {
await checkBadStartPagination(server.url, pathThread, server.accessToken)
})
it('Should fail with a bad count pagination', async function () {
await checkBadCountPagination(server.url, pathThread, server.accessToken)
})
it('Should fail with an incorrect sort', async function () {
await checkBadSortPagination(server.url, pathThread, server.accessToken)
})
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 () {
killallServers([ server ])

View File

@ -100,6 +100,7 @@ describe('Test videos API validator', function () {
licence: 1,
language: 6,
nsfw: false,
commentsEnabled: true,
description: 'my super description',
tags: [ 'tag1', 'tag2' ],
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 })
})
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 () {
const fields = immutableAssign(baseCorrectParams, { description: 'super'.repeat(1500) })
const attaches = baseCorrectAttaches
@ -291,6 +306,7 @@ describe('Test videos API validator', function () {
licence: 2,
language: 6,
nsfw: false,
commentsEnabled: false,
description: 'my super description',
privacy: VideoPrivacy.PUBLIC,
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 })
})
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 () {
const fields = immutableAssign(baseCorrectParams, { description: 'super'.repeat(1500) })

View File

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

View File

@ -93,6 +93,7 @@ describe('Test multiple servers', function () {
duration: 10,
tags: [ 'tag1p1', 'tag2p1' ],
privacy: VideoPrivacy.PUBLIC,
commentsEnabled: true,
channel: {
name: 'my channel',
description: 'super channel',
@ -155,6 +156,7 @@ describe('Test multiple servers', function () {
host: 'localhost:9002',
account: 'user1',
isLocal,
commentsEnabled: true,
duration: 5,
tags: [ 'tag1p2', 'tag2p2', 'tag3p2' ],
privacy: VideoPrivacy.PUBLIC,
@ -254,6 +256,7 @@ describe('Test multiple servers', function () {
account: 'root',
isLocal,
duration: 5,
commentsEnabled: true,
tags: [ 'tag1p3' ],
privacy: VideoPrivacy.PUBLIC,
channel: {
@ -280,6 +283,7 @@ describe('Test multiple servers', function () {
description: 'my super description for server 3-2',
host: 'localhost:9003',
account: 'root',
commentsEnabled: true,
isLocal,
duration: 5,
tags: [ 'tag2p3', 'tag3p3', 'tag4p3' ],
@ -545,6 +549,7 @@ describe('Test multiple servers', function () {
account: 'root',
isLocal,
duration: 5,
commentsEnabled: true,
tags: [ 'tag_up_1', 'tag_up_2' ],
privacy: VideoPrivacy.PUBLIC,
channel: {
@ -732,6 +737,26 @@ describe('Test multiple servers', function () {
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 () {
@ -748,6 +773,7 @@ describe('Test multiple servers', function () {
.field('privacy', '1')
.field('nsfw', 'false')
.field('channelId', '1')
.field('commentsEnabled', 'true')
const filePath = join(__dirname, '..', '..', 'api', 'fixtures', 'video_short.webm')
@ -772,6 +798,7 @@ describe('Test multiple servers', function () {
account: 'root',
isLocal,
duration: 5,
commentsEnabled: true,
tags: [ ],
privacy: VideoPrivacy.PUBLIC,
channel: {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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