diff options
Diffstat (limited to 'server/models/user/user-notification.ts')
-rw-r--r-- | server/models/user/user-notification.ts | 534 |
1 files changed, 0 insertions, 534 deletions
diff --git a/server/models/user/user-notification.ts b/server/models/user/user-notification.ts deleted file mode 100644 index 667ee7f5f..000000000 --- a/server/models/user/user-notification.ts +++ /dev/null | |||
@@ -1,534 +0,0 @@ | |||
1 | import { ModelIndexesOptions, Op, WhereOptions } from 'sequelize' | ||
2 | import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' | ||
3 | import { getBiggestActorImage } from '@server/lib/actor-image' | ||
4 | import { UserNotificationIncludes, UserNotificationModelForApi } from '@server/types/models/user' | ||
5 | import { forceNumber } from '@shared/core-utils' | ||
6 | import { uuidToShort } from '@shared/extra-utils' | ||
7 | import { UserNotification, UserNotificationType } from '@shared/models' | ||
8 | import { AttributesOnly } from '@shared/typescript-utils' | ||
9 | import { isBooleanValid } from '../../helpers/custom-validators/misc' | ||
10 | import { isUserNotificationTypeValid } from '../../helpers/custom-validators/user-notifications' | ||
11 | import { AbuseModel } from '../abuse/abuse' | ||
12 | import { AccountModel } from '../account/account' | ||
13 | import { ActorFollowModel } from '../actor/actor-follow' | ||
14 | import { ApplicationModel } from '../application/application' | ||
15 | import { PluginModel } from '../server/plugin' | ||
16 | import { throwIfNotValid } from '../shared' | ||
17 | import { VideoModel } from '../video/video' | ||
18 | import { VideoBlacklistModel } from '../video/video-blacklist' | ||
19 | import { VideoCommentModel } from '../video/video-comment' | ||
20 | import { VideoImportModel } from '../video/video-import' | ||
21 | import { UserNotificationListQueryBuilder } from './sql/user-notitication-list-query-builder' | ||
22 | import { UserModel } from './user' | ||
23 | import { UserRegistrationModel } from './user-registration' | ||
24 | |||
25 | @Table({ | ||
26 | tableName: 'userNotification', | ||
27 | indexes: [ | ||
28 | { | ||
29 | fields: [ 'userId' ] | ||
30 | }, | ||
31 | { | ||
32 | fields: [ 'videoId' ], | ||
33 | where: { | ||
34 | videoId: { | ||
35 | [Op.ne]: null | ||
36 | } | ||
37 | } | ||
38 | }, | ||
39 | { | ||
40 | fields: [ 'commentId' ], | ||
41 | where: { | ||
42 | commentId: { | ||
43 | [Op.ne]: null | ||
44 | } | ||
45 | } | ||
46 | }, | ||
47 | { | ||
48 | fields: [ 'abuseId' ], | ||
49 | where: { | ||
50 | abuseId: { | ||
51 | [Op.ne]: null | ||
52 | } | ||
53 | } | ||
54 | }, | ||
55 | { | ||
56 | fields: [ 'videoBlacklistId' ], | ||
57 | where: { | ||
58 | videoBlacklistId: { | ||
59 | [Op.ne]: null | ||
60 | } | ||
61 | } | ||
62 | }, | ||
63 | { | ||
64 | fields: [ 'videoImportId' ], | ||
65 | where: { | ||
66 | videoImportId: { | ||
67 | [Op.ne]: null | ||
68 | } | ||
69 | } | ||
70 | }, | ||
71 | { | ||
72 | fields: [ 'accountId' ], | ||
73 | where: { | ||
74 | accountId: { | ||
75 | [Op.ne]: null | ||
76 | } | ||
77 | } | ||
78 | }, | ||
79 | { | ||
80 | fields: [ 'actorFollowId' ], | ||
81 | where: { | ||
82 | actorFollowId: { | ||
83 | [Op.ne]: null | ||
84 | } | ||
85 | } | ||
86 | }, | ||
87 | { | ||
88 | fields: [ 'pluginId' ], | ||
89 | where: { | ||
90 | pluginId: { | ||
91 | [Op.ne]: null | ||
92 | } | ||
93 | } | ||
94 | }, | ||
95 | { | ||
96 | fields: [ 'applicationId' ], | ||
97 | where: { | ||
98 | applicationId: { | ||
99 | [Op.ne]: null | ||
100 | } | ||
101 | } | ||
102 | }, | ||
103 | { | ||
104 | fields: [ 'userRegistrationId' ], | ||
105 | where: { | ||
106 | userRegistrationId: { | ||
107 | [Op.ne]: null | ||
108 | } | ||
109 | } | ||
110 | } | ||
111 | ] as (ModelIndexesOptions & { where?: WhereOptions })[] | ||
112 | }) | ||
113 | export class UserNotificationModel extends Model<Partial<AttributesOnly<UserNotificationModel>>> { | ||
114 | |||
115 | @AllowNull(false) | ||
116 | @Default(null) | ||
117 | @Is('UserNotificationType', value => throwIfNotValid(value, isUserNotificationTypeValid, 'type')) | ||
118 | @Column | ||
119 | type: UserNotificationType | ||
120 | |||
121 | @AllowNull(false) | ||
122 | @Default(false) | ||
123 | @Is('UserNotificationRead', value => throwIfNotValid(value, isBooleanValid, 'read')) | ||
124 | @Column | ||
125 | read: boolean | ||
126 | |||
127 | @CreatedAt | ||
128 | createdAt: Date | ||
129 | |||
130 | @UpdatedAt | ||
131 | updatedAt: Date | ||
132 | |||
133 | @ForeignKey(() => UserModel) | ||
134 | @Column | ||
135 | userId: number | ||
136 | |||
137 | @BelongsTo(() => UserModel, { | ||
138 | foreignKey: { | ||
139 | allowNull: false | ||
140 | }, | ||
141 | onDelete: 'cascade' | ||
142 | }) | ||
143 | User: UserModel | ||
144 | |||
145 | @ForeignKey(() => VideoModel) | ||
146 | @Column | ||
147 | videoId: number | ||
148 | |||
149 | @BelongsTo(() => VideoModel, { | ||
150 | foreignKey: { | ||
151 | allowNull: true | ||
152 | }, | ||
153 | onDelete: 'cascade' | ||
154 | }) | ||
155 | Video: VideoModel | ||
156 | |||
157 | @ForeignKey(() => VideoCommentModel) | ||
158 | @Column | ||
159 | commentId: number | ||
160 | |||
161 | @BelongsTo(() => VideoCommentModel, { | ||
162 | foreignKey: { | ||
163 | allowNull: true | ||
164 | }, | ||
165 | onDelete: 'cascade' | ||
166 | }) | ||
167 | VideoComment: VideoCommentModel | ||
168 | |||
169 | @ForeignKey(() => AbuseModel) | ||
170 | @Column | ||
171 | abuseId: number | ||
172 | |||
173 | @BelongsTo(() => AbuseModel, { | ||
174 | foreignKey: { | ||
175 | allowNull: true | ||
176 | }, | ||
177 | onDelete: 'cascade' | ||
178 | }) | ||
179 | Abuse: AbuseModel | ||
180 | |||
181 | @ForeignKey(() => VideoBlacklistModel) | ||
182 | @Column | ||
183 | videoBlacklistId: number | ||
184 | |||
185 | @BelongsTo(() => VideoBlacklistModel, { | ||
186 | foreignKey: { | ||
187 | allowNull: true | ||
188 | }, | ||
189 | onDelete: 'cascade' | ||
190 | }) | ||
191 | VideoBlacklist: VideoBlacklistModel | ||
192 | |||
193 | @ForeignKey(() => VideoImportModel) | ||
194 | @Column | ||
195 | videoImportId: number | ||
196 | |||
197 | @BelongsTo(() => VideoImportModel, { | ||
198 | foreignKey: { | ||
199 | allowNull: true | ||
200 | }, | ||
201 | onDelete: 'cascade' | ||
202 | }) | ||
203 | VideoImport: VideoImportModel | ||
204 | |||
205 | @ForeignKey(() => AccountModel) | ||
206 | @Column | ||
207 | accountId: number | ||
208 | |||
209 | @BelongsTo(() => AccountModel, { | ||
210 | foreignKey: { | ||
211 | allowNull: true | ||
212 | }, | ||
213 | onDelete: 'cascade' | ||
214 | }) | ||
215 | Account: AccountModel | ||
216 | |||
217 | @ForeignKey(() => ActorFollowModel) | ||
218 | @Column | ||
219 | actorFollowId: number | ||
220 | |||
221 | @BelongsTo(() => ActorFollowModel, { | ||
222 | foreignKey: { | ||
223 | allowNull: true | ||
224 | }, | ||
225 | onDelete: 'cascade' | ||
226 | }) | ||
227 | ActorFollow: ActorFollowModel | ||
228 | |||
229 | @ForeignKey(() => PluginModel) | ||
230 | @Column | ||
231 | pluginId: number | ||
232 | |||
233 | @BelongsTo(() => PluginModel, { | ||
234 | foreignKey: { | ||
235 | allowNull: true | ||
236 | }, | ||
237 | onDelete: 'cascade' | ||
238 | }) | ||
239 | Plugin: PluginModel | ||
240 | |||
241 | @ForeignKey(() => ApplicationModel) | ||
242 | @Column | ||
243 | applicationId: number | ||
244 | |||
245 | @BelongsTo(() => ApplicationModel, { | ||
246 | foreignKey: { | ||
247 | allowNull: true | ||
248 | }, | ||
249 | onDelete: 'cascade' | ||
250 | }) | ||
251 | Application: ApplicationModel | ||
252 | |||
253 | @ForeignKey(() => UserRegistrationModel) | ||
254 | @Column | ||
255 | userRegistrationId: number | ||
256 | |||
257 | @BelongsTo(() => UserRegistrationModel, { | ||
258 | foreignKey: { | ||
259 | allowNull: true | ||
260 | }, | ||
261 | onDelete: 'cascade' | ||
262 | }) | ||
263 | UserRegistration: UserRegistrationModel | ||
264 | |||
265 | static listForApi (userId: number, start: number, count: number, sort: string, unread?: boolean) { | ||
266 | const where = { userId } | ||
267 | |||
268 | const query = { | ||
269 | userId, | ||
270 | unread, | ||
271 | offset: start, | ||
272 | limit: count, | ||
273 | sort, | ||
274 | where | ||
275 | } | ||
276 | |||
277 | if (unread !== undefined) query.where['read'] = !unread | ||
278 | |||
279 | return Promise.all([ | ||
280 | UserNotificationModel.count({ where }) | ||
281 | .then(count => count || 0), | ||
282 | |||
283 | count === 0 | ||
284 | ? [] as UserNotificationModelForApi[] | ||
285 | : new UserNotificationListQueryBuilder(this.sequelize, query).listNotifications() | ||
286 | ]).then(([ total, data ]) => ({ total, data })) | ||
287 | } | ||
288 | |||
289 | static markAsRead (userId: number, notificationIds: number[]) { | ||
290 | const query = { | ||
291 | where: { | ||
292 | userId, | ||
293 | id: { | ||
294 | [Op.in]: notificationIds | ||
295 | } | ||
296 | } | ||
297 | } | ||
298 | |||
299 | return UserNotificationModel.update({ read: true }, query) | ||
300 | } | ||
301 | |||
302 | static markAllAsRead (userId: number) { | ||
303 | const query = { where: { userId } } | ||
304 | |||
305 | return UserNotificationModel.update({ read: true }, query) | ||
306 | } | ||
307 | |||
308 | static removeNotificationsOf (options: { id: number, type: 'account' | 'server', forUserId?: number }) { | ||
309 | const id = forceNumber(options.id) | ||
310 | |||
311 | function buildAccountWhereQuery (base: string) { | ||
312 | const whereSuffix = options.forUserId | ||
313 | ? ` AND "userNotification"."userId" = ${options.forUserId}` | ||
314 | : '' | ||
315 | |||
316 | if (options.type === 'account') { | ||
317 | return base + | ||
318 | ` WHERE "account"."id" = ${id} ${whereSuffix}` | ||
319 | } | ||
320 | |||
321 | return base + | ||
322 | ` WHERE "actor"."serverId" = ${id} ${whereSuffix}` | ||
323 | } | ||
324 | |||
325 | const queries = [ | ||
326 | buildAccountWhereQuery( | ||
327 | `SELECT "userNotification"."id" FROM "userNotification" ` + | ||
328 | `INNER JOIN "account" ON "userNotification"."accountId" = "account"."id" ` + | ||
329 | `INNER JOIN actor ON "actor"."id" = "account"."actorId" ` | ||
330 | ), | ||
331 | |||
332 | // Remove notifications from muted accounts that followed ours | ||
333 | buildAccountWhereQuery( | ||
334 | `SELECT "userNotification"."id" FROM "userNotification" ` + | ||
335 | `INNER JOIN "actorFollow" ON "actorFollow".id = "userNotification"."actorFollowId" ` + | ||
336 | `INNER JOIN actor ON actor.id = "actorFollow"."actorId" ` + | ||
337 | `INNER JOIN account ON account."actorId" = actor.id ` | ||
338 | ), | ||
339 | |||
340 | // Remove notifications from muted accounts that commented something | ||
341 | buildAccountWhereQuery( | ||
342 | `SELECT "userNotification"."id" FROM "userNotification" ` + | ||
343 | `INNER JOIN "actorFollow" ON "actorFollow".id = "userNotification"."actorFollowId" ` + | ||
344 | `INNER JOIN actor ON actor.id = "actorFollow"."actorId" ` + | ||
345 | `INNER JOIN account ON account."actorId" = actor.id ` | ||
346 | ), | ||
347 | |||
348 | buildAccountWhereQuery( | ||
349 | `SELECT "userNotification"."id" FROM "userNotification" ` + | ||
350 | `INNER JOIN "videoComment" ON "videoComment".id = "userNotification"."commentId" ` + | ||
351 | `INNER JOIN account ON account.id = "videoComment"."accountId" ` + | ||
352 | `INNER JOIN actor ON "actor"."id" = "account"."actorId" ` | ||
353 | ) | ||
354 | ] | ||
355 | |||
356 | const query = `DELETE FROM "userNotification" WHERE id IN (${queries.join(' UNION ')})` | ||
357 | |||
358 | return UserNotificationModel.sequelize.query(query) | ||
359 | } | ||
360 | |||
361 | toFormattedJSON (this: UserNotificationModelForApi): UserNotification { | ||
362 | const video = this.Video | ||
363 | ? { | ||
364 | ...this.formatVideo(this.Video), | ||
365 | |||
366 | channel: this.formatActor(this.Video.VideoChannel) | ||
367 | } | ||
368 | : undefined | ||
369 | |||
370 | const videoImport = this.VideoImport | ||
371 | ? { | ||
372 | id: this.VideoImport.id, | ||
373 | video: this.VideoImport.Video | ||
374 | ? this.formatVideo(this.VideoImport.Video) | ||
375 | : undefined, | ||
376 | torrentName: this.VideoImport.torrentName, | ||
377 | magnetUri: this.VideoImport.magnetUri, | ||
378 | targetUrl: this.VideoImport.targetUrl | ||
379 | } | ||
380 | : undefined | ||
381 | |||
382 | const comment = this.VideoComment | ||
383 | ? { | ||
384 | id: this.VideoComment.id, | ||
385 | threadId: this.VideoComment.getThreadId(), | ||
386 | account: this.formatActor(this.VideoComment.Account), | ||
387 | video: this.formatVideo(this.VideoComment.Video) | ||
388 | } | ||
389 | : undefined | ||
390 | |||
391 | const abuse = this.Abuse ? this.formatAbuse(this.Abuse) : undefined | ||
392 | |||
393 | const videoBlacklist = this.VideoBlacklist | ||
394 | ? { | ||
395 | id: this.VideoBlacklist.id, | ||
396 | video: this.formatVideo(this.VideoBlacklist.Video) | ||
397 | } | ||
398 | : undefined | ||
399 | |||
400 | const account = this.Account ? this.formatActor(this.Account) : undefined | ||
401 | |||
402 | const actorFollowingType = { | ||
403 | Application: 'instance' as 'instance', | ||
404 | Group: 'channel' as 'channel', | ||
405 | Person: 'account' as 'account' | ||
406 | } | ||
407 | const actorFollow = this.ActorFollow | ||
408 | ? { | ||
409 | id: this.ActorFollow.id, | ||
410 | state: this.ActorFollow.state, | ||
411 | follower: { | ||
412 | id: this.ActorFollow.ActorFollower.Account.id, | ||
413 | displayName: this.ActorFollow.ActorFollower.Account.getDisplayName(), | ||
414 | name: this.ActorFollow.ActorFollower.preferredUsername, | ||
415 | host: this.ActorFollow.ActorFollower.getHost(), | ||
416 | |||
417 | ...this.formatAvatars(this.ActorFollow.ActorFollower.Avatars) | ||
418 | }, | ||
419 | following: { | ||
420 | type: actorFollowingType[this.ActorFollow.ActorFollowing.type], | ||
421 | displayName: (this.ActorFollow.ActorFollowing.VideoChannel || this.ActorFollow.ActorFollowing.Account).getDisplayName(), | ||
422 | name: this.ActorFollow.ActorFollowing.preferredUsername, | ||
423 | host: this.ActorFollow.ActorFollowing.getHost() | ||
424 | } | ||
425 | } | ||
426 | : undefined | ||
427 | |||
428 | const plugin = this.Plugin | ||
429 | ? { | ||
430 | name: this.Plugin.name, | ||
431 | type: this.Plugin.type, | ||
432 | latestVersion: this.Plugin.latestVersion | ||
433 | } | ||
434 | : undefined | ||
435 | |||
436 | const peertube = this.Application | ||
437 | ? { latestVersion: this.Application.latestPeerTubeVersion } | ||
438 | : undefined | ||
439 | |||
440 | const registration = this.UserRegistration | ||
441 | ? { id: this.UserRegistration.id, username: this.UserRegistration.username } | ||
442 | : undefined | ||
443 | |||
444 | return { | ||
445 | id: this.id, | ||
446 | type: this.type, | ||
447 | read: this.read, | ||
448 | video, | ||
449 | videoImport, | ||
450 | comment, | ||
451 | abuse, | ||
452 | videoBlacklist, | ||
453 | account, | ||
454 | actorFollow, | ||
455 | plugin, | ||
456 | peertube, | ||
457 | registration, | ||
458 | createdAt: this.createdAt.toISOString(), | ||
459 | updatedAt: this.updatedAt.toISOString() | ||
460 | } | ||
461 | } | ||
462 | |||
463 | formatVideo (video: UserNotificationIncludes.VideoInclude) { | ||
464 | return { | ||
465 | id: video.id, | ||
466 | uuid: video.uuid, | ||
467 | shortUUID: uuidToShort(video.uuid), | ||
468 | name: video.name | ||
469 | } | ||
470 | } | ||
471 | |||
472 | formatAbuse (abuse: UserNotificationIncludes.AbuseInclude) { | ||
473 | const commentAbuse = abuse.VideoCommentAbuse?.VideoComment | ||
474 | ? { | ||
475 | threadId: abuse.VideoCommentAbuse.VideoComment.getThreadId(), | ||
476 | |||
477 | video: abuse.VideoCommentAbuse.VideoComment.Video | ||
478 | ? { | ||
479 | id: abuse.VideoCommentAbuse.VideoComment.Video.id, | ||
480 | name: abuse.VideoCommentAbuse.VideoComment.Video.name, | ||
481 | shortUUID: uuidToShort(abuse.VideoCommentAbuse.VideoComment.Video.uuid), | ||
482 | uuid: abuse.VideoCommentAbuse.VideoComment.Video.uuid | ||
483 | } | ||
484 | : undefined | ||
485 | } | ||
486 | : undefined | ||
487 | |||
488 | const videoAbuse = abuse.VideoAbuse?.Video | ||
489 | ? this.formatVideo(abuse.VideoAbuse.Video) | ||
490 | : undefined | ||
491 | |||
492 | const accountAbuse = (!commentAbuse && !videoAbuse && abuse.FlaggedAccount) | ||
493 | ? this.formatActor(abuse.FlaggedAccount) | ||
494 | : undefined | ||
495 | |||
496 | return { | ||
497 | id: abuse.id, | ||
498 | state: abuse.state, | ||
499 | video: videoAbuse, | ||
500 | comment: commentAbuse, | ||
501 | account: accountAbuse | ||
502 | } | ||
503 | } | ||
504 | |||
505 | formatActor ( | ||
506 | accountOrChannel: UserNotificationIncludes.AccountIncludeActor | UserNotificationIncludes.VideoChannelIncludeActor | ||
507 | ) { | ||
508 | return { | ||
509 | id: accountOrChannel.id, | ||
510 | displayName: accountOrChannel.getDisplayName(), | ||
511 | name: accountOrChannel.Actor.preferredUsername, | ||
512 | host: accountOrChannel.Actor.getHost(), | ||
513 | |||
514 | ...this.formatAvatars(accountOrChannel.Actor.Avatars) | ||
515 | } | ||
516 | } | ||
517 | |||
518 | formatAvatars (avatars: UserNotificationIncludes.ActorImageInclude[]) { | ||
519 | if (!avatars || avatars.length === 0) return { avatar: undefined, avatars: [] } | ||
520 | |||
521 | return { | ||
522 | avatar: this.formatAvatar(getBiggestActorImage(avatars)), | ||
523 | |||
524 | avatars: avatars.map(a => this.formatAvatar(a)) | ||
525 | } | ||
526 | } | ||
527 | |||
528 | formatAvatar (a: UserNotificationIncludes.ActorImageInclude) { | ||
529 | return { | ||
530 | path: a.getStaticPath(), | ||
531 | width: a.width | ||
532 | } | ||
533 | } | ||
534 | } | ||