Split types and typings
[oweals/peertube.git] / server / models / account / user-notification.ts
index 08388f2687e7aa537144c7fd39c7a65da161bdc5..30985bb0f19cb318f485578353bc250c8eb15a8b 100644 (file)
@@ -6,7 +6,7 @@ import { isUserNotificationTypeValid } from '../../helpers/custom-validators/use
 import { UserModel } from './user'
 import { VideoModel } from '../video/video'
 import { VideoCommentModel } from '../video/video-comment'
-import { FindOptions, 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'
@@ -16,6 +16,7 @@ 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/types/models/user'
 
 enum ScopeNames {
   WITH_ALL = 'WITH_ALL'
@@ -24,17 +25,17 @@ enum ScopeNames {
 function buildActorWithAvatarInclude () {
   return {
     attributes: [ 'preferredUsername' ],
-    model: () => ActorModel.unscoped(),
+    model: ActorModel.unscoped(),
     required: true,
     include: [
       {
         attributes: [ 'filename' ],
-        model: () => AvatarModel.unscoped(),
+        model: AvatarModel.unscoped(),
         required: false
       },
       {
         attributes: [ 'host' ],
-        model: () => ServerModel.unscoped(),
+        model: ServerModel.unscoped(),
         required: false
       }
     ]
@@ -44,7 +45,7 @@ function buildActorWithAvatarInclude () {
 function buildVideoInclude (required: boolean) {
   return {
     attributes: [ 'id', 'uuid', 'name' ],
-    model: () => VideoModel.unscoped(),
+    model: VideoModel.unscoped(),
     required
   }
 }
@@ -53,7 +54,7 @@ function buildChannelInclude (required: boolean, withActor = false) {
   return {
     required,
     attributes: [ 'id', 'name' ],
-    model: () => VideoChannelModel.unscoped(),
+    model: VideoChannelModel.unscoped(),
     include: withActor === true ? [ buildActorWithAvatarInclude() ] : []
   }
 }
@@ -62,12 +63,12 @@ function buildAccountInclude (required: boolean, withActor = false) {
   return {
     required,
     attributes: [ 'id', 'name' ],
-    model: () => AccountModel.unscoped(),
+    model: AccountModel.unscoped(),
     include: withActor === true ? [ buildActorWithAvatarInclude() ] : []
   }
 }
 
-@Scopes({
+@Scopes(() => ({
   [ScopeNames.WITH_ALL]: {
     include: [
       Object.assign(buildVideoInclude(false), {
@@ -76,7 +77,7 @@ function buildAccountInclude (required: boolean, withActor = false) {
 
       {
         attributes: [ 'id', 'originCommentId' ],
-        model: () => VideoCommentModel.unscoped(),
+        model: VideoCommentModel.unscoped(),
         required: false,
         include: [
           buildAccountInclude(true, true),
@@ -86,70 +87,75 @@ function buildAccountInclude (required: boolean, withActor = false) {
 
       {
         attributes: [ 'id' ],
-        model: () => VideoAbuseModel.unscoped(),
+        model: VideoAbuseModel.unscoped(),
         required: false,
         include: [ buildVideoInclude(true) ]
       },
 
       {
         attributes: [ 'id' ],
-        model: () => VideoBlacklistModel.unscoped(),
+        model: VideoBlacklistModel.unscoped(),
         required: false,
         include: [ buildVideoInclude(true) ]
       },
 
       {
         attributes: [ 'id', 'magnetUri', 'targetUrl', 'torrentName' ],
-        model: () => VideoImportModel.unscoped(),
+        model: VideoImportModel.unscoped(),
         required: false,
         include: [ buildVideoInclude(false) ]
       },
 
       {
         attributes: [ 'id', 'state' ],
-        model: () => ActorFollowModel.unscoped(),
+        model: ActorFollowModel.unscoped(),
         required: false,
         include: [
           {
             attributes: [ 'preferredUsername' ],
-            model: () => ActorModel.unscoped(),
+            model: ActorModel.unscoped(),
             required: true,
             as: 'ActorFollower',
             include: [
               {
                 attributes: [ 'id', 'name' ],
-                model: () => AccountModel.unscoped(),
+                model: AccountModel.unscoped(),
                 required: true
               },
               {
                 attributes: [ 'filename' ],
-                model: () => AvatarModel.unscoped(),
+                model: AvatarModel.unscoped(),
                 required: false
               },
               {
                 attributes: [ 'host' ],
-                model: () => ServerModel.unscoped(),
+                model: ServerModel.unscoped(),
                 required: false
               }
             ]
           },
           {
-            attributes: [ 'preferredUsername' ],
-            model: () => ActorModel.unscoped(),
+            attributes: [ 'preferredUsername', 'type' ],
+            model: ActorModel.unscoped(),
             required: true,
             as: 'ActorFollowing',
             include: [
               buildChannelInclude(false),
-              buildAccountInclude(false)
+              buildAccountInclude(false),
+              {
+                attributes: [ 'host' ],
+                model: ServerModel.unscoped(),
+                required: false
+              }
             ]
           }
         ]
       },
 
       buildAccountInclude(false, true)
-    ] as any // FIXME: sequelize typings
+    ]
   }
-})
+}))
 @Table({
   tableName: 'userNotification',
   indexes: [
@@ -212,7 +218,7 @@ function buildAccountInclude (required: boolean, withActor = false) {
         }
       }
     }
-  ] as any // FIXME: sequelize typings
+  ] as (ModelIndexesOptions & { where?: WhereOptions })[]
 })
 export class UserNotificationModel extends Model<UserNotificationModel> {
 
@@ -331,25 +337,25 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
   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
     }
 
     if (unread !== undefined) query.where['read'] = !unread
 
-    return UserNotificationModel.scope(ScopeNames.WITH_ALL)
-                                .findAndCountAll(query)
-                                .then(({ rows, count }) => {
-                                  return {
-                                    data: rows,
-                                    total: count
-                                  }
-                                })
+    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[]) {
@@ -357,7 +363,7 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
       where: {
         userId,
         id: {
-          [Op.any]: notificationIds
+          [Op.in]: notificationIds
         }
       }
     }
@@ -371,9 +377,9 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
     return UserNotificationModel.update({ read: true }, query)
   }
 
-  toFormattedJSON (): UserNotification {
+  toFormattedJSON (this: UserNotificationModelForApi): UserNotification {
     const video = this.Video
-      ? Object.assign(this.formatVideo(this.Video),{ channel: this.formatActor(this.Video.VideoChannel) })
+      ? Object.assign(this.formatVideo(this.Video), { channel: this.formatActor(this.Video.VideoChannel) })
       : undefined
 
     const videoImport = this.VideoImport ? {
@@ -403,6 +409,11 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
 
     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,
@@ -410,13 +421,14 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
         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.getWebserverPath() } : undefined,
+        avatar: this.ActorFollow.ActorFollower.Avatar ? { path: this.ActorFollow.ActorFollower.Avatar.getStaticPath() } : undefined,
         host: this.ActorFollow.ActorFollower.getHost()
       },
       following: {
-        type: this.ActorFollow.ActorFollowing.VideoChannel ? 'channel' as 'channel' : 'account' as 'account',
+        type: actorFollowingType[this.ActorFollow.ActorFollowing.type],
         displayName: (this.ActorFollow.ActorFollowing.VideoChannel || this.ActorFollow.ActorFollowing.Account).getDisplayName(),
-        name: this.ActorFollow.ActorFollowing.preferredUsername
+        name: this.ActorFollow.ActorFollowing.preferredUsername,
+        host: this.ActorFollow.ActorFollowing.getHost()
       }
     } : undefined
 
@@ -436,7 +448,7 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
     }
   }
 
-  private formatVideo (video: VideoModel) {
+  formatVideo (this: UserNotificationModelForApi, video: UserNotificationIncludes.VideoInclude) {
     return {
       id: video.id,
       uuid: video.uuid,
@@ -444,9 +456,12 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
     }
   }
 
-  private formatActor (accountOrChannel: AccountModel | VideoChannelModel) {
+  formatActor (
+    this: UserNotificationModelForApi,
+    accountOrChannel: UserNotificationIncludes.AccountIncludeActor | UserNotificationIncludes.VideoChannelIncludeActor
+  ) {
     const avatar = accountOrChannel.Actor.Avatar
-      ? { path: accountOrChannel.Actor.Avatar.getWebserverPath() }
+      ? { path: accountOrChannel.Actor.Avatar.getStaticPath() }
       : undefined
 
     return {