]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/models/account/user-notification.ts
Cleanup model types
[github/Chocobozzz/PeerTube.git] / server / models / account / user-notification.ts
CommitLineData
1735c825 1import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
cef534ed
C
2import { UserNotification, UserNotificationType } from '../../../shared'
3import { getSort, throwIfNotValid } from '../utils'
4import { isBooleanValid } from '../../helpers/custom-validators/misc'
5import { isUserNotificationTypeValid } from '../../helpers/custom-validators/user-notifications'
6import { UserModel } from './user'
7import { VideoModel } from '../video/video'
8import { VideoCommentModel } from '../video/video-comment'
3acc5084 9import { FindOptions, ModelIndexesOptions, Op, WhereOptions } from 'sequelize'
cef534ed
C
10import { VideoChannelModel } from '../video/video-channel'
11import { AccountModel } from './account'
12import { VideoAbuseModel } from '../video/video-abuse'
13import { VideoBlacklistModel } from '../video/video-blacklist'
dc133480 14import { VideoImportModel } from '../video/video-import'
f7cc67b4
C
15import { ActorModel } from '../activitypub/actor'
16import { ActorFollowModel } from '../activitypub/actor-follow'
457bb213 17import { AvatarModel } from '../avatar/avatar'
38967f7b 18import { ServerModel } from '../server/server'
453e83ea 19import { UserNotificationIncludes, UserNotificationModelForApi } from '@server/typings/models/user'
cef534ed
C
20
21enum ScopeNames {
22 WITH_ALL = 'WITH_ALL'
23}
24
457bb213
C
25function buildActorWithAvatarInclude () {
26 return {
27 attributes: [ 'preferredUsername' ],
3acc5084 28 model: ActorModel.unscoped(),
457bb213
C
29 required: true,
30 include: [
31 {
32 attributes: [ 'filename' ],
3acc5084 33 model: AvatarModel.unscoped(),
457bb213 34 required: false
38967f7b
C
35 },
36 {
37 attributes: [ 'host' ],
3acc5084 38 model: ServerModel.unscoped(),
38967f7b 39 required: false
457bb213
C
40 }
41 ]
42 }
43}
44
dc133480
C
45function buildVideoInclude (required: boolean) {
46 return {
47 attributes: [ 'id', 'uuid', 'name' ],
3acc5084 48 model: VideoModel.unscoped(),
dc133480
C
49 required
50 }
51}
52
457bb213 53function buildChannelInclude (required: boolean, withActor = false) {
dc133480 54 return {
f7cc67b4 55 required,
dc133480 56 attributes: [ 'id', 'name' ],
3acc5084 57 model: VideoChannelModel.unscoped(),
457bb213 58 include: withActor === true ? [ buildActorWithAvatarInclude() ] : []
dc133480
C
59 }
60}
61
457bb213 62function buildAccountInclude (required: boolean, withActor = false) {
dc133480 63 return {
f7cc67b4 64 required,
dc133480 65 attributes: [ 'id', 'name' ],
3acc5084 66 model: AccountModel.unscoped(),
457bb213 67 include: withActor === true ? [ buildActorWithAvatarInclude() ] : []
dc133480
C
68 }
69}
70
3acc5084 71@Scopes(() => ({
cef534ed
C
72 [ScopeNames.WITH_ALL]: {
73 include: [
dc133480 74 Object.assign(buildVideoInclude(false), {
457bb213 75 include: [ buildChannelInclude(true, true) ]
dc133480 76 }),
457bb213 77
cef534ed 78 {
dc133480 79 attributes: [ 'id', 'originCommentId' ],
3acc5084 80 model: VideoCommentModel.unscoped(),
cef534ed
C
81 required: false,
82 include: [
457bb213 83 buildAccountInclude(true, true),
dc133480 84 buildVideoInclude(true)
cef534ed
C
85 ]
86 },
457bb213 87
cef534ed
C
88 {
89 attributes: [ 'id' ],
3acc5084 90 model: VideoAbuseModel.unscoped(),
cef534ed 91 required: false,
dc133480 92 include: [ buildVideoInclude(true) ]
cef534ed 93 },
457bb213 94
cef534ed
C
95 {
96 attributes: [ 'id' ],
3acc5084 97 model: VideoBlacklistModel.unscoped(),
cef534ed 98 required: false,
dc133480
C
99 include: [ buildVideoInclude(true) ]
100 },
457bb213 101
dc133480
C
102 {
103 attributes: [ 'id', 'magnetUri', 'targetUrl', 'torrentName' ],
3acc5084 104 model: VideoImportModel.unscoped(),
dc133480
C
105 required: false,
106 include: [ buildVideoInclude(false) ]
f7cc67b4 107 },
457bb213 108
f7cc67b4 109 {
8ce1ba6e 110 attributes: [ 'id', 'state' ],
3acc5084 111 model: ActorFollowModel.unscoped(),
f7cc67b4
C
112 required: false,
113 include: [
114 {
115 attributes: [ 'preferredUsername' ],
3acc5084 116 model: ActorModel.unscoped(),
f7cc67b4
C
117 required: true,
118 as: 'ActorFollower',
457bb213
C
119 include: [
120 {
121 attributes: [ 'id', 'name' ],
3acc5084 122 model: AccountModel.unscoped(),
457bb213
C
123 required: true
124 },
125 {
126 attributes: [ 'filename' ],
3acc5084 127 model: AvatarModel.unscoped(),
457bb213 128 required: false
38967f7b
C
129 },
130 {
131 attributes: [ 'host' ],
3acc5084 132 model: ServerModel.unscoped(),
38967f7b 133 required: false
457bb213
C
134 }
135 ]
f7cc67b4
C
136 },
137 {
138 attributes: [ 'preferredUsername' ],
3acc5084 139 model: ActorModel.unscoped(),
f7cc67b4
C
140 required: true,
141 as: 'ActorFollowing',
142 include: [
143 buildChannelInclude(false),
144 buildAccountInclude(false)
145 ]
146 }
147 ]
457bb213
C
148 },
149
150 buildAccountInclude(false, true)
3acc5084 151 ]
cef534ed 152 }
3acc5084 153}))
cef534ed
C
154@Table({
155 tableName: 'userNotification',
156 indexes: [
157 {
457bb213 158 fields: [ 'userId' ]
cef534ed
C
159 },
160 {
457bb213
C
161 fields: [ 'videoId' ],
162 where: {
163 videoId: {
164 [Op.ne]: null
165 }
166 }
167 },
168 {
169 fields: [ 'commentId' ],
170 where: {
171 commentId: {
172 [Op.ne]: null
173 }
174 }
175 },
176 {
177 fields: [ 'videoAbuseId' ],
178 where: {
179 videoAbuseId: {
180 [Op.ne]: null
181 }
182 }
183 },
184 {
185 fields: [ 'videoBlacklistId' ],
186 where: {
187 videoBlacklistId: {
188 [Op.ne]: null
189 }
190 }
191 },
192 {
193 fields: [ 'videoImportId' ],
194 where: {
195 videoImportId: {
196 [Op.ne]: null
197 }
198 }
199 },
200 {
201 fields: [ 'accountId' ],
202 where: {
203 accountId: {
204 [Op.ne]: null
205 }
206 }
207 },
208 {
209 fields: [ 'actorFollowId' ],
210 where: {
211 actorFollowId: {
212 [Op.ne]: null
213 }
214 }
cef534ed 215 }
3acc5084 216 ] as (ModelIndexesOptions & { where?: WhereOptions })[]
cef534ed
C
217})
218export class UserNotificationModel extends Model<UserNotificationModel> {
219
220 @AllowNull(false)
221 @Default(null)
222 @Is('UserNotificationType', value => throwIfNotValid(value, isUserNotificationTypeValid, 'type'))
223 @Column
224 type: UserNotificationType
225
226 @AllowNull(false)
227 @Default(false)
228 @Is('UserNotificationRead', value => throwIfNotValid(value, isBooleanValid, 'read'))
229 @Column
230 read: boolean
231
232 @CreatedAt
233 createdAt: Date
234
235 @UpdatedAt
236 updatedAt: Date
237
238 @ForeignKey(() => UserModel)
239 @Column
240 userId: number
241
242 @BelongsTo(() => UserModel, {
243 foreignKey: {
244 allowNull: false
245 },
246 onDelete: 'cascade'
247 })
248 User: UserModel
249
250 @ForeignKey(() => VideoModel)
251 @Column
252 videoId: number
253
254 @BelongsTo(() => VideoModel, {
255 foreignKey: {
256 allowNull: true
257 },
258 onDelete: 'cascade'
259 })
260 Video: VideoModel
261
262 @ForeignKey(() => VideoCommentModel)
263 @Column
264 commentId: number
265
266 @BelongsTo(() => VideoCommentModel, {
267 foreignKey: {
268 allowNull: true
269 },
270 onDelete: 'cascade'
271 })
272 Comment: VideoCommentModel
273
274 @ForeignKey(() => VideoAbuseModel)
275 @Column
276 videoAbuseId: number
277
278 @BelongsTo(() => VideoAbuseModel, {
279 foreignKey: {
280 allowNull: true
281 },
282 onDelete: 'cascade'
283 })
284 VideoAbuse: VideoAbuseModel
285
286 @ForeignKey(() => VideoBlacklistModel)
287 @Column
288 videoBlacklistId: number
289
290 @BelongsTo(() => VideoBlacklistModel, {
291 foreignKey: {
292 allowNull: true
293 },
294 onDelete: 'cascade'
295 })
296 VideoBlacklist: VideoBlacklistModel
297
dc133480
C
298 @ForeignKey(() => VideoImportModel)
299 @Column
300 videoImportId: number
301
302 @BelongsTo(() => VideoImportModel, {
303 foreignKey: {
304 allowNull: true
305 },
306 onDelete: 'cascade'
307 })
308 VideoImport: VideoImportModel
309
f7cc67b4
C
310 @ForeignKey(() => AccountModel)
311 @Column
312 accountId: number
313
314 @BelongsTo(() => AccountModel, {
315 foreignKey: {
316 allowNull: true
317 },
318 onDelete: 'cascade'
319 })
320 Account: AccountModel
321
322 @ForeignKey(() => ActorFollowModel)
323 @Column
324 actorFollowId: number
325
326 @BelongsTo(() => ActorFollowModel, {
327 foreignKey: {
328 allowNull: true
329 },
330 onDelete: 'cascade'
331 })
332 ActorFollow: ActorFollowModel
333
dc133480 334 static listForApi (userId: number, start: number, count: number, sort: string, unread?: boolean) {
1735c825 335 const query: FindOptions = {
cef534ed
C
336 offset: start,
337 limit: count,
338 order: getSort(sort),
339 where: {
340 userId
341 }
342 }
343
dc133480
C
344 if (unread !== undefined) query.where['read'] = !unread
345
cef534ed
C
346 return UserNotificationModel.scope(ScopeNames.WITH_ALL)
347 .findAndCountAll(query)
348 .then(({ rows, count }) => {
349 return {
350 data: rows,
351 total: count
352 }
353 })
354 }
355
356 static markAsRead (userId: number, notificationIds: number[]) {
357 const query = {
358 where: {
359 userId,
360 id: {
3acc5084 361 [Op.in]: notificationIds // FIXME: sequelize ANY seems broken
cef534ed
C
362 }
363 }
364 }
365
366 return UserNotificationModel.update({ read: true }, query)
367 }
368
2f1548fd
C
369 static markAllAsRead (userId: number) {
370 const query = { where: { userId } }
371
372 return UserNotificationModel.update({ read: true }, query)
373 }
374
453e83ea 375 toFormattedJSON (this: UserNotificationModelForApi): UserNotification {
457bb213
C
376 const video = this.Video
377 ? Object.assign(this.formatVideo(this.Video),{ channel: this.formatActor(this.Video.VideoChannel) })
378 : undefined
dc133480
C
379
380 const videoImport = this.VideoImport ? {
381 id: this.VideoImport.id,
382 video: this.VideoImport.Video ? this.formatVideo(this.VideoImport.Video) : undefined,
383 torrentName: this.VideoImport.torrentName,
384 magnetUri: this.VideoImport.magnetUri,
385 targetUrl: this.VideoImport.targetUrl
cef534ed
C
386 } : undefined
387
388 const comment = this.Comment ? {
389 id: this.Comment.id,
dc133480 390 threadId: this.Comment.getThreadId(),
457bb213 391 account: this.formatActor(this.Comment.Account),
dc133480 392 video: this.formatVideo(this.Comment.Video)
cef534ed
C
393 } : undefined
394
395 const videoAbuse = this.VideoAbuse ? {
396 id: this.VideoAbuse.id,
dc133480 397 video: this.formatVideo(this.VideoAbuse.Video)
cef534ed
C
398 } : undefined
399
400 const videoBlacklist = this.VideoBlacklist ? {
401 id: this.VideoBlacklist.id,
dc133480 402 video: this.formatVideo(this.VideoBlacklist.Video)
cef534ed
C
403 } : undefined
404
457bb213 405 const account = this.Account ? this.formatActor(this.Account) : undefined
f7cc67b4
C
406
407 const actorFollow = this.ActorFollow ? {
408 id: this.ActorFollow.id,
883993c8 409 state: this.ActorFollow.state,
f7cc67b4 410 follower: {
457bb213 411 id: this.ActorFollow.ActorFollower.Account.id,
f7cc67b4 412 displayName: this.ActorFollow.ActorFollower.Account.getDisplayName(),
457bb213 413 name: this.ActorFollow.ActorFollower.preferredUsername,
557b13ae 414 avatar: this.ActorFollow.ActorFollower.Avatar ? { path: this.ActorFollow.ActorFollower.Avatar.getStaticPath() } : undefined,
38967f7b 415 host: this.ActorFollow.ActorFollower.getHost()
f7cc67b4
C
416 },
417 following: {
418 type: this.ActorFollow.ActorFollowing.VideoChannel ? 'channel' as 'channel' : 'account' as 'account',
419 displayName: (this.ActorFollow.ActorFollowing.VideoChannel || this.ActorFollow.ActorFollowing.Account).getDisplayName(),
420 name: this.ActorFollow.ActorFollowing.preferredUsername
421 }
422 } : undefined
423
cef534ed
C
424 return {
425 id: this.id,
426 type: this.type,
427 read: this.read,
428 video,
dc133480 429 videoImport,
cef534ed
C
430 comment,
431 videoAbuse,
432 videoBlacklist,
f7cc67b4
C
433 account,
434 actorFollow,
cef534ed
C
435 createdAt: this.createdAt.toISOString(),
436 updatedAt: this.updatedAt.toISOString()
437 }
438 }
dc133480 439
453e83ea 440 formatVideo (this: UserNotificationModelForApi, video: UserNotificationIncludes.VideoInclude) {
dc133480
C
441 return {
442 id: video.id,
443 uuid: video.uuid,
444 name: video.name
445 }
446 }
457bb213 447
453e83ea
C
448 formatActor (
449 this: UserNotificationModelForApi,
450 accountOrChannel: UserNotificationIncludes.AccountIncludeActor | UserNotificationIncludes.VideoChannelIncludeActor
451 ) {
457bb213 452 const avatar = accountOrChannel.Actor.Avatar
557b13ae 453 ? { path: accountOrChannel.Actor.Avatar.getStaticPath() }
457bb213
C
454 : undefined
455
456 return {
457 id: accountOrChannel.id,
458 displayName: accountOrChannel.getDisplayName(),
459 name: accountOrChannel.Actor.preferredUsername,
38967f7b 460 host: accountOrChannel.Actor.getHost(),
457bb213
C
461 avatar
462 }
463 }
cef534ed 464}