]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/models/video/video-comment.ts
Automatically resize avatars
[github/Chocobozzz/PeerTube.git] / server / models / video / video-comment.ts
CommitLineData
6d852470
C
1import * as Sequelize from 'sequelize'
2import {
bf1f6508 3 AfterDestroy, AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, IFindOptions, Is, Model, Scopes, Table,
6d852470
C
4 UpdatedAt
5} from 'sequelize-typescript'
ea44f375 6import { VideoCommentObject } from '../../../shared/models/activitypub/objects/video-comment-object'
bf1f6508 7import { VideoComment } from '../../../shared/models/videos/video-comment.model'
da854ddd 8import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
6d852470 9import { CONSTRAINTS_FIELDS } from '../../initializers'
d3ea8975 10import { AccountModel } from '../account/account'
4635f59d
C
11import { ActorModel } from '../activitypub/actor'
12import { ServerModel } from '../server/server'
bf1f6508 13import { getSort, throwIfNotValid } from '../utils'
6d852470
C
14import { VideoModel } from './video'
15
bf1f6508 16enum ScopeNames {
ea44f375 17 WITH_ACCOUNT = 'WITH_ACCOUNT',
4635f59d 18 WITH_IN_REPLY_TO = 'WITH_IN_REPLY_TO',
da854ddd 19 WITH_VIDEO = 'WITH_VIDEO',
4635f59d 20 ATTRIBUTES_FOR_API = 'ATTRIBUTES_FOR_API'
bf1f6508
C
21}
22
23@Scopes({
4635f59d
C
24 [ScopeNames.ATTRIBUTES_FOR_API]: {
25 attributes: {
26 include: [
27 [
28 Sequelize.literal(
29 '(SELECT COUNT("replies"."id") ' +
30 'FROM "videoComment" AS "replies" ' +
31 'WHERE "replies"."originCommentId" = "VideoCommentModel"."id")'
32 ),
33 'totalReplies'
34 ]
35 ]
36 }
37 },
d3ea8975 38 [ScopeNames.WITH_ACCOUNT]: {
bf1f6508 39 include: [
4635f59d
C
40 {
41 model: () => AccountModel,
42 include: [
43 {
44 model: () => ActorModel,
45 include: [
46 {
47 model: () => ServerModel,
48 required: false
49 }
50 ]
51 }
52 ]
53 }
bf1f6508 54 ]
ea44f375
C
55 },
56 [ScopeNames.WITH_IN_REPLY_TO]: {
57 include: [
58 {
59 model: () => VideoCommentModel,
da854ddd
C
60 as: 'InReplyToVideoComment'
61 }
62 ]
63 },
64 [ScopeNames.WITH_VIDEO]: {
65 include: [
66 {
67 model: () => VideoModel,
68 required: false
ea44f375
C
69 }
70 ]
bf1f6508
C
71 }
72})
6d852470
C
73@Table({
74 tableName: 'videoComment',
75 indexes: [
76 {
77 fields: [ 'videoId' ]
bf1f6508
C
78 },
79 {
80 fields: [ 'videoId', 'originCommentId' ]
6d852470
C
81 }
82 ]
83})
84export class VideoCommentModel extends Model<VideoCommentModel> {
85 @CreatedAt
86 createdAt: Date
87
88 @UpdatedAt
89 updatedAt: Date
90
91 @AllowNull(false)
92 @Is('VideoCommentUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url'))
93 @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEOS.URL.max))
94 url: string
95
96 @AllowNull(false)
97 @Column(DataType.TEXT)
98 text: string
99
100 @ForeignKey(() => VideoCommentModel)
101 @Column
102 originCommentId: number
103
104 @BelongsTo(() => VideoCommentModel, {
105 foreignKey: {
db799da3 106 name: 'originCommentId',
6d852470
C
107 allowNull: true
108 },
db799da3 109 as: 'OriginVideoComment',
6d852470
C
110 onDelete: 'CASCADE'
111 })
112 OriginVideoComment: VideoCommentModel
113
114 @ForeignKey(() => VideoCommentModel)
115 @Column
116 inReplyToCommentId: number
117
118 @BelongsTo(() => VideoCommentModel, {
119 foreignKey: {
db799da3 120 name: 'inReplyToCommentId',
6d852470
C
121 allowNull: true
122 },
da854ddd 123 as: 'InReplyToVideoComment',
6d852470
C
124 onDelete: 'CASCADE'
125 })
126 InReplyToVideoComment: VideoCommentModel
127
128 @ForeignKey(() => VideoModel)
129 @Column
130 videoId: number
131
132 @BelongsTo(() => VideoModel, {
133 foreignKey: {
134 allowNull: false
135 },
136 onDelete: 'CASCADE'
137 })
138 Video: VideoModel
139
d3ea8975 140 @ForeignKey(() => AccountModel)
6d852470 141 @Column
d3ea8975 142 accountId: number
6d852470 143
d3ea8975 144 @BelongsTo(() => AccountModel, {
6d852470
C
145 foreignKey: {
146 allowNull: false
147 },
148 onDelete: 'CASCADE'
149 })
d3ea8975 150 Account: AccountModel
6d852470 151
bf1f6508
C
152 @AfterDestroy
153 static sendDeleteIfOwned (instance: VideoCommentModel) {
154 // TODO
155 return undefined
156 }
157
158 static loadById (id: number, t?: Sequelize.Transaction) {
159 const query: IFindOptions<VideoCommentModel> = {
160 where: {
161 id
162 }
163 }
164
165 if (t !== undefined) query.transaction = t
166
167 return VideoCommentModel.findOne(query)
168 }
169
da854ddd
C
170 static loadByIdAndPopulateVideoAndAccountAndReply (id: number, t?: Sequelize.Transaction) {
171 const query: IFindOptions<VideoCommentModel> = {
172 where: {
173 id
174 }
175 }
176
177 if (t !== undefined) query.transaction = t
178
179 return VideoCommentModel
180 .scope([ ScopeNames.WITH_VIDEO, ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_IN_REPLY_TO ])
181 .findOne(query)
182 }
183
6d852470
C
184 static loadByUrl (url: string, t?: Sequelize.Transaction) {
185 const query: IFindOptions<VideoCommentModel> = {
186 where: {
187 url
188 }
189 }
190
191 if (t !== undefined) query.transaction = t
192
193 return VideoCommentModel.findOne(query)
194 }
bf1f6508
C
195
196 static listThreadsForApi (videoId: number, start: number, count: number, sort: string) {
197 const query = {
198 offset: start,
199 limit: count,
200 order: [ getSort(sort) ],
201 where: {
d3ea8975
C
202 videoId,
203 inReplyToCommentId: null
bf1f6508
C
204 }
205 }
206
207 return VideoCommentModel
4635f59d 208 .scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.ATTRIBUTES_FOR_API ])
bf1f6508
C
209 .findAndCountAll(query)
210 .then(({ rows, count }) => {
211 return { total: count, data: rows }
212 })
213 }
214
215 static listThreadCommentsForApi (videoId: number, threadId: number) {
216 const query = {
e8e12200 217 order: [ [ 'createdAt', 'ASC' ] ],
bf1f6508
C
218 where: {
219 videoId,
220 [ Sequelize.Op.or ]: [
221 { id: threadId },
222 { originCommentId: threadId }
223 ]
224 }
225 }
226
227 return VideoCommentModel
4635f59d 228 .scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.ATTRIBUTES_FOR_API ])
bf1f6508
C
229 .findAndCountAll(query)
230 .then(({ rows, count }) => {
231 return { total: count, data: rows }
232 })
233 }
234
235 toFormattedJSON () {
236 return {
237 id: this.id,
238 url: this.url,
239 text: this.text,
240 threadId: this.originCommentId || this.id,
d50acfab 241 inReplyToCommentId: this.inReplyToCommentId || null,
bf1f6508
C
242 videoId: this.videoId,
243 createdAt: this.createdAt,
d3ea8975 244 updatedAt: this.updatedAt,
4635f59d 245 totalReplies: this.get('totalReplies') || 0,
d3ea8975 246 account: {
4635f59d
C
247 name: this.Account.name,
248 host: this.Account.Actor.getHost()
d3ea8975 249 }
bf1f6508
C
250 } as VideoComment
251 }
ea44f375
C
252
253 toActivityPubObject (): VideoCommentObject {
254 let inReplyTo: string
255 // New thread, so in AS we reply to the video
256 if (this.inReplyToCommentId === null) {
257 inReplyTo = this.Video.url
258 } else {
259 inReplyTo = this.InReplyToVideoComment.url
260 }
261
262 return {
263 type: 'Note' as 'Note',
264 id: this.url,
265 content: this.text,
266 inReplyTo,
da854ddd 267 updated: this.updatedAt.toISOString(),
ea44f375 268 published: this.createdAt.toISOString(),
da854ddd
C
269 url: this.url,
270 attributedTo: this.Account.Actor.url
ea44f375
C
271 }
272 }
6d852470 273}