-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
account: Account
likesPercent: number
dislikesPercent: number
+ commentsEnabled: boolean
constructor (hash: VideoDetailsServerModel) {
super(hash)
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
name: string
tags: string[]
nsfw: boolean
+ commentsEnabled: boolean
channel: number
privacy: VideoPrivacy
uuid?: string
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
}
name: this.name,
tags: this.tags,
nsfw: this.nsfw,
+ commentsEnabled: this.commentsEnabled,
channelId: this.channel,
privacy: this.privacy
}
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)
<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>
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))
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()
// 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)
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>
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'
styleUrls: ['./video-comments.component.scss']
})
export class VideoCommentsComponent implements OnInit {
- @Input() video: Video
+ @Input() video: VideoDetails
@Input() user: User
comments: VideoComment[] = []
) {}
ngOnInit () {
- this.loadMoreComments()
+ if (this.video.commentsEnabled === true) {
+ this.loadMoreComments()
+ }
}
viewReplies (comment: VideoComment) {
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'
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()
// ---------------------------------------------------------------------------
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))
}
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
category: videoInfo.category,
licence: videoInfo.licence,
language: videoInfo.language,
+ commentsEnabled: videoInfo.commentsEnabled,
nsfw: videoInfo.nsfw,
description: videoInfo.description,
privacy: videoInfo.privacy,
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)
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,
}
function checkUpdateActivity (activity: any) {
- return isVideoTorrentUpdateActivityValid(activity) ||
- isVideoChannelUpdateActivityValid(activity)
+ return isVideoTorrentUpdateActivityValid(activity)
}
function checkDeleteActivity (activity: any) {
return isVideoTorrentDeleteActivityValid(activity) ||
- isVideoChannelDeleteActivityValid(activity) ||
isActorDeleteActivityValid(activity)
}
+++ /dev/null
-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
-}
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
(!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)) &&
return isIdValid(value) || isUUIDValid(value)
}
+function isBooleanValid (value: string) {
+ return typeof value === 'boolean' || (typeof value === 'string' && validator.isBoolean(value))
+}
+
// ---------------------------------------------------------------------------
export {
isIdValid,
isUUIDValid,
isIdOrUUIDValid,
- isDateValid
+ isDateValid,
+ isBooleanValid
}
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)
}
isVideoCategoryValid,
isVideoLicenceValid,
isVideoLanguageValid,
- isVideoNSFWValid,
isVideoTruncatedDescriptionValid,
isVideoDescriptionValid,
isVideoFileInfoHashValid,
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'
// ---------------------------------------------------------------------------
-const LAST_MIGRATION_VERSION = 150
+const LAST_MIGRATION_VERSION = 155
// ---------------------------------------------------------------------------
--- /dev/null
+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
+}
language,
description,
nsfw: videoObject.nsfw,
+ commentsEnabled: videoObject.commentsEnabled,
channelId: videoChannel.id,
duration: parseInt(duration, 10),
createdAt: new Date(videoObject.published),
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'
if (areValidationErrors(req, res)) return
if (!await isVideoExist(req.params.videoId, res)) return
+ if (!isVideoCommentsEnabled(res.locals.video, res)) return
return next()
}
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()
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
+}
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'
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 })
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 })
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,
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'
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'
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'
privacy: number
@AllowNull(false)
- @Is('VideoNSFW', value => throwIfNotValid(value, isVideoNSFWValid, 'NSFW boolean'))
+ @Is('VideoNSFW', value => throwIfNotValid(value, isBooleanValid, 'NSFW boolean'))
@Column
nsfw: boolean
@Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEOS.URL.max))
url: string
+ @AllowNull(false)
+ @Column
+ commentsEnabled: boolean
+
@CreatedAt
createdAt: Date
channel: this.VideoChannel.toFormattedJSON(),
account: this.VideoChannel.Account.toFormattedJSON(),
tags: map<TagModel, string>(this.Tags, 'name'),
+ commentsEnabled: this.commentsEnabled,
files: []
}
language,
views: this.views,
nsfw: this.nsfw,
+ commentsEnabled: this.commentsEnabled,
published: this.createdAt.toISOString(),
updated: this.updatedAt.toISOString(),
mediaType: 'text/markdown',
})
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')
})
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'
// ---------------------------------------------------------------
before(async function () {
- this.timeout(120000)
+ this.timeout(20000)
await flushTests()
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
+ })
})
})
/* tslint:disable:no-unused-expression */
+import * as chai from 'chai'
import 'mocha'
import {
flushTests, killallServers, makeGetRequest, makePostBodyRequest, runServer, ServerInfo, setAccessTokensToServers,
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
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 () {
})
})
+ 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 ])
licence: 1,
language: 6,
nsfw: false,
+ commentsEnabled: true,
description: 'my super description',
tags: [ 'tag1', 'tag2' ],
privacy: VideoPrivacy.PUBLIC,
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
licence: 2,
language: 6,
nsfw: false,
+ commentsEnabled: false,
description: 'my super description',
privacy: VideoPrivacy.PUBLIC,
tags: [ 'tag1', 'tag2' ]
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) })
host: 'localhost:9003',
account: 'root',
isLocal,
+ commentsEnabled: true,
duration: 5,
tags: [ 'tag1', 'tag2', 'tag3' ],
privacy: VideoPrivacy.PUBLIC,
duration: 10,
tags: [ 'tag1p1', 'tag2p1' ],
privacy: VideoPrivacy.PUBLIC,
+ commentsEnabled: true,
channel: {
name: 'my channel',
description: 'super channel',
host: 'localhost:9002',
account: 'user1',
isLocal,
+ commentsEnabled: true,
duration: 5,
tags: [ 'tag1p2', 'tag2p2', 'tag3p2' ],
privacy: VideoPrivacy.PUBLIC,
account: 'root',
isLocal,
duration: 5,
+ commentsEnabled: true,
tags: [ 'tag1p3' ],
privacy: VideoPrivacy.PUBLIC,
channel: {
description: 'my super description for server 3-2',
host: 'localhost:9003',
account: 'root',
+ commentsEnabled: true,
isLocal,
duration: 5,
tags: [ 'tag2p3', 'tag3p3', 'tag4p3' ],
account: 'root',
isLocal,
duration: 5,
+ commentsEnabled: true,
tags: [ 'tag_up_1', 'tag_up_2' ],
privacy: VideoPrivacy.PUBLIC,
channel: {
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 () {
.field('privacy', '1')
.field('nsfw', 'false')
.field('channelId', '1')
+ .field('commentsEnabled', 'true')
const filePath = join(__dirname, '..', '..', 'api', 'fixtures', 'video_short.webm')
account: 'root',
isLocal,
duration: 5,
+ commentsEnabled: true,
tags: [ ],
privacy: VideoPrivacy.PUBLIC,
channel: {
duration: 5,
tags: [ 'tag1', 'tag2', 'tag3' ],
privacy: VideoPrivacy.PUBLIC,
+ commentsEnabled: true,
channel: {
name: 'Default root channel',
description: '',
category: 4,
licence: 2,
language: 5,
- nsfw: true,
+ nsfw: false,
description: 'my super description updated',
host: 'localhost:9001',
account: 'root',
tags: [ 'tagup1', 'tagup2' ],
privacy: VideoPrivacy.PUBLIC,
duration: 5,
+ commentsEnabled: false,
channel: {
name: 'Default root channel',
description: '',
language: 5,
nsfw: false,
description: 'my super description updated',
+ commentsEnabled: false,
tags: [ 'tagup1', 'tagup2' ]
}
await updateVideo(server.url, server.accessToken, videoId, attributes)
licence?: number
language?: number
nsfw?: boolean
+ commentsEnabled?: boolean
description?: string
tags?: string[]
channelId?: number
description: 'my super description',
tags: [ 'tag' ],
privacy: VideoPrivacy.PUBLIC,
+ commentsEnabled: true,
fixture: 'video_short.webm'
}
attributes = Object.assign(attributes, 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)
.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 = {}
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
licence: number
language: number
nsfw: boolean
+ commentsEnabled: boolean
description: string
host: string
account: string
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)
language: ActivityIdentifierObject
views: number
nsfw: boolean
+ commentsEnabled: boolean
published: string
updated: string
mediaType: 'text/markdown'
nsfw: boolean
name: string
tags?: string[]
+ commentsEnabled?: boolean
privacy: VideoPrivacy
}
description?: string
privacy?: VideoPrivacy
tags?: string[]
+ commentsEnabled?: boolean
nsfw?: boolean
}
tags: string[]
files: VideoFile[]
account: Account
+ commentsEnabled: boolean
}