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