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