1 import * as Sequelize from 'sequelize'
3 AfterDestroy, AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, IFindOptions, Is, Model, Scopes, Table,
5 } from 'sequelize-typescript'
6 import { VideoCommentObject } from '../../../shared/models/activitypub/objects/video-comment-object'
7 import { VideoComment } from '../../../shared/models/videos/video-comment.model'
8 import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
9 import { CONSTRAINTS_FIELDS } from '../../initializers'
10 import { AccountModel } from '../account/account'
11 import { ActorModel } from '../activitypub/actor'
12 import { ServerModel } from '../server/server'
13 import { getSort, throwIfNotValid } from '../utils'
14 import { VideoModel } from './video'
17 WITH_ACCOUNT = 'WITH_ACCOUNT',
18 WITH_IN_REPLY_TO = 'WITH_IN_REPLY_TO',
19 WITH_VIDEO = 'WITH_VIDEO',
20 ATTRIBUTES_FOR_API = 'ATTRIBUTES_FOR_API'
24 [ScopeNames.ATTRIBUTES_FOR_API]: {
29 '(SELECT COUNT("replies"."id") ' +
30 'FROM "videoComment" AS "replies" ' +
31 'WHERE "replies"."originCommentId" = "VideoCommentModel"."id")'
38 [ScopeNames.WITH_ACCOUNT]: {
41 model: () => AccountModel,
44 model: () => ActorModel,
47 model: () => ServerModel,
56 [ScopeNames.WITH_IN_REPLY_TO]: {
59 model: () => VideoCommentModel,
60 as: 'InReplyToVideoComment'
64 [ScopeNames.WITH_VIDEO]: {
67 model: () => VideoModel,
74 tableName: 'videoComment',
80 fields: [ 'videoId', 'originCommentId' ]
84 export class VideoCommentModel extends Model<VideoCommentModel> {
92 @Is('VideoCommentUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url'))
93 @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEOS.URL.max))
97 @Column(DataType.TEXT)
100 @ForeignKey(() => VideoCommentModel)
102 originCommentId: number
104 @BelongsTo(() => VideoCommentModel, {
110 OriginVideoComment: VideoCommentModel
112 @ForeignKey(() => VideoCommentModel)
114 inReplyToCommentId: number
116 @BelongsTo(() => VideoCommentModel, {
120 as: 'InReplyToVideoComment',
123 InReplyToVideoComment: VideoCommentModel
125 @ForeignKey(() => VideoModel)
129 @BelongsTo(() => VideoModel, {
137 @ForeignKey(() => AccountModel)
141 @BelongsTo(() => AccountModel, {
147 Account: AccountModel
150 static sendDeleteIfOwned (instance: VideoCommentModel) {
155 static loadById (id: number, t?: Sequelize.Transaction) {
156 const query: IFindOptions<VideoCommentModel> = {
162 if (t !== undefined) query.transaction = t
164 return VideoCommentModel.findOne(query)
167 static loadByIdAndPopulateVideoAndAccountAndReply (id: number, t?: Sequelize.Transaction) {
168 const query: IFindOptions<VideoCommentModel> = {
174 if (t !== undefined) query.transaction = t
176 return VideoCommentModel
177 .scope([ ScopeNames.WITH_VIDEO, ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_IN_REPLY_TO ])
181 static loadByUrl (url: string, t?: Sequelize.Transaction) {
182 const query: IFindOptions<VideoCommentModel> = {
188 if (t !== undefined) query.transaction = t
190 return VideoCommentModel.findOne(query)
193 static listThreadsForApi (videoId: number, start: number, count: number, sort: string) {
197 order: [ getSort(sort) ],
200 inReplyToCommentId: null
204 return VideoCommentModel
205 .scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.ATTRIBUTES_FOR_API ])
206 .findAndCountAll(query)
207 .then(({ rows, count }) => {
208 return { total: count, data: rows }
212 static listThreadCommentsForApi (videoId: number, threadId: number) {
214 order: [ [ 'id', 'ASC' ] ],
217 [ Sequelize.Op.or ]: [
219 { originCommentId: threadId }
224 return VideoCommentModel
225 .scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.ATTRIBUTES_FOR_API ])
226 .findAndCountAll(query)
227 .then(({ rows, count }) => {
228 return { total: count, data: rows }
237 threadId: this.originCommentId || this.id,
238 inReplyToCommentId: this.inReplyToCommentId || null,
239 videoId: this.videoId,
240 createdAt: this.createdAt,
241 updatedAt: this.updatedAt,
242 totalReplies: this.get('totalReplies') || 0,
244 name: this.Account.name,
245 host: this.Account.Actor.getHost()
250 toActivityPubObject (): VideoCommentObject {
251 let inReplyTo: string
252 // New thread, so in AS we reply to the video
253 if (this.inReplyToCommentId === null) {
254 inReplyTo = this.Video.url
256 inReplyTo = this.InReplyToVideoComment.url
260 type: 'Note' as 'Note',
264 updated: this.updatedAt.toISOString(),
265 published: this.createdAt.toISOString(),
267 attributedTo: this.Account.Actor.url