Add ability to disable video comments
authorChocobozzz <me@florianbigard.com>
Wed, 3 Jan 2018 09:12:36 +0000 (10:12 +0100)
committerChocobozzz <me@florianbigard.com>
Wed, 3 Jan 2018 09:38:19 +0000 (10:38 +0100)
37 files changed:
client/src/app/shared/video/video-details.model.ts
client/src/app/shared/video/video-edit.model.ts
client/src/app/shared/video/video.service.ts
client/src/app/videos/+video-edit/shared/video-edit.component.html
client/src/app/videos/+video-edit/shared/video-edit.component.ts
client/src/app/videos/+video-edit/video-add.component.ts
client/src/app/videos/+video-watch/comment/video-comments.component.html
client/src/app/videos/+video-watch/comment/video-comments.component.ts
server/controllers/api/videos/comment.ts
server/controllers/api/videos/index.ts
server/helpers/custom-validators/activitypub/activity.ts
server/helpers/custom-validators/activitypub/video-channels.ts [deleted file]
server/helpers/custom-validators/activitypub/videos.ts
server/helpers/custom-validators/misc.ts
server/helpers/custom-validators/videos.ts
server/helpers/utils.ts
server/initializers/constants.ts
server/initializers/migrations/0155-video-comments-enabled.ts [new file with mode: 0644]
server/lib/activitypub/process/misc.ts
server/middlewares/validators/users.ts
server/middlewares/validators/video-comments.ts
server/middlewares/validators/videos.ts
server/models/activitypub/actor.ts
server/models/avatar/avatar.ts
server/models/video/video.ts
server/tests/activitypub.ts
server/tests/api/check-params/users.ts
server/tests/api/check-params/video-comments.ts
server/tests/api/check-params/videos.ts
server/tests/api/server/follows.ts
server/tests/api/videos/multiple-servers.ts
server/tests/api/videos/single-server.ts
server/tests/utils/videos/videos.ts
shared/models/activitypub/objects/video-torrent-object.ts
shared/models/videos/video-create.model.ts
shared/models/videos/video-update.model.ts
shared/models/videos/video.model.ts

index 8243b9f1ca064ed3a3c64354da368547a8e79294..cf6b71b608c33c7c019152a61c1a813dba88b8dc 100644 (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
index 47c63d976e295195fc8cd1f85afa26992fa4511b..b1c77221783b11609b19891b32120769855e4069 100644 (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
     }
index fc7505a51fdedca0370882d7acb70e8750336fb1..073acb2b63fcab063a45238bd54e501cc5f5ab21 100644 (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)
index 9acbafcb673ad095da8c77def640eb62160d7811..80377933ecadc712393a5d3a8772d10a2afe9532 100644 (file)
       <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>
index 7fe2652843bc400e450a2b7966a9bc11c8f61b4d..2b307d5fafd081a408468fac173ad24effd5d240 100644 (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))
index 9bbee58d8f2504f3be641349486f23646be30aea..843475647401aa67b5977e30353268db3b732988 100644 (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)
 
index 5c6908150777069ae7c31f88e7fc354b0b23362f..078900e0659aa6035f6e4fa32e729601dac69153 100644 (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>
index f4dda90895e062f2a540cbe80e2e15a280d520cb..4d801c97057b4a1e67a9055da9535e79aeb9e3d7 100644 (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) {
index b11da2ef736dabfcf0c7a6030acc2caceb79ceba..e09b242ed9d688faba8912fe915fd996529fa38f 100644 (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))
 }
index ff0d967e182c5b675ebfa96256be7bb33acd7916..368327914b4276fcf72188e3b499d76680af6c5e 100644 (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)
 
index f2e1370610370de24b705a18af7b8ebc59a97a7f..fbdde10ad51bbac7089a313f9f17ba07bd155140 100644 (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)
 }
 
diff --git a/server/helpers/custom-validators/activitypub/video-channels.ts b/server/helpers/custom-validators/activitypub/video-channels.ts
deleted file mode 100644 (file)
index eb45c63..0000000
+++ /dev/null
@@ -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
-}
index ae1339611a90cc0ce62b780c070bd11617a0c2e8..37cd6965a454e653bcb29843a65885f68a70a909 100644 (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)) &&
index 160ec91f3912211c4ea9b787d489ea9aa981a4d7..3903884eadbe410de35c289626ea6752c259d1ad 100644 (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
 }
