]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blobdiff - server/models/account/user-notification.ts
preserve original variable names server-side
[github/Chocobozzz/PeerTube.git] / server / models / account / user-notification.ts
index e22f0d57f661767296704fabe439baf4f98c664c..5a725187a2918f1cb5ed36df1e223379e4d686e3 100644 (file)
@@ -6,85 +6,219 @@ import { isUserNotificationTypeValid } from '../../helpers/custom-validators/use
 import { UserModel } from './user'
 import { VideoModel } from '../video/video'
 import { VideoCommentModel } from '../video/video-comment'
-import { Op } from 'sequelize'
+import { FindOptions, ModelIndexesOptions, Op, WhereOptions } from 'sequelize'
 import { VideoChannelModel } from '../video/video-channel'
 import { AccountModel } from './account'
 import { VideoAbuseModel } from '../video/video-abuse'
 import { VideoBlacklistModel } from '../video/video-blacklist'
+import { VideoImportModel } from '../video/video-import'
+import { ActorModel } from '../activitypub/actor'
+import { ActorFollowModel } from '../activitypub/actor-follow'
+import { AvatarModel } from '../avatar/avatar'
+import { ServerModel } from '../server/server'
+import { UserNotificationIncludes, UserNotificationModelForApi } from '@server/typings/models/user'
 
 enum ScopeNames {
   WITH_ALL = 'WITH_ALL'
 }
 
-@Scopes({
+function buildActorWithAvatarInclude () {
+  return {
+    attributes: [ 'preferredUsername' ],
+    model: ActorModel.unscoped(),
+    required: true,
+    include: [
+      {
+        attributes: [ 'filename' ],
+        model: AvatarModel.unscoped(),
+        required: false
+      },
+      {
+        attributes: [ 'host' ],
+        model: ServerModel.unscoped(),
+        required: false
+      }
+    ]
+  }
+}
+
+function buildVideoInclude (required: boolean) {
+  return {
+    attributes: [ 'id', 'uuid', 'name' ],
+    model: VideoModel.unscoped(),
+    required
+  }
+}
+
+function buildChannelInclude (required: boolean, withActor = false) {
+  return {
+    required,
+    attributes: [ 'id', 'name' ],
+    model: VideoChannelModel.unscoped(),
+    include: withActor === true ? [ buildActorWithAvatarInclude() ] : []
+  }
+}
+
+function buildAccountInclude (required: boolean, withActor = false) {
+  return {
+    required,
+    attributes: [ 'id', 'name' ],
+    model: AccountModel.unscoped(),
+    include: withActor === true ? [ buildActorWithAvatarInclude() ] : []
+  }
+}
+
+@Scopes(() => ({
   [ScopeNames.WITH_ALL]: {
     include: [
+      Object.assign(buildVideoInclude(false), {
+        include: [ buildChannelInclude(true, true) ]
+      }),
+
       {
-        attributes: [ 'id', 'uuid', 'name' ],
-        model: () => VideoModel.unscoped(),
+        attributes: [ 'id', 'originCommentId' ],
+        model: VideoCommentModel.unscoped(),
         required: false,
         include: [
-          {
-            required: true,
-            attributes: [ 'id', 'name' ],
-            model: () => VideoChannelModel.unscoped()
-          }
+          buildAccountInclude(true, true),
+          buildVideoInclude(true)
         ]
       },
+
       {
         attributes: [ 'id' ],
-        model: () => VideoCommentModel.unscoped(),
+        model: VideoAbuseModel.unscoped(),
         required: false,
-        include: [
-          {
-            required: true,
-            attributes: [ 'id', 'name' ],
-            model: () => AccountModel.unscoped()
-          },
-          {
-            required: true,
-            attributes: [ 'id', 'uuid', 'name' ],
-            model: () => VideoModel.unscoped()
-          }
-        ]
+        include: [ buildVideoInclude(true) ]
       },
+
       {
         attributes: [ 'id' ],
-        model: () => VideoAbuseModel.unscoped(),
+        model: VideoBlacklistModel.unscoped(),
         required: false,
-        include: [
-          {
-            required: true,
-            attributes: [ 'id', 'uuid', 'name' ],
-            model: () => VideoModel.unscoped()
-          }
-        ]
+        include: [ buildVideoInclude(true) ]
       },
+
       {
-        attributes: [ 'id' ],
-        model: () => VideoBlacklistModel.unscoped(),
+        attributes: [ 'id', 'magnetUri', 'targetUrl', 'torrentName' ],
+        model: VideoImportModel.unscoped(),
+        required: false,
+        include: [ buildVideoInclude(false) ]
+      },
+
+      {
+        attributes: [ 'id', 'state' ],
+        model: ActorFollowModel.unscoped(),
         required: false,
         include: [
           {
+            attributes: [ 'preferredUsername' ],
+            model: ActorModel.unscoped(),
             required: true,
-            attributes: [ 'id', 'uuid', 'name' ],
-            model: () => VideoModel.unscoped()
+            as: 'ActorFollower',
+            include: [
+              {
+                attributes: [ 'id', 'name' ],
+                model: AccountModel.unscoped(),
+                required: true
+              },
+              {
+                attributes: [ 'filename' ],
+                model: AvatarModel.unscoped(),
+                required: false
+              },
+              {
+                attributes: [ 'host' ],
+                model: ServerModel.unscoped(),
+                required: false
+              }
+            ]
+          },
+          {
+            attributes: [ 'preferredUsername', 'type' ],
+            model: ActorModel.unscoped(),
+            required: true,
+            as: 'ActorFollowing',
+            include: [
+              buildChannelInclude(false),
+              buildAccountInclude(false),
+              {
+                attributes: [ 'host' ],
+                model: ServerModel.unscoped(),
+                required: false
+              }
+            ]
           }
         ]
-      }
+      },
+
+      buildAccountInclude(false, true)
     ]
   }
-})
+}))
 @Table({
   tableName: 'userNotification',
   indexes: [
     {
-      fields: [ 'videoId' ]
+      fields: [ 'userId' ]
+    },
+    {
+      fields: [ 'videoId' ],
+      where: {
+        videoId: {
+          [Op.ne]: null
+        }
+      }
+    },
+    {
+      fields: [ 'commentId' ],
+      where: {
+        commentId: {
+          [Op.ne]: null
+        }
+      }
+    },
+    {
+      fields: [ 'videoAbuseId' ],
+      where: {
+        videoAbuseId: {
+          [Op.ne]: null
+        }
+      }
+    },
+    {
+      fields: [ 'videoBlacklistId' ],
+      where: {
+        videoBlacklistId: {
+          [Op.ne]: null
+        }
+      }
+    },
+    {
+      fields: [ 'videoImportId' ],
+      where: {
+        videoImportId: {
+          [Op.ne]: null
+        }
+      }
     },
     {
-      fields: [ 'commentId' ]
+      fields: [ 'accountId' ],
+      where: {
+        accountId: {
+          [Op.ne]: null
+        }
+      }
+    },
+    {
+      fields: [ 'actorFollowId' ],
+      where: {
+        actorFollowId: {
+          [Op.ne]: null
+        }
+      }
     }
-  ]
+  ] as (ModelIndexesOptions & { where?: WhereOptions })[]
 })
 export class UserNotificationModel extends Model<UserNotificationModel> {
 
@@ -166,24 +300,62 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
   })
   VideoBlacklist: VideoBlacklistModel
 
