]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/models/account/user-notification.ts
Add newInstanceFollower in notification settings
[github/Chocobozzz/PeerTube.git] / server / models / account / user-notification.ts
CommitLineData
dc133480
C
1import {
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'
cef534ed
C
15import { UserNotification, UserNotificationType } from '../../../shared'
16import { getSort, throwIfNotValid } from '../utils'
17import { isBooleanValid } from '../../helpers/custom-validators/misc'
18import { isUserNotificationTypeValid } from '../../helpers/custom-validators/user-notifications'
19import { UserModel } from './user'
20import { VideoModel } from '../video/video'
21import { VideoCommentModel } from '../video/video-comment'
22import { Op } from 'sequelize'
23import { VideoChannelModel } from '../video/video-channel'
24import { AccountModel } from './account'
25import { VideoAbuseModel } from '../video/video-abuse'
26import { VideoBlacklistModel } from '../video/video-blacklist'
dc133480 27import { VideoImportModel } from '../video/video-import'
f7cc67b4
C
28import { ActorModel } from '../activitypub/actor'
29import { ActorFollowModel } from '../activitypub/actor-follow'
457bb213 30import { AvatarModel } from '../avatar/avatar'
38967f7b 31import { ServerModel } from '../server/server'
cef534ed
C
32
33enum ScopeNames {
34 WITH_ALL = 'WITH_ALL'
35}
36
457bb213
C
37function buildActorWithAvatarInclude () {
38 return {
39 attributes: [ 'preferredUsername' ],
40 model: () => ActorModel.unscoped(),
41 required: true,
42 include: [
43 {
44 attributes: [ 'filename' ],
45 model: () => AvatarModel.unscoped(),
46 required: false
38967f7b
C
47 },
48 {
49 attributes: [ 'host' ],
50 model: () => ServerModel.unscoped(),
51 required: false
457bb213
C
52 }
53 ]
54 }
55}
56
dc133480
C
57function buildVideoInclude (required: boolean) {
58 return {
59 attributes: [ 'id', 'uuid', 'name' ],
60 model: () => VideoModel.unscoped(),
61 required
62 }
63}
64
457bb213 65function buildChannelInclude (required: boolean, withActor = false) {
dc133480 66 return {
f7cc67b4 67 required,
dc133480 68 attributes: [ 'id', 'name' ],
457bb213
C
69 model: () => VideoChannelModel.unscoped(),
70 include: withActor === true ? [ buildActorWithAvatarInclude() ] : []
dc133480
C
71 }
72}
73
457bb213 74function buildAccountInclude (required: boolean, withActor = false) {
dc133480 75 return {
f7cc67b4 76 required,
dc133480 77 attributes: [ 'id', 'name' ],
457bb213
C
78 model: () => AccountModel.unscoped(),
79 include: withActor === true ? [ buildActorWithAvatarInclude() ] : []
dc133480
C
80 }
81}
82
cef534ed
C
83@Scopes({
84 [ScopeNames.WITH_ALL]: {
85 include: [
dc133480 86 Object.assign(buildVideoInclude(false), {
457bb213 87 include: [ buildChannelInclude(true, true) ]
dc133480 88 }),
457bb213 89
cef534ed 90 {
dc133480 91 attributes: [ 'id', 'originCommentId' ],
cef534ed
C
92 model: () => VideoCommentModel.unscoped(),
93 required: false,
94 include: [
457bb213 95 buildAccountInclude(true, true),
dc133480 96 buildVideoInclude(true)
cef534ed
C
97 ]
98 },
457bb213 99
cef534ed
C
100 {
101 attributes: [ 'id' ],
102 model: () => VideoAbuseModel.unscoped(),
103 required: false,
dc133480 104 include: [ buildVideoInclude(true) ]
cef534ed 105 },
457bb213 106
cef534ed
C
107 {
108 attributes: [ 'id' ],
109 model: () => VideoBlacklistModel.unscoped(),
110 required: false,
dc133480
C
111 include: [ buildVideoInclude(true) ]
112 },
457bb213 113
dc133480
C
114 {
115 attributes: [ 'id', 'magnetUri', 'targetUrl', 'torrentName' ],
116 model: () => VideoImportModel.unscoped(),
117 required: false,
118 include: [ buildVideoInclude(false) ]
f7cc67b4 119 },
457bb213 120
f7cc67b4
C
121 {
122 attributes: [ 'id' ],
123 model: () => ActorFollowModel.unscoped(),
124 required: false,
125 include: [
126 {
127 attributes: [ 'preferredUsername' ],
128 model: () => ActorModel.unscoped(),
129 required: true,
130 as: 'ActorFollower',
457bb213
C
131 include: [
132 {
133 attributes: [ 'id', 'name' ],
134 model: () => AccountModel.unscoped(),
135 required: true
136 },
137 {
138 attributes: [ 'filename' ],
139 model: () => AvatarModel.unscoped(),
140 required: false
38967f7b
C
141 },
142 {
143 attributes: [ 'host' ],
144 model: () => ServerModel.unscoped(),
145 required: false
457bb213
C
146 }
147 ]
f7cc67b4
C
148 },
149 {
150 attributes: [ 'preferredUsername' ],
151 model: () => ActorModel.unscoped(),
152 required: true,
153 as: 'ActorFollowing',
154 include: [
155 buildChannelInclude(false),
156 buildAccountInclude(false)
157 ]
158 }
159 ]
457bb213
C
160 },
161
162 buildAccountInclude(false, true)
cef534ed
C
163 ]
164 }
165})
166@Table({
167 tableName: 'userNotification',
168 indexes: [
169 {
457bb213 170 fields: [ 'userId' ]
cef534ed
C
171 },
172 {
457bb213
C
173 fields: [ 'videoId' ],
174 where: {
175 videoId: {
176 [Op.ne]: null
177 }
178 }
179 },
180 {
181 fields: [ 'commentId' ],
182 where: {
183 commentId: {
184 [Op.ne]: null
185 }
186 }
187 },
188 {
189 fields: [ 'videoAbuseId' ],
190 where: {
191 videoAbuseId: {
192 [Op.ne]: null
193 }
194 }
195 },
196 {
197 fields: [ 'videoBlacklistId' ],
198 where: {
199 videoBlacklistId: {
200 [Op.ne]: null
201 }
202 }
203 },
204 {
205 fields: [ 'videoImportId' ],
206 where: {
207 videoImportId: {
208 [Op.ne]: null
209 }
210 }
211 },
212 {
213 fields: [ 'accountId' ],
214 where: {
215 accountId: {
216 [Op.ne]: null
217 }
218 }
219 },
220 {
221 fields: [ 'actorFollowId' ],
222 where: {
223 actorFollowId: {
224 [Op.ne]: null
225 }
226 }
cef534ed
C
227 }
228 ]
229})
230export class UserNotificationModel extends Model<UserNotificationModel> {
231
232 @AllowNull(false)
233 @Default(null)
234 @Is('UserNotificationType', value => throwIfNotValid(value, isUserNotificationTypeValid, 'type'))
235 @Column
236 type: UserNotificationType
237
238 @AllowNull(false)
239 @Default(false)
240 @Is('UserNotificationRead', value => throwIfNotValid(value, isBooleanValid, 'read'))
241 @Column
242 read: boolean
243
244 @CreatedAt
245 createdAt: Date
246
247 @UpdatedAt
248 updatedAt: Date
249
250 @ForeignKey(() => UserModel)
251 @Column
252 userId: number
253
254 @BelongsTo(() => UserModel, {
255 foreignKey: {
256 allowNull: false
257 },
258 onDelete: 'cascade'
259 })
260 User: UserModel
261
262 @ForeignKey(() => VideoModel)
263 @Column
264 videoId: number
265
266 @BelongsTo(() => VideoModel, {
267 foreignKey: {
268 allowNull: true
269 },
270 onDelete: 'cascade'
271 })
272 Video: VideoModel
273
274 @ForeignKey(() => VideoCommentModel)
275 @Column
276 commentId: number
277
278 @BelongsTo(() => VideoCommentModel, {
279 foreignKey: {
280 allowNull: true
281 },
282 onDelete: 'cascade'
283 })
284 Comment: VideoCommentModel
285
286 @ForeignKey(() => VideoAbuseModel)
287 @Column
288 videoAbuseId: number
289
290 @BelongsTo(() => VideoAbuseModel, {
291 foreignKey: {
292 allowNull: true
293 },
294 onDelete: 'cascade'
295 })
296 VideoAbuse: VideoAbuseModel
297
298 @ForeignKey(() => VideoBlacklistModel)
299 @Column
300 videoBlacklistId: number
301
302 @BelongsTo(() => VideoBlacklistModel, {
303 foreignKey: {
304 allowNull: true
305 },
306 onDelete: 'cascade'
307 })
308 VideoBlacklist: VideoBlacklistModel
309
dc133480
C
310 @ForeignKey(() => VideoImportModel)
311 @Column
312 videoImportId: number
313
314 @BelongsTo(() => VideoImportModel, {
315 foreignKey: {
316 allowNull: true
317 },
318 onDelete: 'cascade'
319 })
320 VideoImport: VideoImportModel
321
f7cc67b4
C
322 @ForeignKey(() => AccountModel)
323 @Column
324 accountId: number
325
326 @BelongsTo(() => AccountModel, {
327 foreignKey: {
328 allowNull: true
329 },
330 onDelete: 'cascade'
331 })
332 Account: AccountModel
333
334 @ForeignKey(() => ActorFollowModel)
335 @Column
336 actorFollowId: number
337
338 @BelongsTo(() => ActorFollowModel, {
339 foreignKey: {
340 allowNull: true
341 },
342 onDelete: 'cascade'
343 })
344 ActorFollow: ActorFollowModel
345
dc133480
C
346 static listForApi (userId: number, start: number, count: number, sort: string, unread?: boolean) {
347 const query: IFindOptions<UserNotificationModel> = {
cef534ed
C
348 offset: start,
349 limit: count,
350 order: getSort(sort),
351 where: {
352 userId
353 }
354 }
355
dc133480
C
356 if (unread !== undefined) query.where['read'] = !unread
357
cef534ed
C
358 return UserNotificationModel.scope(ScopeNames.WITH_ALL)
359 .findAndCountAll(query)
360 .then(({ rows, count }) => {
361 return {
362 data: rows,
363 total: count
364 }
365 })
366 }
367
368 static markAsRead (userId: number, notificationIds: number[]) {
369 const query = {
370 where: {
371 userId,
372 id: {
373 [Op.any]: notificationIds
374 }
375 }
376 }
377
378 return UserNotificationModel.update({ read: true }, query)
379 }
380
2f1548fd
C
381 static markAllAsRead (userId: number) {
382 const query = { where: { userId } }
383
384 return UserNotificationModel.update({ read: true }, query)
385 }
386
cef534ed 387 toFormattedJSON (): UserNotification {
457bb213
C
388 const video = this.Video
389 ? Object.assign(this.formatVideo(this.Video),{ channel: this.formatActor(this.Video.VideoChannel) })
390 : undefined
dc133480
C
391
392 const videoImport = this.VideoImport ? {
393 id: this.VideoImport.id,
394 video: this.VideoImport.Video ? this.formatVideo(this.VideoImport.Video) : undefined,
395 torrentName: this.VideoImport.torrentName,
396 magnetUri: this.VideoImport.magnetUri,
397 targetUrl: this.VideoImport.targetUrl
cef534ed
C
398 } : undefined
399
400 const comment = this.Comment ? {
401 id: this.Comment.id,
dc133480 402 threadId: this.Comment.getThreadId(),
457bb213 403 account: this.formatActor(this.Comment.Account),
dc133480 404 video: this.formatVideo(this.Comment.Video)
cef534ed
C
405 } : undefined
406
407 const videoAbuse = this.VideoAbuse ? {
408 id: this.VideoAbuse.id,
dc133480 409 video: this.formatVideo(this.VideoAbuse.Video)
cef534ed
C
410 } : undefined
411
412 const videoBlacklist = this.VideoBlacklist ? {
413 id: this.VideoBlacklist.id,
dc133480 414 video: this.formatVideo(this.VideoBlacklist.Video)
cef534ed
C
415 } : undefined
416
457bb213 417 const account = this.Account ? this.formatActor(this.Account) : undefined
f7cc67b4
C
418
419 const actorFollow = this.ActorFollow ? {
420 id: this.ActorFollow.id,
883993c8 421 state: this.ActorFollow.state,
f7cc67b4 422 follower: {
457bb213 423 id: this.ActorFollow.ActorFollower.Account.id,
f7cc67b4 424 displayName: this.ActorFollow.ActorFollower.Account.getDisplayName(),
457bb213 425 name: this.ActorFollow.ActorFollower.preferredUsername,
38967f7b
C
426 avatar: this.ActorFollow.ActorFollower.Avatar ? { path: this.ActorFollow.ActorFollower.Avatar.getWebserverPath() } : undefined,
427 host: this.ActorFollow.ActorFollower.getHost()
f7cc67b4
C
428 },
429 following: {
430 type: this.ActorFollow.ActorFollowing.VideoChannel ? 'channel' as 'channel' : 'account' as 'account',
431 displayName: (this.ActorFollow.ActorFollowing.VideoChannel || this.ActorFollow.ActorFollowing.Account).getDisplayName(),
432 name: this.ActorFollow.ActorFollowing.preferredUsername
433 }
434 } : undefined
435
cef534ed
C
436 return {
437 id: this.id,
438 type: this.type,
439 read: this.read,
440 video,
dc133480 441 videoImport,
cef534ed
C
442 comment,
443 videoAbuse,
444 videoBlacklist,
f7cc67b4
C
445 account,
446 actorFollow,
cef534ed
C
447 createdAt: this.createdAt.toISOString(),
448 updatedAt: this.updatedAt.toISOString()
449 }
450 }
dc133480
C
451
452 private formatVideo (video: VideoModel) {
453 return {
454 id: video.id,
455 uuid: video.uuid,
456 name: video.name
457 }
458 }
457bb213
C
459
460 private formatActor (accountOrChannel: AccountModel | VideoChannelModel) {
461 const avatar = accountOrChannel.Actor.Avatar
462 ? { path: accountOrChannel.Actor.Avatar.getWebserverPath() }
463 : undefined
464
465 return {
466 id: accountOrChannel.id,
467 displayName: accountOrChannel.getDisplayName(),
468 name: accountOrChannel.Actor.preferredUsername,
38967f7b 469 host: accountOrChannel.Actor.getHost(),
457bb213
C
470 avatar
471 }
472 }
cef534ed 473}