aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/models/user/user-notification.ts
diff options
context:
space:
mode:
Diffstat (limited to 'server/models/user/user-notification.ts')
-rw-r--r--server/models/user/user-notification.ts272
1 files changed, 55 insertions, 217 deletions
diff --git a/server/models/user/user-notification.ts b/server/models/user/user-notification.ts
index edad10a55..6209cb4bf 100644
--- a/server/models/user/user-notification.ts
+++ b/server/models/user/user-notification.ts
@@ -1,5 +1,6 @@
1import { FindOptions, ModelIndexesOptions, Op, WhereOptions } from 'sequelize' 1import { ModelIndexesOptions, Op, WhereOptions } from 'sequelize'
2import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' 2import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
3import { getBiggestActorImage } from '@server/lib/actor-image'
3import { UserNotificationIncludes, UserNotificationModelForApi } from '@server/types/models/user' 4import { UserNotificationIncludes, UserNotificationModelForApi } from '@server/types/models/user'
4import { uuidToShort } from '@shared/extra-utils' 5import { uuidToShort } from '@shared/extra-utils'
5import { UserNotification, UserNotificationType } from '@shared/models' 6import { UserNotification, UserNotificationType } from '@shared/models'
@@ -7,207 +8,18 @@ import { AttributesOnly } from '@shared/typescript-utils'
7import { isBooleanValid } from '../../helpers/custom-validators/misc' 8import { isBooleanValid } from '../../helpers/custom-validators/misc'
8import { isUserNotificationTypeValid } from '../../helpers/custom-validators/user-notifications' 9import { isUserNotificationTypeValid } from '../../helpers/custom-validators/user-notifications'
9import { AbuseModel } from '../abuse/abuse' 10import { AbuseModel } from '../abuse/abuse'
10import { VideoAbuseModel } from '../abuse/video-abuse'
11import { VideoCommentAbuseModel } from '../abuse/video-comment-abuse'
12import { AccountModel } from '../account/account' 11import { AccountModel } from '../account/account'
13import { ActorModel } from '../actor/actor'
14import { ActorFollowModel } from '../actor/actor-follow' 12import { ActorFollowModel } from '../actor/actor-follow'
15import { ActorImageModel } from '../actor/actor-image'
16import { ApplicationModel } from '../application/application' 13import { ApplicationModel } from '../application/application'
17import { PluginModel } from '../server/plugin' 14import { PluginModel } from '../server/plugin'
18import { ServerModel } from '../server/server' 15import { throwIfNotValid } from '../utils'
19import { getSort, throwIfNotValid } from '../utils'
20import { VideoModel } from '../video/video' 16import { VideoModel } from '../video/video'
21import { VideoBlacklistModel } from '../video/video-blacklist' 17import { VideoBlacklistModel } from '../video/video-blacklist'
22import { VideoChannelModel } from '../video/video-channel'
23import { VideoCommentModel } from '../video/video-comment' 18import { VideoCommentModel } from '../video/video-comment'
24import { VideoImportModel } from '../video/video-import' 19import { VideoImportModel } from '../video/video-import'
20import { UserNotificationListQueryBuilder } from './sql/user-notitication-list-query-builder'
25import { UserModel } from './user' 21import { UserModel } from './user'
26 22
27enum ScopeNames {
28 WITH_ALL = 'WITH_ALL'
29}
30
31function buildActorWithAvatarInclude () {
32 return {
33 attributes: [ 'preferredUsername' ],
34 model: ActorModel.unscoped(),
35 required: true,
36 include: [
37 {
38 attributes: [ 'filename' ],
39 as: 'Avatar',
40 model: ActorImageModel.unscoped(),
41 required: false
42 },
43 {
44 attributes: [ 'host' ],
45 model: ServerModel.unscoped(),
46 required: false
47 }
48 ]
49 }
50}
51
52function buildVideoInclude (required: boolean) {
53 return {
54 attributes: [ 'id', 'uuid', 'name' ],
55 model: VideoModel.unscoped(),
56 required
57 }
58}
59
60function buildChannelInclude (required: boolean, withActor = false) {
61 return {
62 required,
63 attributes: [ 'id', 'name' ],
64 model: VideoChannelModel.unscoped(),
65 include: withActor === true ? [ buildActorWithAvatarInclude() ] : []
66 }
67}
68
69function buildAccountInclude (required: boolean, withActor = false) {
70 return {
71 required,
72 attributes: [ 'id', 'name' ],
73 model: AccountModel.unscoped(),
74 include: withActor === true ? [ buildActorWithAvatarInclude() ] : []
75 }
76}
77
78@Scopes(() => ({
79 [ScopeNames.WITH_ALL]: {
80 include: [
81 Object.assign(buildVideoInclude(false), {
82 include: [ buildChannelInclude(true, true) ]
83 }),
84
85 {
86 attributes: [ 'id', 'originCommentId' ],
87 model: VideoCommentModel.unscoped(),
88 required: false,
89 include: [
90 buildAccountInclude(true, true),
91 buildVideoInclude(true)
92 ]
93 },
94
95 {
96 attributes: [ 'id', 'state' ],
97 model: AbuseModel.unscoped(),
98 required: false,
99 include: [
100 {
101 attributes: [ 'id' ],
102 model: VideoAbuseModel.unscoped(),
103 required: false,
104 include: [ buildVideoInclude(false) ]
105 },
106 {
107 attributes: [ 'id' ],
108 model: VideoCommentAbuseModel.unscoped(),
109 required: false,
110 include: [
111 {
112 attributes: [ 'id', 'originCommentId' ],
113 model: VideoCommentModel.unscoped(),
114 required: false,
115 include: [
116 {
117 attributes: [ 'id', 'name', 'uuid' ],
118 model: VideoModel.unscoped(),
119 required: false
120 }
121 ]
122 }
123 ]
124 },
125 {
126 model: AccountModel,
127 as: 'FlaggedAccount',
128 required: false,
129 include: [ buildActorWithAvatarInclude() ]
130 }
131 ]
132 },
133
134 {
135 attributes: [ 'id' ],
136 model: VideoBlacklistModel.unscoped(),
137 required: false,
138 include: [ buildVideoInclude(true) ]
139 },
140
141 {
142 attributes: [ 'id', 'magnetUri', 'targetUrl', 'torrentName' ],
143 model: VideoImportModel.unscoped(),
144 required: false,
145 include: [ buildVideoInclude(false) ]
146 },
147
148 {
149 attributes: [ 'id', 'name', 'type', 'latestVersion' ],
150 model: PluginModel.unscoped(),
151 required: false
152 },
153
154 {
155 attributes: [ 'id', 'latestPeerTubeVersion' ],
156 model: ApplicationModel.unscoped(),
157 required: false
158 },
159
160 {
161 attributes: [ 'id', 'state' ],
162 model: ActorFollowModel.unscoped(),
163 required: false,
164 include: [
165 {
166 attributes: [ 'preferredUsername' ],
167 model: ActorModel.unscoped(),
168 required: true,
169 as: 'ActorFollower',
170 include: [
171 {
172 attributes: [ 'id', 'name' ],
173 model: AccountModel.unscoped(),
174 required: true
175 },
176 {
177 attributes: [ 'filename' ],
178 as: 'Avatar',
179 model: ActorImageModel.unscoped(),
180 required: false
181 },
182 {
183 attributes: [ 'host' ],
184 model: ServerModel.unscoped(),
185 required: false
186 }
187 ]
188 },
189 {
190 attributes: [ 'preferredUsername', 'type' ],
191 model: ActorModel.unscoped(),
192 required: true,
193 as: 'ActorFollowing',
194 include: [
195 buildChannelInclude(false),
196 buildAccountInclude(false),
197 {
198 attributes: [ 'host' ],
199 model: ServerModel.unscoped(),
200 required: false
201 }
202 ]
203 }
204 ]
205 },
206
207 buildAccountInclude(false, true)
208 ]
209 }
210}))
211@Table({ 23@Table({
212 tableName: 'userNotification', 24 tableName: 'userNotification',
213 indexes: [ 25 indexes: [
@@ -342,7 +154,7 @@ export class UserNotificationModel extends Model<Partial<AttributesOnly<UserNoti
342 }, 154 },
343 onDelete: 'cascade' 155 onDelete: 'cascade'
344 }) 156 })
345 Comment: VideoCommentModel 157 VideoComment: VideoCommentModel
346 158
347 @ForeignKey(() => AbuseModel) 159 @ForeignKey(() => AbuseModel)
348 @Column 160 @Column
@@ -431,10 +243,12 @@ export class UserNotificationModel extends Model<Partial<AttributesOnly<UserNoti
431 static listForApi (userId: number, start: number, count: number, sort: string, unread?: boolean) { 243 static listForApi (userId: number, start: number, count: number, sort: string, unread?: boolean) {
432 const where = { userId } 244 const where = { userId }
433 245
434 const query: FindOptions = { 246 const query = {
247 userId,
248 unread,
435 offset: start, 249 offset: start,
436 limit: count, 250 limit: count,
437 order: getSort(sort), 251 sort,
438 where 252 where
439 } 253 }
440 254
@@ -445,8 +259,8 @@ export class UserNotificationModel extends Model<Partial<AttributesOnly<UserNoti
445 .then(count => count || 0), 259 .then(count => count || 0),
446 260
447 count === 0 261 count === 0
448 ? [] 262 ? [] as UserNotificationModelForApi[]
449 : UserNotificationModel.scope(ScopeNames.WITH_ALL).findAll(query) 263 : new UserNotificationListQueryBuilder(this.sequelize, query).listNotifications()
450 ]).then(([ total, data ]) => ({ total, data })) 264 ]).then(([ total, data ]) => ({ total, data }))
451 } 265 }
452 266
@@ -524,25 +338,31 @@ export class UserNotificationModel extends Model<Partial<AttributesOnly<UserNoti
524 338
525 toFormattedJSON (this: UserNotificationModelForApi): UserNotification { 339 toFormattedJSON (this: UserNotificationModelForApi): UserNotification {
526 const video = this.Video 340 const video = this.Video
527 ? Object.assign(this.formatVideo(this.Video), { channel: this.formatActor(this.Video.VideoChannel) }) 341 ? {
342 ...this.formatVideo(this.Video),
343
344 channel: this.formatActor(this.Video.VideoChannel)
345 }
528 : undefined 346 : undefined
529 347
530 const videoImport = this.VideoImport 348 const videoImport = this.VideoImport
531 ? { 349 ? {
532 id: this.VideoImport.id, 350 id: this.VideoImport.id,
533 video: this.VideoImport.Video ? this.formatVideo(this.VideoImport.Video) : undefined, 351 video: this.VideoImport.Video
352 ? this.formatVideo(this.VideoImport.Video)
353 : undefined,
534 torrentName: this.VideoImport.torrentName, 354 torrentName: this.VideoImport.torrentName,
535 magnetUri: this.VideoImport.magnetUri, 355 magnetUri: this.VideoImport.magnetUri,
536 targetUrl: this.VideoImport.targetUrl 356 targetUrl: this.VideoImport.targetUrl
537 } 357 }
538 : undefined 358 : undefined
539 359
540 const comment = this.Comment 360 const comment = this.VideoComment
541 ? { 361 ? {
542 id: this.Comment.id, 362 id: this.VideoComment.id,
543 threadId: this.Comment.getThreadId(), 363 threadId: this.VideoComment.getThreadId(),
544 account: this.formatActor(this.Comment.Account), 364 account: this.formatActor(this.VideoComment.Account),
545 video: this.formatVideo(this.Comment.Video) 365 video: this.formatVideo(this.VideoComment.Video)
546 } 366 }
547 : undefined 367 : undefined
548 368
@@ -570,8 +390,9 @@ export class UserNotificationModel extends Model<Partial<AttributesOnly<UserNoti
570 id: this.ActorFollow.ActorFollower.Account.id, 390 id: this.ActorFollow.ActorFollower.Account.id,
571 displayName: this.ActorFollow.ActorFollower.Account.getDisplayName(), 391 displayName: this.ActorFollow.ActorFollower.Account.getDisplayName(),
572 name: this.ActorFollow.ActorFollower.preferredUsername, 392 name: this.ActorFollow.ActorFollower.preferredUsername,
573 avatar: this.ActorFollow.ActorFollower.Avatar ? { path: this.ActorFollow.ActorFollower.Avatar.getStaticPath() } : undefined, 393 host: this.ActorFollow.ActorFollower.getHost(),
574 host: this.ActorFollow.ActorFollower.getHost() 394
395 ...this.formatAvatars(this.ActorFollow.ActorFollower.Avatars)
575 }, 396 },
576 following: { 397 following: {
577 type: actorFollowingType[this.ActorFollow.ActorFollowing.type], 398 type: actorFollowingType[this.ActorFollow.ActorFollowing.type],
@@ -612,7 +433,7 @@ export class UserNotificationModel extends Model<Partial<AttributesOnly<UserNoti
612 } 433 }
613 } 434 }
614 435
615 formatVideo (this: UserNotificationModelForApi, video: UserNotificationIncludes.VideoInclude) { 436 formatVideo (video: UserNotificationIncludes.VideoInclude) {
616 return { 437 return {
617 id: video.id, 438 id: video.id,
618 uuid: video.uuid, 439 uuid: video.uuid,
@@ -621,7 +442,7 @@ export class UserNotificationModel extends Model<Partial<AttributesOnly<UserNoti
621 } 442 }
622 } 443 }
623 444
624 formatAbuse (this: UserNotificationModelForApi, abuse: UserNotificationIncludes.AbuseInclude) { 445 formatAbuse (abuse: UserNotificationIncludes.AbuseInclude) {
625 const commentAbuse = abuse.VideoCommentAbuse?.VideoComment 446 const commentAbuse = abuse.VideoCommentAbuse?.VideoComment
626 ? { 447 ? {
627 threadId: abuse.VideoCommentAbuse.VideoComment.getThreadId(), 448 threadId: abuse.VideoCommentAbuse.VideoComment.getThreadId(),
@@ -637,9 +458,13 @@ export class UserNotificationModel extends Model<Partial<AttributesOnly<UserNoti
637 } 458 }
638 : undefined 459 : undefined
639 460
640 const videoAbuse = abuse.VideoAbuse?.Video ? this.formatVideo(abuse.VideoAbuse.Video) : undefined 461 const videoAbuse = abuse.VideoAbuse?.Video
462 ? this.formatVideo(abuse.VideoAbuse.Video)
463 : undefined
641 464
642 const accountAbuse = (!commentAbuse && !videoAbuse && abuse.FlaggedAccount) ? this.formatActor(abuse.FlaggedAccount) : undefined 465 const accountAbuse = (!commentAbuse && !videoAbuse && abuse.FlaggedAccount)
466 ? this.formatActor(abuse.FlaggedAccount)
467 : undefined
643 468
644 return { 469 return {
645 id: abuse.id, 470 id: abuse.id,
@@ -651,19 +476,32 @@ export class UserNotificationModel extends Model<Partial<AttributesOnly<UserNoti
651 } 476 }
652 477
653 formatActor ( 478 formatActor (
654 this: UserNotificationModelForApi,
655 accountOrChannel: UserNotificationIncludes.AccountIncludeActor | UserNotificationIncludes.VideoChannelIncludeActor 479 accountOrChannel: UserNotificationIncludes.AccountIncludeActor | UserNotificationIncludes.VideoChannelIncludeActor
656 ) { 480 ) {
657 const avatar = accountOrChannel.Actor.Avatar
658 ? { path: accountOrChannel.Actor.Avatar.getStaticPath() }
659 : undefined
660
661 return { 481 return {
662 id: accountOrChannel.id, 482 id: accountOrChannel.id,
663 displayName: accountOrChannel.getDisplayName(), 483 displayName: accountOrChannel.getDisplayName(),
664 name: accountOrChannel.Actor.preferredUsername, 484 name: accountOrChannel.Actor.preferredUsername,
665 host: accountOrChannel.Actor.getHost(), 485 host: accountOrChannel.Actor.getHost(),
666 avatar 486
487 ...this.formatAvatars(accountOrChannel.Actor.Avatars)
488 }
489 }
490
491 formatAvatars (avatars: UserNotificationIncludes.ActorImageInclude[]) {
492 if (!avatars || avatars.length === 0) return { avatar: undefined, avatars: [] }
493
494 return {
495 avatar: this.formatAvatar(getBiggestActorImage(avatars)),
496
497 avatars: avatars.map(a => this.formatAvatar(a))
498 }
499 }
500
501 formatAvatar (a: UserNotificationIncludes.ActorImageInclude) {
502 return {
503 path: a.getStaticPath(),
504 width: a.width
667 } 505 }
668 } 506 }
669} 507}