-  static listForApi (userId: number, start: number, count: number, sort: string) {
-    const query = {
+  @ForeignKey(() => VideoImportModel)
+  @Column
+  videoImportId: number
+
+  @BelongsTo(() => VideoImportModel, {
+    foreignKey: {
+      allowNull: true
+    },
+    onDelete: 'cascade'
+  })
+  VideoImport: VideoImportModel
+
+  @ForeignKey(() => AccountModel)
+  @Column
+  accountId: number
+
+  @BelongsTo(() => AccountModel, {
+    foreignKey: {
+      allowNull: true
+    },
+    onDelete: 'cascade'
+  })
+  Account: AccountModel
+
+  @ForeignKey(() => ActorFollowModel)
+  @Column
+  actorFollowId: number
+
+  @BelongsTo(() => ActorFollowModel, {
+    foreignKey: {
+      allowNull: true
+    },
+    onDelete: 'cascade'
+  })
+  ActorFollow: ActorFollowModel
+
+  static listForApi (userId: number, start: number, count: number, sort: string, unread?: boolean) {
+    const where = { userId }
+
+    const query: FindOptions = {
       offset: start,
       limit: count,
       order: getSort(sort),
-      where: {
-        userId
-      }
+      where
     }
 
-    return UserNotificationModel.scope(ScopeNames.WITH_ALL)
-                                .findAndCountAll(query)
-                                .then(({ rows, count }) => {
-                                  return {
-                                    data: rows,
-                                    total: count
-                                  }
-                                })
+    if (unread !== undefined) query.where['read'] = !unread
+
+    return Promise.all([
+      UserNotificationModel.count({ where })
+        .then(count => count || 0),
+
+      count === 0
+        ? []
+        : UserNotificationModel.scope(ScopeNames.WITH_ALL).findAll(query)
+    ]).then(([ total, data ]) => ({ total, data }))
   }
 
   static markAsRead (userId: number, notificationIds: number[]) {
@@ -191,7 +363,7 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
       where: {
         userId,
         id: {
-          [Op.any]: notificationIds
+          [Op.in]: notificationIds
         }
       }
     }
@@ -199,45 +371,64 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
     return UserNotificationModel.update({ read: true }, query)
   }
 
-  toFormattedJSON (): UserNotification {
-    const video = this.Video ? {
-      id: this.Video.id,
-      uuid: this.Video.uuid,
-      name: this.Video.name,
-      channel: {
-        id: this.Video.VideoChannel.id,
-        displayName: this.Video.VideoChannel.getDisplayName()
-      }
+  static markAllAsRead (userId: number) {
+    const query = { where: { userId } }
+
+    return UserNotificationModel.update({ read: true }, query)
+  }
+
+  toFormattedJSON (this: UserNotificationModelForApi): UserNotification {
+    const video = this.Video
+      ? Object.assign(this.formatVideo(this.Video), { channel: this.formatActor(this.Video.VideoChannel) })
+      : undefined
+
+    const videoImport = this.VideoImport ? {
+      id: this.VideoImport.id,
+      video: this.VideoImport.Video ? this.formatVideo(this.VideoImport.Video) : undefined,
+      torrentName: this.VideoImport.torrentName,
+      magnetUri: this.VideoImport.magnetUri,
+      targetUrl: this.VideoImport.targetUrl
     } : undefined
 
     const comment = this.Comment ? {
       id: this.Comment.id,
-      account: {
-        id: this.Comment.Account.id,
-        displayName: this.Comment.Account.getDisplayName()
-      },
-      video: {
-        id: this.Comment.Video.id,
-        uuid: this.Comment.Video.uuid,
-        name: this.Comment.Video.name
-      }
+      threadId: this.Comment.getThreadId(),
+      account: this.formatActor(this.Comment.Account),
+      video: this.formatVideo(this.Comment.Video)
     } : undefined
 
     const videoAbuse = this.VideoAbuse ? {
       id: this.VideoAbuse.id,
-      video: {
-        id: this.VideoAbuse.Video.id,
-        uuid: this.VideoAbuse.Video.uuid,
-        name: this.VideoAbuse.Video.name
-      }
+      video: this.formatVideo(this.VideoAbuse.Video)
     } : undefined
 
     const videoBlacklist = this.VideoBlacklist ? {
       id: this.VideoBlacklist.id,
-      video: {
-        id: this.VideoBlacklist.Video.id,
-        uuid: this.VideoBlacklist.Video.uuid,
-        name: this.VideoBlacklist.Video.name
+      video: this.formatVideo(this.VideoBlacklist.Video)
+    } : undefined
+
+    const account = this.Account ? this.formatActor(this.Account) : undefined
+
+    const actorFollowingType = {
+      Application: 'instance' as 'instance',
+      Group: 'channel' as 'channel',
+      Person: 'account' as 'account'
+    }
+    const actorFollow = this.ActorFollow ? {
+      id: this.ActorFollow.id,
+      state: this.ActorFollow.state,
+      follower: {
+        id: this.ActorFollow.ActorFollower.Account.id,
+        displayName: this.ActorFollow.ActorFollower.Account.getDisplayName(),
+        name: this.ActorFollow.ActorFollower.preferredUsername,
+        avatar: this.ActorFollow.ActorFollower.Avatar ? { path: this.ActorFollow.ActorFollower.Avatar.getStaticPath() } : undefined,
+        host: this.ActorFollow.ActorFollower.getHost()
+      },
+      following: {
+        type: actorFollowingType[this.ActorFollow.ActorFollowing.type],
+        displayName: (this.ActorFollow.ActorFollowing.VideoChannel || this.ActorFollow.ActorFollowing.Account).getDisplayName(),
+        name: this.ActorFollow.ActorFollowing.preferredUsername,
+        host: this.ActorFollow.ActorFollowing.getHost()
       }
     } : undefined
 
@@ -246,11 +437,39 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
       type: this.type,
       read: this.read,
       video,
+      videoImport,
       comment,
       videoAbuse,
       videoBlacklist,
+      account,
+      actorFollow,
       createdAt: this.createdAt.toISOString(),
       updatedAt: this.updatedAt.toISOString()
     }
   }
+
+  formatVideo (this: UserNotificationModelForApi, video: UserNotificationIncludes.VideoInclude) {
+    return {
+      id: video.id,
+      uuid: video.uuid,
+      name: video.name
+    }
+  }
+
+  formatActor (
+    this: UserNotificationModelForApi,
+    accountOrChannel: UserNotificationIncludes.AccountIncludeActor | UserNotificationIncludes.VideoChannelIncludeActor
+  ) {
+    const avatar = accountOrChannel.Actor.Avatar
+      ? { path: accountOrChannel.Actor.Avatar.getStaticPath() }
+      : undefined
+
+    return {
+      id: accountOrChannel.id,
+      displayName: accountOrChannel.getDisplayName(),
+      name: accountOrChannel.Actor.preferredUsername,
+      host: accountOrChannel.Actor.getHost(),
+      avatar
+    }
+  }
 }