mirror of https://github.com/Chocobozzz/PeerTube
Add comments controller
parent
6d85247028
commit
bf1f650817
|
@ -1,88 +1,114 @@
|
|||
// import * as express from 'express'
|
||||
// import { logger, getFormattedObjects } from '../../../helpers'
|
||||
// import {
|
||||
// authenticate,
|
||||
// ensureUserHasRight,
|
||||
// videosBlacklistAddValidator,
|
||||
// videosBlacklistRemoveValidator,
|
||||
// paginationValidator,
|
||||
// blacklistSortValidator,
|
||||
// setBlacklistSort,
|
||||
// setPagination,
|
||||
// asyncMiddleware
|
||||
// } from '../../../middlewares'
|
||||
// import { BlacklistedVideo, UserRight } from '../../../../shared'
|
||||
// import { VideoBlacklistModel } from '../../../models/video/video-blacklist'
|
||||
//
|
||||
// const videoCommentRouter = express.Router()
|
||||
//
|
||||
// videoCommentRouter.get('/:videoId/comment',
|
||||
// authenticate,
|
||||
// ensureUserHasRight(UserRight.MANAGE_VIDEO_BLACKLIST),
|
||||
// asyncMiddleware(listVideoCommentsThreadsValidator),
|
||||
// asyncMiddleware(listVideoCommentsThreads)
|
||||
// )
|
||||
//
|
||||
// videoCommentRouter.post('/:videoId/comment',
|
||||
// authenticate,
|
||||
// ensureUserHasRight(UserRight.MANAGE_VIDEO_BLACKLIST),
|
||||
// asyncMiddleware(videosBlacklistAddValidator),
|
||||
// asyncMiddleware(addVideoToBlacklist)
|
||||
// )
|
||||
//
|
||||
// videoCommentRouter.get('/blacklist',
|
||||
// authenticate,
|
||||
// ensureUserHasRight(UserRight.MANAGE_VIDEO_BLACKLIST),
|
||||
// paginationValidator,
|
||||
// blacklistSortValidator,
|
||||
// setBlacklistSort,
|
||||
// setPagination,
|
||||
// asyncMiddleware(listBlacklist)
|
||||
// )
|
||||
//
|
||||
// videoCommentRouter.delete('/:videoId/blacklist',
|
||||
// authenticate,
|
||||
// ensureUserHasRight(UserRight.MANAGE_VIDEO_BLACKLIST),
|
||||
// asyncMiddleware(videosBlacklistRemoveValidator),
|
||||
// asyncMiddleware(removeVideoFromBlacklistController)
|
||||
// )
|
||||
//
|
||||
// // ---------------------------------------------------------------------------
|
||||
//
|
||||
// export {
|
||||
// videoCommentRouter
|
||||
// }
|
||||
//
|
||||
// // ---------------------------------------------------------------------------
|
||||
//
|
||||
// async function addVideoToBlacklist (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
// const videoInstance = res.locals.video
|
||||
//
|
||||
// const toCreate = {
|
||||
// videoId: videoInstance.id
|
||||
// }
|
||||
//
|
||||
// await VideoBlacklistModel.create(toCreate)
|
||||
// return res.type('json').status(204).end()
|
||||
// }
|
||||
//
|
||||
// async function listBlacklist (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
// const resultList = await VideoBlacklistModel.listForApi(req.query.start, req.query.count, req.query.sort)
|
||||
//
|
||||
// return res.json(getFormattedObjects<BlacklistedVideo, VideoBlacklistModel>(resultList.data, resultList.total))
|
||||
// }
|
||||
//
|
||||
// async function removeVideoFromBlacklistController (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
// const blacklistedVideo = res.locals.blacklistedVideo as VideoBlacklistModel
|
||||
//
|
||||
// try {
|
||||
// await blacklistedVideo.destroy()
|
||||
//
|
||||
// logger.info('Video %s removed from blacklist.', res.locals.video.uuid)
|
||||
//
|
||||
// return res.sendStatus(204)
|
||||
// } catch (err) {
|
||||
// logger.error('Some error while removing video %s from blacklist.', res.locals.video.uuid, err)
|
||||
// throw err
|
||||
// }
|
||||
// }
|
||||
import * as express from 'express'
|
||||
import { VideoCommentCreate } from '../../../../shared/models/videos/video-comment.model'
|
||||
import { getFormattedObjects, retryTransactionWrapper } from '../../../helpers'
|
||||
import { sequelizeTypescript } from '../../../initializers'
|
||||
import { buildFormattedCommentTree, createVideoComment } from '../../../lib/video-comment'
|
||||
import { asyncMiddleware, authenticate, paginationValidator, setPagination, setVideoCommentThreadsSort } from '../../../middlewares'
|
||||
import { videoCommentThreadsSortValidator } from '../../../middlewares/validators'
|
||||
import {
|
||||
addVideoCommentReplyValidator, addVideoCommentThreadValidator, listVideoCommentThreadsValidator,
|
||||
listVideoThreadCommentsValidator
|
||||
} from '../../../middlewares/validators/video-comments'
|
||||
import { VideoCommentModel } from '../../../models/video/video-comment'
|
||||
|
||||
const videoCommentRouter = express.Router()
|
||||
|
||||
videoCommentRouter.get('/:videoId/comment-threads',
|
||||
paginationValidator,
|
||||
videoCommentThreadsSortValidator,
|
||||
setVideoCommentThreadsSort,
|
||||
setPagination,
|
||||
asyncMiddleware(listVideoCommentThreadsValidator),
|
||||
asyncMiddleware(listVideoThreads)
|
||||
)
|
||||
videoCommentRouter.get('/:videoId/comment-threads/:threadId',
|
||||
asyncMiddleware(listVideoThreadCommentsValidator),
|
||||
asyncMiddleware(listVideoThreadComments)
|
||||
)
|
||||
|
||||
videoCommentRouter.post('/:videoId/comment-threads',
|
||||
authenticate,
|
||||
asyncMiddleware(addVideoCommentThreadValidator),
|
||||
asyncMiddleware(addVideoCommentThreadRetryWrapper)
|
||||
)
|
||||
videoCommentRouter.post('/:videoId/comments/:commentId',
|
||||
authenticate,
|
||||
asyncMiddleware(addVideoCommentReplyValidator),
|
||||
asyncMiddleware(addVideoCommentReplyRetryWrapper)
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
videoCommentRouter
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
return res.json(buildFormattedCommentTree(resultList))
|
||||
}
|
||||
|
||||
async function addVideoCommentThreadRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
const options = {
|
||||
arguments: [ req, res ],
|
||||
errorMessage: 'Cannot insert the video comment thread with many retries.'
|
||||
}
|
||||
|
||||
const comment = await retryTransactionWrapper(addVideoCommentThread, options)
|
||||
|
||||
res.json({
|
||||
comment: {
|
||||
id: comment.id
|
||||
}
|
||||
}).end()
|
||||
}
|
||||
|
||||
function addVideoCommentThread (req: express.Request, res: express.Response) {
|
||||
const videoCommentInfo: VideoCommentCreate = req.body
|
||||
|
||||
return sequelizeTypescript.transaction(async t => {
|
||||
return createVideoComment({
|
||||
text: videoCommentInfo.text,
|
||||
inReplyToComment: null,
|
||||
video: res.locals.video,
|
||||
actorId: res.locals.oauth.token.User.Account.Actor.id
|
||||
}, t)
|
||||
})
|
||||
}
|
||||
|
||||
async function addVideoCommentReplyRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
const options = {
|
||||
arguments: [ req, res ],
|
||||
errorMessage: 'Cannot insert the video comment reply with many retries.'
|
||||
}
|
||||
|
||||
const comment = await retryTransactionWrapper(addVideoCommentReply, options)
|
||||
|
||||
res.json({
|
||||
comment: {
|
||||
id: comment.id
|
||||
}
|
||||
}).end()
|
||||
}
|
||||
|
||||
function addVideoCommentReply (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
const videoCommentInfo: VideoCommentCreate = req.body
|
||||
|
||||
return sequelizeTypescript.transaction(async t => {
|
||||
return createVideoComment({
|
||||
text: videoCommentInfo.text,
|
||||
inReplyToComment: res.locals.videoComment.id,
|
||||
video: res.locals.video,
|
||||
actorId: res.locals.oauth.token.User.Account.Actor.id
|
||||
}, t)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -47,6 +47,7 @@ import { VideoFileModel } from '../../../models/video/video-file'
|
|||
import { abuseVideoRouter } from './abuse'
|
||||
import { blacklistRouter } from './blacklist'
|
||||
import { videoChannelRouter } from './channel'
|
||||
import { videoCommentRouter } from './comment'
|
||||
import { rateVideoRouter } from './rate'
|
||||
|
||||
const videosRouter = express.Router()
|
||||
|
@ -78,6 +79,7 @@ videosRouter.use('/', abuseVideoRouter)
|
|||
videosRouter.use('/', blacklistRouter)
|
||||
videosRouter.use('/', rateVideoRouter)
|
||||
videosRouter.use('/', videoChannelRouter)
|
||||
videosRouter.use('/', videoCommentRouter)
|
||||
|
||||
videosRouter.get('/categories', listVideoCategories)
|
||||
videosRouter.get('/licences', listVideoLicences)
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
import 'express-validator'
|
||||
import 'multer'
|
||||
import * as validator from 'validator'
|
||||
import { CONSTRAINTS_FIELDS } from '../../initializers'
|
||||
|
||||
const VIDEO_COMMENTS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_COMMENTS
|
||||
|
||||
function isValidVideoCommentText (value: string) {
|
||||
return value === null || validator.isLength(value, VIDEO_COMMENTS_CONSTRAINTS_FIELDS.TEXT)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
isValidVideoCommentText
|
||||
}
|
|
@ -26,6 +26,7 @@ const SORTABLE_COLUMNS = {
|
|||
VIDEO_ABUSES: [ 'id', 'createdAt' ],
|
||||
VIDEO_CHANNELS: [ 'id', 'name', 'updatedAt', 'createdAt' ],
|
||||
VIDEOS: [ 'name', 'duration', 'createdAt', 'views', 'likes' ],
|
||||
VIDEO_COMMENT_THREADS: [ 'createdAt' ],
|
||||
BLACKLISTS: [ 'id', 'name', 'duration', 'views', 'likes', 'dislikes', 'uuid', 'createdAt' ],
|
||||
FOLLOWERS: [ 'createdAt' ],
|
||||
FOLLOWING: [ 'createdAt' ]
|
||||
|
@ -176,7 +177,8 @@ const CONSTRAINTS_FIELDS = {
|
|||
VIDEO_EVENTS: {
|
||||
COUNT: { min: 0 }
|
||||
},
|
||||
COMMENT: {
|
||||
VIDEO_COMMENTS: {
|
||||
TEXT: { min: 2, max: 3000 }, // Length
|
||||
URL: { min: 3, max: 2000 } // Length
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,17 +3,18 @@ import { ActorModel } from '../../models/activitypub/actor'
|
|||
import { ActorFollowModel } from '../../models/activitypub/actor-follow'
|
||||
import { VideoModel } from '../../models/video/video'
|
||||
import { VideoAbuseModel } from '../../models/video/video-abuse'
|
||||
import { VideoCommentModel } from '../../models/video/video-comment'
|
||||
|
||||
function getVideoActivityPubUrl (video: VideoModel) {
|
||||
return CONFIG.WEBSERVER.URL + '/videos/watch/' + video.uuid
|
||||
}
|
||||
|
||||
function getVideoChannelActivityPubUrl (videoChannelUUID: string) {
|
||||
return CONFIG.WEBSERVER.URL + '/video-channels/' + videoChannelUUID
|
||||
function getVideoCommentActivityPubUrl (video: VideoModel, videoComment: VideoCommentModel) {
|
||||
return CONFIG.WEBSERVER.URL + '/videos/watch/' + video.uuid + '#comment-' + videoComment.id
|
||||
}
|
||||
|
||||
function getApplicationActivityPubUrl () {
|
||||
return CONFIG.WEBSERVER.URL + '/application/peertube'
|
||||
function getVideoChannelActivityPubUrl (videoChannelUUID: string) {
|
||||
return CONFIG.WEBSERVER.URL + '/video-channels/' + videoChannelUUID
|
||||
}
|
||||
|
||||
function getAccountActivityPubUrl (accountName: string) {
|
||||
|
@ -63,7 +64,6 @@ function getUndoActivityPubUrl (originalUrl: string) {
|
|||
}
|
||||
|
||||
export {
|
||||
getApplicationActivityPubUrl,
|
||||
getVideoActivityPubUrl,
|
||||
getVideoChannelActivityPubUrl,
|
||||
getAccountActivityPubUrl,
|
||||
|
@ -75,5 +75,6 @@ export {
|
|||
getUndoActivityPubUrl,
|
||||
getVideoViewActivityPubUrl,
|
||||
getVideoLikeActivityPubUrl,
|
||||
getVideoDislikeActivityPubUrl
|
||||
getVideoDislikeActivityPubUrl,
|
||||
getVideoCommentActivityPubUrl
|
||||
}
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
import * as Sequelize from 'sequelize'
|
||||
import { ResultList } from '../../shared/models'
|
||||
import { VideoCommentThread } from '../../shared/models/videos/video-comment.model'
|
||||
import { VideoModel } from '../models/video/video'
|
||||
import { VideoCommentModel } from '../models/video/video-comment'
|
||||
import { getVideoCommentActivityPubUrl } from './activitypub'
|
||||
|
||||
async function createVideoComment (obj: {
|
||||
text: string,
|
||||
inReplyToComment: number,
|
||||
video: VideoModel
|
||||
actorId: number
|
||||
}, t: Sequelize.Transaction) {
|
||||
let originCommentId: number = null
|
||||
if (obj.inReplyToComment) {
|
||||
const repliedComment = await VideoCommentModel.loadById(obj.inReplyToComment)
|
||||
if (!repliedComment) throw new Error('Unknown replied comment.')
|
||||
|
||||
originCommentId = repliedComment.originCommentId || repliedComment.id
|
||||
}
|
||||
|
||||
const comment = await VideoCommentModel.create({
|
||||
text: obj.text,
|
||||
originCommentId,
|
||||
inReplyToComment: obj.inReplyToComment,
|
||||
videoId: obj.video.id,
|
||||
actorId: obj.actorId
|
||||
}, { transaction: t })
|
||||
|
||||
comment.set('url', getVideoCommentActivityPubUrl(obj.video, comment))
|
||||
|
||||
return comment.save({ transaction: t })
|
||||
}
|
||||
|
||||
function buildFormattedCommentTree (resultList: ResultList<VideoCommentModel>): VideoCommentThread {
|
||||
// Comments are sorted by id ASC
|
||||
const comments = resultList.data
|
||||
|
||||
const comment = comments.shift()
|
||||
const thread: VideoCommentThread = {
|
||||
comment: comment.toFormattedJSON(),
|
||||
children: []
|
||||
}
|
||||
const idx = {
|
||||
[comment.id]: thread
|
||||
}
|
||||
|
||||
while (comments.length !== 0) {
|
||||
const childComment = comments.shift()
|
||||
|
||||
const childCommentThread: VideoCommentThread = {
|
||||
comment: childComment.toFormattedJSON(),
|
||||
children: []
|
||||
}
|
||||
|
||||
const parentCommentThread = idx[childComment.inReplyToCommentId]
|
||||
if (!parentCommentThread) {
|
||||
const msg = `Cannot format video thread tree, parent ${childComment.inReplyToCommentId} not found for child ${childComment.id}`
|
||||
throw new Error(msg)
|
||||
}
|
||||
|
||||
parentCommentThread.children.push(childCommentThread)
|
||||
idx[childComment.id] = childCommentThread
|
||||
}
|
||||
|
||||
return thread
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
createVideoComment,
|
||||
buildFormattedCommentTree
|
||||
}
|
|
@ -32,6 +32,12 @@ function setVideosSort (req: express.Request, res: express.Response, next: expre
|
|||
return next()
|
||||
}
|
||||
|
||||
function setVideoCommentThreadsSort (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
if (!req.query.sort) req.query.sort = '-createdAt'
|
||||
|
||||
return next()
|
||||
}
|
||||
|
||||
function setFollowersSort (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
if (!req.query.sort) req.query.sort = '-createdAt'
|
||||
|
||||
|
@ -75,5 +81,6 @@ export {
|
|||
setBlacklistSort,
|
||||
setFollowersSort,
|
||||
setFollowingSort,
|
||||
setJobsSort
|
||||
setJobsSort,
|
||||
setVideoCommentThreadsSort
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ const SORTABLE_USERS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.USERS)
|
|||
const SORTABLE_JOBS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.JOBS)
|
||||
const SORTABLE_VIDEO_ABUSES_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_ABUSES)
|
||||
const SORTABLE_VIDEOS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEOS)
|
||||
const SORTABLE_VIDEO_COMMENT_THREADS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_COMMENT_THREADS)
|
||||
const SORTABLE_BLACKLISTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.BLACKLISTS)
|
||||
const SORTABLE_VIDEO_CHANNELS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_CHANNELS)
|
||||
const SORTABLE_FOLLOWERS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.FOLLOWERS)
|
||||
|
@ -18,6 +19,7 @@ const usersSortValidator = checkSort(SORTABLE_USERS_COLUMNS)
|
|||
const jobsSortValidator = checkSort(SORTABLE_JOBS_COLUMNS)
|
||||
const videoAbusesSortValidator = checkSort(SORTABLE_VIDEO_ABUSES_COLUMNS)
|
||||
const videosSortValidator = checkSort(SORTABLE_VIDEOS_COLUMNS)
|
||||
const videoCommentThreadsSortValidator = checkSort(SORTABLE_VIDEO_COMMENT_THREADS_COLUMNS)
|
||||
const blacklistSortValidator = checkSort(SORTABLE_BLACKLISTS_COLUMNS)
|
||||
const videoChannelsSortValidator = checkSort(SORTABLE_VIDEO_CHANNELS_COLUMNS)
|
||||
const followersSortValidator = checkSort(SORTABLE_FOLLOWERS_COLUMNS)
|
||||
|
@ -33,7 +35,8 @@ export {
|
|||
blacklistSortValidator,
|
||||
followersSortValidator,
|
||||
followingSortValidator,
|
||||
jobsSortValidator
|
||||
jobsSortValidator,
|
||||
videoCommentThreadsSortValidator
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
import * as express from 'express'
|
||||
import { body, param } from 'express-validator/check'
|
||||
import { logger } from '../../helpers'
|
||||
import { isIdOrUUIDValid, isIdValid } from '../../helpers/custom-validators/misc'
|
||||
import { isValidVideoCommentText } from '../../helpers/custom-validators/video-comments'
|
||||
import { isVideoExist } from '../../helpers/custom-validators/videos'
|
||||
import { VideoCommentModel } from '../../models/video/video-comment'
|
||||
import { areValidationErrors } from './utils'
|
||||
|
||||
const listVideoCommentThreadsValidator = [
|
||||
param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
|
||||
|
||||
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
logger.debug('Checking blacklistRemove parameters.', { parameters: req.params })
|
||||
|
||||
if (areValidationErrors(req, res)) return
|
||||
if (!await isVideoExist(req.params.videoId, res)) return
|
||||
|
||||
return next()
|
||||
}
|
||||
]
|
||||
|
||||
const listVideoThreadCommentsValidator = [
|
||||
param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
|
||||
param('threadId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid threadId'),
|
||||
|
||||
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
logger.debug('Checking blacklistRemove parameters.', { parameters: req.params })
|
||||
|
||||
if (areValidationErrors(req, res)) return
|
||||
if (!await isVideoExist(req.params.videoId, res)) return
|
||||
if (!await isVideoCommentThreadExist(req.params.threadId, req.params.videoId, res)) return
|
||||
|
||||
return next()
|
||||
}
|
||||
]
|
||||
|
||||
const addVideoCommentThreadValidator = [
|
||||
param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
|
||||
body('text').custom(isValidVideoCommentText).not().isEmpty().withMessage('Should have a valid comment text'),
|
||||
|
||||
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
logger.debug('Checking blacklistRemove parameters.', { parameters: req.params })
|
||||
|
||||
if (areValidationErrors(req, res)) return
|
||||
if (!await isVideoExist(req.params.videoId, res)) return
|
||||
|
||||
return next()
|
||||
}
|
||||
]
|
||||
|
||||
const addVideoCommentReplyValidator = [
|
||||
param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
|
||||
param('commentId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid commentId'),
|
||||
body('text').custom(isValidVideoCommentText).not().isEmpty().withMessage('Should have a valid comment text'),
|
||||
|
||||
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
logger.debug('Checking blacklistRemove parameters.', { parameters: req.params })
|
||||
|
||||
if (areValidationErrors(req, res)) return
|
||||
if (!await isVideoExist(req.params.videoId, res)) return
|
||||
if (!await isVideoCommentExist(req.params.commentId, req.params.videoId, res)) return
|
||||
|
||||
return next()
|
||||
}
|
||||
]
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
listVideoCommentThreadsValidator,
|
||||
listVideoThreadCommentsValidator,
|
||||
addVideoCommentThreadValidator,
|
||||
addVideoCommentReplyValidator
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function isVideoCommentThreadExist (id: number, videoId: number, res: express.Response) {
|
||||
const videoComment = await VideoCommentModel.loadById(id)
|
||||
|
||||
if (!videoComment) {
|
||||
res.status(404)
|
||||
.json({ error: 'Video comment thread not found' })
|
||||
.end()
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
if (videoComment.videoId !== videoId) {
|
||||
res.status(400)
|
||||
.json({ error: 'Video comment is associated to this video.' })
|
||||
.end()
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
if (videoComment.inReplyToCommentId !== null) {
|
||||
res.status(400)
|
||||
.json({ error: 'Video comment is not a thread.' })
|
||||
.end()
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
res.locals.videoCommentThread = videoComment
|
||||
return true
|
||||
}
|
||||
|
||||
async function isVideoCommentExist (id: number, videoId: number, res: express.Response) {
|
||||
const videoComment = await VideoCommentModel.loadById(id)
|
||||
|
||||
if (!videoComment) {
|
||||
res.status(404)
|
||||
.json({ error: 'Video comment thread not found' })
|
||||
.end()
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
if (videoComment.videoId !== videoId) {
|
||||
res.status(400)
|
||||
.json({ error: 'Video comment is associated to this video.' })
|
||||
.end()
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
res.locals.videoComment = videoComment
|
||||
return true
|
||||
}
|
|
@ -1,19 +1,34 @@
|
|||
import * as Sequelize from 'sequelize'
|
||||
import {
|
||||
AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, IFindOptions, Is, IsUUID, Model, Table,
|
||||
AfterDestroy, AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, IFindOptions, Is, Model, Scopes, Table,
|
||||
UpdatedAt
|
||||
} from 'sequelize-typescript'
|
||||
import { VideoComment } from '../../../shared/models/videos/video-comment.model'
|
||||
import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub'
|
||||
import { CONSTRAINTS_FIELDS } from '../../initializers'
|
||||
import { ActorModel } from '../activitypub/actor'
|
||||
import { throwIfNotValid } from '../utils'
|
||||
import { getSort, throwIfNotValid } from '../utils'
|
||||
import { VideoModel } from './video'
|
||||
|
||||
enum ScopeNames {
|
||||
WITH_ACTOR = 'WITH_ACTOR'
|
||||
}
|
||||
|
||||
@Scopes({
|
||||
[ScopeNames.WITH_ACTOR]: {
|
||||
include: [
|
||||
() => ActorModel
|
||||
]
|
||||
}
|
||||
})
|
||||
@Table({
|
||||
tableName: 'videoComment',
|
||||
indexes: [
|
||||
{
|
||||
fields: [ 'videoId' ]
|
||||
},
|
||||
{
|
||||
fields: [ 'videoId', 'originCommentId' ]
|
||||
}
|
||||
]
|
||||
})
|
||||
|
@ -81,6 +96,24 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
|
|||
})
|
||||
Actor: ActorModel
|
||||
|
||||
@AfterDestroy
|
||||
static sendDeleteIfOwned (instance: VideoCommentModel) {
|
||||
// TODO
|
||||
return undefined
|
||||
}
|
||||
|
||||
static loadById (id: number, t?: Sequelize.Transaction) {
|
||||
const query: IFindOptions<VideoCommentModel> = {
|
||||
where: {
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
if (t !== undefined) query.transaction = t
|
||||
|
||||
return VideoCommentModel.findOne(query)
|
||||
}
|
||||
|
||||
static loadByUrl (url: string, t?: Sequelize.Transaction) {
|
||||
const query: IFindOptions<VideoCommentModel> = {
|
||||
where: {
|
||||
|
@ -92,4 +125,55 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
|
|||
|
||||
return VideoCommentModel.findOne(query)
|
||||
}
|
||||
|
||||
static listThreadsForApi (videoId: number, start: number, count: number, sort: string) {
|
||||
const query = {
|
||||
offset: start,
|
||||
limit: count,
|
||||
order: [ getSort(sort) ],
|
||||
where: {
|
||||
videoId
|
||||
}
|
||||
}
|
||||
|
||||
return VideoCommentModel
|
||||
.scope([ ScopeNames.WITH_ACTOR ])
|
||||
.findAndCountAll(query)
|
||||
.then(({ rows, count }) => {
|
||||
return { total: count, data: rows }
|
||||
})
|
||||
}
|
||||
|
||||
static listThreadCommentsForApi (videoId: number, threadId: number) {
|
||||
const query = {
|
||||
order: [ 'id', 'ASC' ],
|
||||
where: {
|
||||
videoId,
|
||||
[ Sequelize.Op.or ]: [
|
||||
{ id: threadId },
|
||||
{ originCommentId: threadId }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
return VideoCommentModel
|
||||
.scope([ ScopeNames.WITH_ACTOR ])
|
||||
.findAndCountAll(query)
|
||||
.then(({ rows, count }) => {
|
||||
return { total: count, data: rows }
|
||||
})
|
||||
}
|
||||
|
||||
toFormattedJSON () {
|
||||
return {
|
||||
id: this.id,
|
||||
url: this.url,
|
||||
text: this.text,
|
||||
threadId: this.originCommentId || this.id,
|
||||
inReplyToCommentId: this.inReplyToCommentId,
|
||||
videoId: this.videoId,
|
||||
createdAt: this.createdAt,
|
||||
updatedAt: this.updatedAt
|
||||
} as VideoComment
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
export interface VideoComment {
|
||||
id: number
|
||||
url: string
|
||||
text: string
|
||||
threadId: number
|
||||
inReplyToCommentId: number
|
||||
videoId: number
|
||||
createdAt: Date | string
|
||||
updatedAt: Date | string
|
||||
}
|
||||
|
||||
export interface VideoCommentThread {
|
||||
comment: VideoComment
|
||||
children: VideoCommentThread[]
|
||||
}
|
||||
|
||||
export interface VideoCommentCreate {
|
||||
text: string
|
||||
}
|
Loading…
Reference in New Issue