]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/models/account/user-notification.ts
9e4f982a335aa4b735e36ac870212555519a8738
[github/Chocobozzz/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 import { ActorModel } from '../activitypub/actor'
29 import { ActorFollowModel } from '../activitypub/actor-follow'
30
31 enum ScopeNames {
32 WITH_ALL = 'WITH_ALL'
33 }
34
35 function buildVideoInclude (required: boolean) {
36 return {
37 attributes: [ 'id', 'uuid', 'name' ],
38 model: () => VideoModel.unscoped(),
39 required
40 }
41 }
42
43 function buildChannelInclude (required: boolean) {
44 return {
45 required,
46 attributes: [ 'id', 'name' ],
47 model: () => VideoChannelModel.unscoped()
48 }
49 }
50
51 function buildAccountInclude (required: boolean) {
52 return {
53 required,
54 attributes: [ 'id', 'name' ],
55 model: () => AccountModel.unscoped()
56 }
57 }
58
59 @Scopes({
60 [ScopeNames.WITH_ALL]: {
61 include: [
62 Object.assign(buildVideoInclude(false), {
63 include: [ buildChannelInclude(true) ]
64 }),
65 {
66 attributes: [ 'id', 'originCommentId' ],
67 model: () => VideoCommentModel.unscoped(),
68 required: false,
69 include: [
70 buildAccountInclude(true),
71 buildVideoInclude(true)
72 ]
73 },
74 {
75 attributes: [ 'id' ],
76 model: () => VideoAbuseModel.unscoped(),
77 required: false,
78 include: [ buildVideoInclude(true) ]
79 },
80 {
81 attributes: [ 'id' ],
82 model: () => VideoBlacklistModel.unscoped(),
83 required: false,
84 include: [ buildVideoInclude(true) ]
85 },
86 {
87 attributes: [ 'id', 'magnetUri', 'targetUrl', 'torrentName' ],
88 model: () => VideoImportModel.unscoped(),
89 required: false,
90 include: [ buildVideoInclude(false) ]
91 },
92 {
93 attributes: [ 'id', 'name' ],
94 model: () => AccountModel.unscoped(),
95 required: false,
96 include: [
97 {
98 attributes: [ 'id', 'preferredUsername' ],
99 model: () => ActorModel.unscoped(),
100 required: true
101 }
102 ]
103 },
104 {
105 attributes: [ 'id' ],
106 model: () => ActorFollowModel.unscoped(),
107 required: false,
108 include: [
109 {
110 attributes: [ 'preferredUsername' ],
111 model: () => ActorModel.unscoped(),
112 required: true,
113 as: 'ActorFollower',
114 include: [ buildAccountInclude(true) ]
115 },
116 {
117 attributes: [ 'preferredUsername' ],
118 model: () => ActorModel.unscoped(),
119 required: true,
120 as: 'ActorFollowing',
121 include: [
122 buildChannelInclude(false),
123 buildAccountInclude(false)
124 ]
125 }
126 ]
127 }
128 ]
129 }
130 })
131 @Table({
132 tableName: 'userNotification',
133 indexes: [
134 {
135 fields: [ 'videoId' ]
136 },
137 {
138 fields: [ 'commentId' ]
139 }
140 ]
141 })
142 export class UserNotificationModel extends Model<UserNotificationModel> {
143
144 @AllowNull(false)
145 @Default(null)
146 @Is('UserNotificationType', value => throwIfNotValid(value, isUserNotificationTypeValid, 'type'))
147 @Column
148 type: UserNotificationType
149
150 @AllowNull(false)
151 @Default(false)
152 @Is('UserNotificationRead', value => throwIfNotValid(value, isBooleanValid, 'read'))
153 @Column
154 read: boolean
155
156 @CreatedAt
157 createdAt: Date
158
159 @UpdatedAt
160 updatedAt: Date
161
162 @ForeignKey(() => UserModel)
163 @Column
164 userId: number
165
166 @BelongsTo(() => UserModel, {
167 foreignKey: {
168 allowNull: false
169 },
170 onDelete: 'cascade'
171 })
172 User: UserModel
173
174 @ForeignKey(() => VideoModel)
175 @Column
176 videoId: number
177
178 @BelongsTo(() => VideoModel, {
179 foreignKey: {
180 allowNull: true
181 },
182 onDelete: 'cascade'
183 })
184 Video: VideoModel
185
186 @ForeignKey(() => VideoCommentModel)
187 @Column
188 commentId: number
189
190 @BelongsTo(() => VideoCommentModel, {
191 foreignKey: {
192 allowNull: true
193 },
194 onDelete: 'cascade'
195 })
196 Comment: VideoCommentModel
197
198 @ForeignKey(() => VideoAbuseModel)
199 @Column
200 videoAbuseId: number
201
202 @BelongsTo(() => VideoAbuseModel, {
203 foreignKey: {
204 allowNull: true
205 },
206 onDelete: 'cascade'
207 })
208 VideoAbuse: VideoAbuseModel
209
210 @ForeignKey(() => VideoBlacklistModel)
211 @Column
212 videoBlacklistId: number
213
214 @BelongsTo(() => VideoBlacklistModel, {
215 foreignKey: {
216 allowNull: true
217 },
218 onDelete: 'cascade'
219 })
220 VideoBlacklist: VideoBlacklistModel
221
222 @ForeignKey(() => VideoImportModel)
223 @Column
224 videoImportId: number
225
226 @BelongsTo(() => VideoImportModel, {
227 foreignKey: {
228 allowNull: true
229 },
230 onDelete: 'cascade'
231 })
232 VideoImport: VideoImportModel
233
234 @ForeignKey(() => AccountModel)
235 @Column
236 accountId: number
237
238 @BelongsTo(() => AccountModel, {
239 foreignKey: {
240 allowNull: true
241 },
242 onDelete: 'cascade'
243 })
244 Account: AccountModel
245
246 @ForeignKey(() => ActorFollowModel)
247 @Column
248 actorFollowId: number
249
250 @BelongsTo(() => ActorFollowModel, {
251 foreignKey: {
252 allowNull: true
253 },
254 onDelete: 'cascade'
255 })
256 ActorFollow: ActorFollowModel
257
258 static listForApi (userId: number, start: number, count: number, sort: string, unread?: boolean) {
259 const query: IFindOptions<UserNotificationModel> = {
260 offset: start,
261 limit: count,
262 order: getSort(sort),
263 where: {
264 userId
265 }
266 }
267
268 if (unread !== undefined) query.where['read'] = !unread
269
270 return UserNotificationModel.scope(ScopeNames.WITH_ALL)
271 .findAndCountAll(query)
272 .then(({ rows, count }) => {
273 return {
274 data: rows,
275 total: count
276 }
277 })
278 }
279
280 static markAsRead (userId: number, notificationIds: number[]) {
281 const query = {
282 where: {
283 userId,
284 id: {
285 [Op.any]: notificationIds
286 }
287 }
288 }
289
290 return UserNotificationModel.update({ read: true }, query)
291 }
292
293 static markAllAsRead (userId: number) {
294 const query = { where: { userId } }
295
296 return UserNotificationModel.update({ read: true }, query)
297 }
298
299 toFormattedJSON (): UserNotification {
300 const video = this.Video ? Object.assign(this.formatVideo(this.Video), {
301 channel: {
302 id: this.Video.VideoChannel.id,
303 displayName: this.Video.VideoChannel.getDisplayName()
304 }
305 }) : undefined
306
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
313 } : undefined
314
315 const comment = this.Comment ? {
316 id: this.Comment.id,
317 threadId: this.Comment.getThreadId(),
318 account: {
319 id: this.Comment.Account.id,
320 displayName: this.Comment.Account.getDisplayName()
321 },
322 video: this.formatVideo(this.Comment.Video)
323 } : undefined
324
325 const videoAbuse = this.VideoAbuse ? {
326 id: this.VideoAbuse.id,
327 video: this.formatVideo(this.VideoAbuse.Video)
328 } : undefined
329
330 const videoBlacklist = this.VideoBlacklist ? {
331 id: this.VideoBlacklist.id,
332 video: this.formatVideo(this.VideoBlacklist.Video)
333 } : undefined
334
335 const account = this.Account ? {
336 id: this.Account.id,
337 displayName: this.Account.getDisplayName(),
338 name: this.Account.Actor.preferredUsername
339 } : undefined
340
341 const actorFollow = this.ActorFollow ? {
342 id: this.ActorFollow.id,
343 follower: {
344 displayName: this.ActorFollow.ActorFollower.Account.getDisplayName(),
345 name: this.ActorFollow.ActorFollower.preferredUsername
346 },
347 following: {
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
351 }
352 } : undefined
353
354 return {
355 id: this.id,
356 type: this.type,
357 read: this.read,
358 video,
359 videoImport,
360 comment,
361 videoAbuse,
362 videoBlacklist,
363 account,
364 actorFollow,
365 createdAt: this.createdAt.toISOString(),
366 updatedAt: this.updatedAt.toISOString()
367 }
368 }
369
370 private formatVideo (video: VideoModel) {
371 return {
372 id: video.id,
373 uuid: video.uuid,
374 name: video.name
375 }
376 }
377 }