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'
35 function buildVideoInclude (required: boolean) {
37 attributes: [ 'id', 'uuid', 'name' ],
38 model: () => VideoModel.unscoped(),
43 function buildChannelInclude (required: boolean) {
46 attributes: [ 'id', 'name' ],
47 model: () => VideoChannelModel.unscoped()
51 function buildAccountInclude (required: boolean) {
54 attributes: [ 'id', 'name' ],
55 model: () => AccountModel.unscoped()
60 [ScopeNames.WITH_ALL]: {
62 Object.assign(buildVideoInclude(false), {
63 include: [ buildChannelInclude(true) ]
66 attributes: [ 'id', 'originCommentId' ],
67 model: () => VideoCommentModel.unscoped(),
70 buildAccountInclude(true),
71 buildVideoInclude(true)
76 model: () => VideoAbuseModel.unscoped(),
78 include: [ buildVideoInclude(true) ]
82 model: () => VideoBlacklistModel.unscoped(),
84 include: [ buildVideoInclude(true) ]
87 attributes: [ 'id', 'magnetUri', 'targetUrl', 'torrentName' ],
88 model: () => VideoImportModel.unscoped(),
90 include: [ buildVideoInclude(false) ]
93 attributes: [ 'id', 'name' ],
94 model: () => AccountModel.unscoped(),
98 attributes: [ 'id', 'preferredUsername' ],
99 model: () => ActorModel.unscoped(),
105 attributes: [ 'id' ],
106 model: () => ActorFollowModel.unscoped(),
110 attributes: [ 'preferredUsername' ],
111 model: () => ActorModel.unscoped(),
114 include: [ buildAccountInclude(true) ]
117 attributes: [ 'preferredUsername' ],
118 model: () => ActorModel.unscoped(),
120 as: 'ActorFollowing',
122 buildChannelInclude(false),
123 buildAccountInclude(false)
132 tableName: 'userNotification',
135 fields: [ 'videoId' ]
138 fields: [ 'commentId' ]
142 export class UserNotificationModel extends Model<UserNotificationModel> {
146 @Is('UserNotificationType', value => throwIfNotValid(value, isUserNotificationTypeValid, 'type'))
148 type: UserNotificationType
152 @Is('UserNotificationRead', value => throwIfNotValid(value, isBooleanValid, 'read'))
162 @ForeignKey(() => UserModel)
166 @BelongsTo(() => UserModel, {
174 @ForeignKey(() => VideoModel)
178 @BelongsTo(() => VideoModel, {
186 @ForeignKey(() => VideoCommentModel)
190 @BelongsTo(() => VideoCommentModel, {
196 Comment: VideoCommentModel
198 @ForeignKey(() => VideoAbuseModel)
202 @BelongsTo(() => VideoAbuseModel, {
208 VideoAbuse: VideoAbuseModel
210 @ForeignKey(() => VideoBlacklistModel)
212 videoBlacklistId: number
214 @BelongsTo(() => VideoBlacklistModel, {
220 VideoBlacklist: VideoBlacklistModel
222 @ForeignKey(() => VideoImportModel)
224 videoImportId: number
226 @BelongsTo(() => VideoImportModel, {
232 VideoImport: VideoImportModel
234 @ForeignKey(() => AccountModel)
238 @BelongsTo(() => AccountModel, {
244 Account: AccountModel
246 @ForeignKey(() => ActorFollowModel)
248 actorFollowId: number
250 @BelongsTo(() => ActorFollowModel, {
256 ActorFollow: ActorFollowModel
258 static listForApi (userId: number, start: number, count: number, sort: string, unread?: boolean) {
259 const query: IFindOptions<UserNotificationModel> = {
262 order: getSort(sort),
268 if (unread !== undefined) query.where['read'] = !unread
270 return UserNotificationModel.scope(ScopeNames.WITH_ALL)
271 .findAndCountAll(query)
272 .then(({ rows, count }) => {
280 static markAsRead (userId: number, notificationIds: number[]) {
285 [Op.any]: notificationIds
290 return UserNotificationModel.update({ read: true }, query)
293 static markAllAsRead (userId: number) {
294 const query = { where: { userId } }
296 return UserNotificationModel.update({ read: true }, query)
299 toFormattedJSON (): UserNotification {
300 const video = this.Video ? Object.assign(this.formatVideo(this.Video), {
302 id: this.Video.VideoChannel.id,
303 displayName: this.Video.VideoChannel.getDisplayName()
307 const videoImport = this.VideoImport ? {
308 id: this.VideoImport.id,
309 video: this.VideoImport.Video ? this.formatVideo(this.VideoImport.Video) : undefined,
310 torrentName: this.VideoImport.torrentName,
311 magnetUri: this.VideoImport.magnetUri,
312 targetUrl: this.VideoImport.targetUrl
315 const comment = this.Comment ? {
317 threadId: this.Comment.getThreadId(),
319 id: this.Comment.Account.id,
320 displayName: this.Comment.Account.getDisplayName()
322 video: this.formatVideo(this.Comment.Video)
325 const videoAbuse = this.VideoAbuse ? {
326 id: this.VideoAbuse.id,
327 video: this.formatVideo(this.VideoAbuse.Video)
330 const videoBlacklist = this.VideoBlacklist ? {
331 id: this.VideoBlacklist.id,
332 video: this.formatVideo(this.VideoBlacklist.Video)
335 const account = this.Account ? {
337 displayName: this.Account.getDisplayName(),
338 name: this.Account.Actor.preferredUsername
341 const actorFollow = this.ActorFollow ? {
342 id: this.ActorFollow.id,
344 displayName: this.ActorFollow.ActorFollower.Account.getDisplayName(),
345 name: this.ActorFollow.ActorFollower.preferredUsername
348 type: this.ActorFollow.ActorFollowing.VideoChannel ? 'channel' as 'channel' : 'account' as 'account',
349 displayName: (this.ActorFollow.ActorFollowing.VideoChannel || this.ActorFollow.ActorFollowing.Account).getDisplayName(),
350 name: this.ActorFollow.ActorFollowing.preferredUsername
365 createdAt: this.createdAt.toISOString(),
366 updatedAt: this.updatedAt.toISOString()
370 private formatVideo (video: VideoModel) {