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