]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/models/video/video-comment.ts
Send video comment comments to followers/origin
[github/Chocobozzz/PeerTube.git] / server / models / video / video-comment.ts
1 import * as Sequelize from 'sequelize'
2 import {
3 AfterDestroy, AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, IFindOptions, Is, Model, Scopes, Table,
4 UpdatedAt
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'
9 import { CONSTRAINTS_FIELDS } from '../../initializers'
10 import { AccountModel } from '../account/account'
11 import { getSort, throwIfNotValid } from '../utils'
12 import { VideoModel } from './video'
13
14 enum ScopeNames {
15 WITH_ACCOUNT = 'WITH_ACCOUNT',
16 WITH_IN_REPLY_TO = 'WITH_IN_REPLY_TO'
17 }
18
19 @Scopes({
20 [ScopeNames.WITH_ACCOUNT]: {
21 include: [
22 () => AccountModel
23 ]
24 },
25 [ScopeNames.WITH_IN_REPLY_TO]: {
26 include: [
27 {
28 model: () => VideoCommentModel,
29 as: 'InReplyTo'
30 }
31 ]
32 }
33 })
34 @Table({
35 tableName: 'videoComment',
36 indexes: [
37 {
38 fields: [ 'videoId' ]
39 },
40 {
41 fields: [ 'videoId', 'originCommentId' ]
42 }
43 ]
44 })
45 export class VideoCommentModel extends Model<VideoCommentModel> {
46 @CreatedAt
47 createdAt: Date
48
49 @UpdatedAt
50 updatedAt: Date
51
52 @AllowNull(false)
53 @Is('VideoCommentUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url'))
54 @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEOS.URL.max))
55 url: string
56
57 @AllowNull(false)
58 @Column(DataType.TEXT)
59 text: string
60
61 @ForeignKey(() => VideoCommentModel)
62 @Column
63 originCommentId: number
64
65 @BelongsTo(() => VideoCommentModel, {
66 foreignKey: {
67 allowNull: true
68 },
69 onDelete: 'CASCADE'
70 })
71 OriginVideoComment: VideoCommentModel
72
73 @ForeignKey(() => VideoCommentModel)
74 @Column
75 inReplyToCommentId: number
76
77 @BelongsTo(() => VideoCommentModel, {
78 foreignKey: {
79 allowNull: true
80 },
81 as: 'InReplyTo',
82 onDelete: 'CASCADE'
83 })
84 InReplyToVideoComment: VideoCommentModel
85
86 @ForeignKey(() => VideoModel)
87 @Column
88 videoId: number
89
90 @BelongsTo(() => VideoModel, {
91 foreignKey: {
92 allowNull: false
93 },
94 onDelete: 'CASCADE'
95 })
96 Video: VideoModel
97
98 @ForeignKey(() => AccountModel)
99 @Column
100 accountId: number
101
102 @BelongsTo(() => AccountModel, {
103 foreignKey: {
104 allowNull: false
105 },
106 onDelete: 'CASCADE'
107 })
108 Account: AccountModel
109
110 @AfterDestroy
111 static sendDeleteIfOwned (instance: VideoCommentModel) {
112 // TODO
113 return undefined
114 }
115
116 static loadById (id: number, t?: Sequelize.Transaction) {
117 const query: IFindOptions<VideoCommentModel> = {
118 where: {
119 id
120 }
121 }
122
123 if (t !== undefined) query.transaction = t
124
125 return VideoCommentModel.findOne(query)
126 }
127
128 static loadByUrl (url: string, t?: Sequelize.Transaction) {
129 const query: IFindOptions<VideoCommentModel> = {
130 where: {
131 url
132 }
133 }
134
135 if (t !== undefined) query.transaction = t
136
137 return VideoCommentModel.findOne(query)
138 }
139
140 static listThreadsForApi (videoId: number, start: number, count: number, sort: string) {
141 const query = {
142 offset: start,
143 limit: count,
144 order: [ getSort(sort) ],
145 where: {
146 videoId,
147 inReplyToCommentId: null
148 }
149 }
150
151 return VideoCommentModel
152 .scope([ ScopeNames.WITH_ACCOUNT ])
153 .findAndCountAll(query)
154 .then(({ rows, count }) => {
155 return { total: count, data: rows }
156 })
157 }
158
159 static listThreadCommentsForApi (videoId: number, threadId: number) {
160 const query = {
161 order: [ [ 'id', 'ASC' ] ],
162 where: {
163 videoId,
164 [ Sequelize.Op.or ]: [
165 { id: threadId },
166 { originCommentId: threadId }
167 ]
168 }
169 }
170
171 return VideoCommentModel
172 .scope([ ScopeNames.WITH_ACCOUNT ])
173 .findAndCountAll(query)
174 .then(({ rows, count }) => {
175 return { total: count, data: rows }
176 })
177 }
178
179 toFormattedJSON () {
180 return {
181 id: this.id,
182 url: this.url,
183 text: this.text,
184 threadId: this.originCommentId || this.id,
185 inReplyToCommentId: this.inReplyToCommentId,
186 videoId: this.videoId,
187 createdAt: this.createdAt,
188 updatedAt: this.updatedAt,
189 account: {
190 name: this.Account.name
191 }
192 } as VideoComment
193 }
194
195 toActivityPubObject (): VideoCommentObject {
196 let inReplyTo: string
197 // New thread, so in AS we reply to the video
198 if (this.inReplyToCommentId === null) {
199 inReplyTo = this.Video.url
200 } else {
201 inReplyTo = this.InReplyToVideoComment.url
202 }
203
204 return {
205 type: 'Note' as 'Note',
206 id: this.url,
207 content: this.text,
208 inReplyTo,
209 published: this.createdAt.toISOString(),
210 url: this.url
211 }
212 }
213 }