index ee9d0ed19908d57dca103e89578354e4d8224dc6..1a5fdb8871403add4089879ffd681c29bf86402e 100644 (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,
index 7a32e286ca56a9c5c341dd03699bfad7a37b293f..b61d6e3fa034baab0ab3120ba0a92864d09ce206 100644 (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'
index 50a29dc4328468132df7a00207d4ad1473f1b03a..31bb6c98139d97d87d2b4aa3b00da13628c1879b 100644 (file)
@@ -9,7 +9,7 @@ import { isTestInstance, root, sanitizeHost, sanitizeUrl } from '../helpers/core
 
 // ---------------------------------------------------------------------------
 
-const LAST_MIGRATION_VERSION = 150
+const LAST_MIGRATION_VERSION = 155
 
 // ---------------------------------------------------------------------------
 
diff --git a/server/initializers/migrations/0155-video-comments-enabled.ts b/server/initializers/migrations/0155-video-comments-enabled.ts
new file mode 100644 (file)
index 0000000..59f4110
--- /dev/null
@@ -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
+}
index f65395c995f0809dca072731bd12f0d8977f56f9..461619ea758c6f4a0ad98016fca5902ce424ed07 100644 (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),
index 42ebddd567984d7513fec89c5aeeb8564379db8b..7c77e9a3945950a61bd57aca53b75f5f5db3f87d 100644 (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'
index fdd092571bc8897376dd0fc6ff1dcd19acb7f6d4..ade0b7b9fb440596e603ce63ea6167eeece07c0d 100644 (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
+}
index bffc5032256273ff55c24c3eaf60ac0314a36341..e8cb2ae03202be93c34555075c7209538901f46b 100644 (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 })
index 8422653df716c8cf3d0bef162f967ee728bcd45f..a12f3ec9eb4ece1c1caab419298c9dcaf5c26ebe 100644 (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'
index 7493c3d75f57dc85c509b558f7f6bf1684c392a0..e1d4c20bccd1dd554200ad0c0e1a674e213cb0ac 100644 (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'
index 2504ae58a1693d668e677a596a0298e15e593c6a..c4b716cd21e68013adbc4f1d0b167f9012d53036 100644 (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',
index 94615c63f0895784f5e57e7e878bc1f0438978e1..c8884719de5ebfe37eed3a4dab44bcdaa54ecbeb 100644 (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')
   })
index 44412ad828771a6ddac814b5cb0b4d0503582fcc..33d92ac24b4b38b06cf7caef713e6735bf4329f7 100644 (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
+      })
     })
   })
 
index cdb48a2769754262191e9b47f26626290337f4cf..c11660d072f0e15910a28a5e3bb68bbad3734fa7 100644 (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 ])
 
index b9484afc406a7401776111932e50575f91adcfd3..5c067dc96a2a4151f975885c9e7b496ba43ae79d 100644 (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) })
 
index fc9c5c3b680fa430245a3fa4267f19e726757b79..e6dfd5f62a5e54771e522be690a139f6f0182801 100644 (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,
index abd051a30f7c6637e43b4b4a8966138ad1a2e86a..b6dfe0d1b569d51075687682ef453d0273c31192 100644 (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: {
index 2a3126f3258df29ace508ab6c18d00dc458ff18f..0a0c95750af5694e4598dd48623b16e7c5178242 100644 (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)
index aca51ee5d456f52a613eaf3e908f2d61893cc0de..c437c21b2e471f5017ac7cccc2a4da0d6d4162df 100644 (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)
index 5ccc80bcbfdbea8c6b732a2558afc51be975503c..cf0e0ba54240444a5322e9479a12a21846981df7 100644 (file)
@@ -18,6 +18,7 @@ export interface VideoTorrentObject {
   language: ActivityIdentifierObject
   views: number
   nsfw: boolean
+  commentsEnabled: boolean
   published: string
   updated: string
   mediaType: 'text/markdown'
index 8bc6a66390e327eec84e945072abca52c5a80463..139c2579e2ae2baf49ce3db5d5c9b26e8502c839 100644 (file)
@@ -9,5 +9,6 @@ export interface VideoCreate {
   nsfw: boolean
   name: string
   tags?: string[]
+  commentsEnabled?: boolean
   privacy: VideoPrivacy
 }
index 0cf38fe6e2cda7a18a11b57b9c351efe3c1ffac4..fc772f77baa35ae6941239c22d9825042e89a812 100644 (file)
@@ -8,5 +8,6 @@ export interface VideoUpdate {
   description?: string
   privacy?: VideoPrivacy
   tags?: string[]
+  commentsEnabled?: boolean
   nsfw?: boolean
 }
index 13b9c49b3e1a9cbd9120d6c4ebfc0c3475a76de9..39d1edc061253f6182ed11a161fb2551fb5e7276 100644 (file)
@@ -45,4 +45,5 @@ export interface VideoDetails extends Video {
   tags: string[]
   files: VideoFile[]
   account: Account
+  commentsEnabled: boolean
 }