14 } from 'sequelize-typescript'
15 import { UserNotification, UserNotificationType } from '../../../shared'
16 import { getSort, throwIfNotValid } from '../utils'
17 import { isBooleanValid } from '../../helpers/custom-validators/misc'
18 import { isUserNotificationTypeValid } from '../../helpers/custom-validators/user-notifications'
19 import { UserModel } from './user'
20 import { VideoModel } from '../video/video'
21 import { VideoCommentModel } from '../video/video-comment'
22 import { Op } from 'sequelize'
23 import { VideoChannelModel } from '../video/video-channel'
24 import { AccountModel } from './account'
25 import { VideoAbuseModel } from '../video/video-abuse'
26 import { VideoBlacklistModel } from '../video/video-blacklist'
27 import { VideoImportModel } from '../video/video-import'
28 import { ActorModel } from '../activitypub/actor'
29 import { ActorFollowModel } from '../activitypub/actor-follow'
30 import { AvatarModel } from '../avatar/avatar'
36 function buildActorWithAvatarInclude () {
38 attributes: [ 'preferredUsername' ],
39 model: () => ActorModel.unscoped(),
43 attributes: [ 'filename' ],
44 model: () => AvatarModel.unscoped(),
51 function buildVideoInclude (required: boolean) {
53 attributes: [ 'id', 'uuid', 'name' ],
54 model: () => VideoModel.unscoped(),
59 function buildChannelInclude (required: boolean, withActor = false) {
62 attributes: [ 'id', 'name' ],
63 model: () => VideoChannelModel.unscoped(),
64 include: withActor === true ? [ buildActorWithAvatarInclude() ] : []
68 function buildAccountInclude (required: boolean, withActor = false) {
71 attributes: [ 'id', 'name' ],
72 model: () => AccountModel.unscoped(),
73 include: withActor === true ? [ buildActorWithAvatarInclude() ] : []
78 [ScopeNames.WITH_ALL]: {
80 Object.assign(buildVideoInclude(false), {
81 include: [ buildChannelInclude(true, true) ]
85 attributes: [ 'id', 'originCommentId' ],
86 model: () => VideoCommentModel.unscoped(),
89 buildAccountInclude(true, true),
90 buildVideoInclude(true)
96 model: () => VideoAbuseModel.unscoped(),
98 include: [ buildVideoInclude(true) ]
102 attributes: [ 'id' ],
103 model: () => VideoBlacklistModel.unscoped(),
105 include: [ buildVideoInclude(true) ]
109 attributes: [ 'id', 'magnetUri', 'targetUrl', 'torrentName' ],
110 model: () => VideoImportModel.unscoped(),
112 include: [ buildVideoInclude(false) ]
116 attributes: [ 'id' ],
117 model: () => ActorFollowModel.unscoped(),
121 attributes: [ 'preferredUsername' ],
122 model: () => ActorModel.unscoped(),
127 attributes: [ 'id', 'name' ],
128 model: () => AccountModel.unscoped(),
132 attributes: [ 'filename' ],
133 model: () => AvatarModel.unscoped(),
139 attributes: [ 'preferredUsername' ],
140 model: () => ActorModel.unscoped(),
142 as: 'ActorFollowing',
144 buildChannelInclude(false),
145 buildAccountInclude(false)
151 buildAccountInclude(false, true)
156 tableName: 'userNotification',
162 fields: [ 'videoId' ],
170 fields: [ 'commentId' ],
178 fields: [ 'videoAbuseId' ],
186 fields: [ 'videoBlacklistId' ],
194 fields: [ 'videoImportId' ],
202 fields: [ 'accountId' ],
210 fields: [ 'actorFollowId' ],
219 export class UserNotificationModel extends Model<UserNotificationModel> {
223 @Is('UserNotificationType', value => throwIfNotValid(value, isUserNotificationTypeValid, 'type'))
225 type: UserNotificationType
229 @Is('UserNotificationRead', value => throwIfNotValid(value, isBooleanValid, 'read'))
239 @ForeignKey(() => UserModel)
243 @BelongsTo(() => UserModel, {
251 @ForeignKey(() => VideoModel)
255 @BelongsTo(() => VideoModel, {
263 @ForeignKey(() => VideoCommentModel)
267 @BelongsTo(() => VideoCommentModel, {
273 Comment: VideoCommentModel
275 @ForeignKey(() => VideoAbuseModel)
279 @BelongsTo(() => VideoAbuseModel, {
285 VideoAbuse: VideoAbuseModel
287 @ForeignKey(() => VideoBlacklistModel)
289 videoBlacklistId: number
291 @BelongsTo(() => VideoBlacklistModel, {
297 VideoBlacklist: VideoBlacklistModel
299 @ForeignKey(() => VideoImportModel)
301 videoImportId: number
303 @BelongsTo(() => VideoImportModel, {
309 VideoImport: VideoImportModel
311 @ForeignKey(() => AccountModel)
315 @BelongsTo(() => AccountModel, {
321 Account: AccountModel
323 @ForeignKey(() => ActorFollowModel)
325 actorFollowId: number
327 @BelongsTo(() => ActorFollowModel, {
333 ActorFollow: ActorFollowModel
335 static listForApi (userId: number, start: number, count: number, sort: string, unread?: boolean) {
336 const query: IFindOptions<UserNotificationModel> = {
339 order: getSort(sort),
345 if (unread !== undefined) query.where['read'] = !unread
347 return UserNotificationModel.scope(ScopeNames.WITH_ALL)
348 .findAndCountAll(query)
349 .then(({ rows, count }) => {
357 static markAsRead (userId: number, notificationIds: number[]) {
362 [Op.any]: notificationIds
367 return UserNotificationModel.update({ read: true }, query)
370 static markAllAsRead (userId: number) {
371 const query = { where: { userId } }
373 return UserNotificationModel.update({ read: true }, query)
376 toFormattedJSON (): UserNotification {
377 const video = this.Video
378 ? Object.assign(this.formatVideo(this.Video),{ channel: this.formatActor(this.Video.VideoChannel) })
381 const videoImport = this.VideoImport ? {
382 id: this.VideoImport.id,
383 video: this.VideoImport.Video ? this.formatVideo(this.VideoImport.Video) : undefined,
384 torrentName: this.VideoImport.torrentName,
385 magnetUri: this.VideoImport.magnetUri,
386 targetUrl: this.VideoImport.targetUrl
389 const comment = this.Comment ? {
391 threadId: this.Comment.getThreadId(),
392 account: this.formatActor(this.Comment.Account),
393 video: this.formatVideo(this.Comment.Video)
396 const videoAbuse = this.VideoAbuse ? {
397 id: this.VideoAbuse.id,
398 video: this.formatVideo(this.VideoAbuse.Video)
401 const videoBlacklist = this.VideoBlacklist ? {
402 id: this.VideoBlacklist.id,
403 video: this.formatVideo(this.VideoBlacklist.Video)
406 const account = this.Account ? this.formatActor(this.Account) : undefined
408 const actorFollow = this.ActorFollow ? {
409 id: this.ActorFollow.id,
411 id: this.ActorFollow.ActorFollower.Account.id,
412 displayName: this.ActorFollow.ActorFollower.Account.getDisplayName(),
413 name: this.ActorFollow.ActorFollower.preferredUsername,
414 avatar: this.ActorFollow.ActorFollower.Avatar ? { path: this.ActorFollow.ActorFollower.Avatar.getWebserverPath() } : undefined
417 type: this.ActorFollow.ActorFollowing.VideoChannel ? 'channel' as 'channel' : 'account' as 'account',
418 displayName: (this.ActorFollow.ActorFollowing.VideoChannel || this.ActorFollow.ActorFollowing.Account).getDisplayName(),
419 name: this.ActorFollow.ActorFollowing.preferredUsername
434 createdAt: this.createdAt.toISOString(),
435 updatedAt: this.updatedAt.toISOString()
439 private formatVideo (video: VideoModel) {
447 private formatActor (accountOrChannel: AccountModel | VideoChannelModel) {
448 const avatar = accountOrChannel.Actor.Avatar
449 ? { path: accountOrChannel.Actor.Avatar.getWebserverPath() }
453 id: accountOrChannel.id,
454 displayName: accountOrChannel.getDisplayName(),
455 name: accountOrChannel.Actor.preferredUsername,