Add import finished and video published notifs
[oweals/peertube.git] / server / models / account / user-notification.ts
1 import {
2   AllowNull,
3   BelongsTo,
4   Column,
5   CreatedAt,
6   Default,
7   ForeignKey,
8   IFindOptions,
9   Is,
10   Model,
11   Scopes,
12   Table,
13   UpdatedAt
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
29 enum ScopeNames {
30   WITH_ALL = 'WITH_ALL'
31 }
32
33 function buildVideoInclude (required: boolean) {
34   return {
35     attributes: [ 'id', 'uuid', 'name' ],
36     model: () => VideoModel.unscoped(),
37     required
38   }
39 }
40
41 function buildChannelInclude () {
42   return {
43     required: true,
44     attributes: [ 'id', 'name' ],
45     model: () => VideoChannelModel.unscoped()
46   }
47 }
48
49 function buildAccountInclude () {
50   return {
51     required: true,
52     attributes: [ 'id', 'name' ],
53     model: () => AccountModel.unscoped()
54   }
55 }
56
57 @Scopes({
58   [ScopeNames.WITH_ALL]: {
59     include: [
60       Object.assign(buildVideoInclude(false), {
61         include: [ buildChannelInclude() ]
62       }),
63       {
64         attributes: [ 'id', 'originCommentId' ],
65         model: () => VideoCommentModel.unscoped(),
66         required: false,
67         include: [
68           buildAccountInclude(),
69           buildVideoInclude(true)
70         ]
71       },
72       {
73         attributes: [ 'id' ],
74         model: () => VideoAbuseModel.unscoped(),
75         required: false,
76         include: [ buildVideoInclude(true) ]
77       },
78       {
79         attributes: [ 'id' ],
80         model: () => VideoBlacklistModel.unscoped(),
81         required: false,
82         include: [ buildVideoInclude(true) ]
83       },
84       {
85         attributes: [ 'id', 'magnetUri', 'targetUrl', 'torrentName' ],
86         model: () => VideoImportModel.unscoped(),
87         required: false,
88         include: [ buildVideoInclude(false) ]
89       }
90     ]
91   }
92 })
93 @Table({
94   tableName: 'userNotification',
95   indexes: [
96     {
97       fields: [ 'videoId' ]
98     },
99     {
100       fields: [ 'commentId' ]
101     }
102   ]
103 })
104 export class UserNotificationModel extends Model<UserNotificationModel> {
105
106   @AllowNull(false)
107   @Default(null)
108   @Is('UserNotificationType', value => throwIfNotValid(value, isUserNotificationTypeValid, 'type'))
109   @Column
110   type: UserNotificationType
111
112   @AllowNull(false)
113   @Default(false)
114   @Is('UserNotificationRead', value => throwIfNotValid(value, isBooleanValid, 'read'))
115   @Column
116   read: boolean
117
118   @CreatedAt
119   createdAt: Date
120
121   @UpdatedAt
122   updatedAt: Date
123
124   @ForeignKey(() => UserModel)
125   @Column
126   userId: number
127
128   @BelongsTo(() => UserModel, {
129     foreignKey: {
130       allowNull: false
131     },
132     onDelete: 'cascade'
133   })
134   User: UserModel
135
136   @ForeignKey(() => VideoModel)
137   @Column
138   videoId: number
139
140   @BelongsTo(() => VideoModel, {
141     foreignKey: {
142       allowNull: true
143     },
144     onDelete: 'cascade'
145   })
146   Video: VideoModel
147
148   @ForeignKey(() => VideoCommentModel)
149   @Column
150   commentId: number
151
152   @BelongsTo(() => VideoCommentModel, {
153     foreignKey: {
154       allowNull: true
155     },
156     onDelete: 'cascade'
157   })
158   Comment: VideoCommentModel
159
160   @ForeignKey(() => VideoAbuseModel)
161   @Column
162   videoAbuseId: number
163
164   @BelongsTo(() => VideoAbuseModel, {
165     foreignKey: {
166       allowNull: true
167     },
168     onDelete: 'cascade'
169   })
170   VideoAbuse: VideoAbuseModel
171
172   @ForeignKey(() => VideoBlacklistModel)
173   @Column
174   videoBlacklistId: number
175
176   @BelongsTo(() => VideoBlacklistModel, {
177     foreignKey: {
178       allowNull: true
179     },
180     onDelete: 'cascade'
181   })
182   VideoBlacklist: VideoBlacklistModel
183
184   @ForeignKey(() => VideoImportModel)
185   @Column
186   videoImportId: number
187
188   @BelongsTo(() => VideoImportModel, {
189     foreignKey: {
190       allowNull: true
191     },
192     onDelete: 'cascade'
193   })
194   VideoImport: VideoImportModel
195
196   static listForApi (userId: number, start: number, count: number, sort: string, unread?: boolean) {
197     const query: IFindOptions<UserNotificationModel> = {
198       offset: start,
199       limit: count,
200       order: getSort(sort),
201       where: {
202         userId
203       }
204     }
205
206     if (unread !== undefined) query.where['read'] = !unread
207
208     return UserNotificationModel.scope(ScopeNames.WITH_ALL)
209                                 .findAndCountAll(query)
210                                 .then(({ rows, count }) => {
211                                   return {
212                                     data: rows,
213                                     total: count
214                                   }
215                                 })
216   }
217
218   static markAsRead (userId: number, notificationIds: number[]) {
219     const query = {
220       where: {
221         userId,
222         id: {
223           [Op.any]: notificationIds
224         }
225       }
226     }
227
228     return UserNotificationModel.update({ read: true }, query)
229   }
230
231   toFormattedJSON (): UserNotification {
232     const video = this.Video ? Object.assign(this.formatVideo(this.Video), {
233       channel: {
234         id: this.Video.VideoChannel.id,
235         displayName: this.Video.VideoChannel.getDisplayName()
236       }
237     }) : undefined
238
239     const videoImport = this.VideoImport ? {
240       id: this.VideoImport.id,
241       video: this.VideoImport.Video ? this.formatVideo(this.VideoImport.Video) : undefined,
242       torrentName: this.VideoImport.torrentName,
243       magnetUri: this.VideoImport.magnetUri,
244       targetUrl: this.VideoImport.targetUrl
245     } : undefined
246
247     const comment = this.Comment ? {
248       id: this.Comment.id,
249       threadId: this.Comment.getThreadId(),
250       account: {
251         id: this.Comment.Account.id,
252         displayName: this.Comment.Account.getDisplayName()
253       },
254       video: this.formatVideo(this.Comment.Video)
255     } : undefined
256
257     const videoAbuse = this.VideoAbuse ? {
258       id: this.VideoAbuse.id,
259       video: this.formatVideo(this.VideoAbuse.Video)
260     } : undefined
261
262     const videoBlacklist = this.VideoBlacklist ? {
263       id: this.VideoBlacklist.id,
264       video: this.formatVideo(this.VideoBlacklist.Video)
265     } : undefined
266
267     return {
268       id: this.id,
269       type: this.type,
270       read: this.read,
271       video,
272       videoImport,
273       comment,
274       videoAbuse,
275       videoBlacklist,
276       createdAt: this.createdAt.toISOString(),
277       updatedAt: this.updatedAt.toISOString()
278     }
279   }
280
281   private formatVideo (video: VideoModel) {
282     return {
283       id: video.id,
284       uuid: video.uuid,
285       name: video.name
286     }
287   }
288 }