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