]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blobdiff - server/models/user/user-notification.ts
Fix subscribe button responsive
[github/Chocobozzz/PeerTube.git] / server / models / user / user-notification.ts
index f7f9ac867c5e240abe7e6fb6f7df3b28039e30e0..667ee7f5f8e6e917d9157320e18e8190eed7930d 100644 (file)
-import { FindOptions, ModelIndexesOptions, Op, WhereOptions } from 'sequelize'
-import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
+import { ModelIndexesOptions, Op, WhereOptions } from 'sequelize'
+import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
+import { getBiggestActorImage } from '@server/lib/actor-image'
 import { UserNotificationIncludes, UserNotificationModelForApi } from '@server/types/models/user'
-import { UserNotification, UserNotificationType } from '../../../shared'
+import { forceNumber } from '@shared/core-utils'
+import { uuidToShort } from '@shared/extra-utils'
+import { UserNotification, UserNotificationType } from '@shared/models'
+import { AttributesOnly } from '@shared/typescript-utils'
 import { isBooleanValid } from '../../helpers/custom-validators/misc'
 import { isUserNotificationTypeValid } from '../../helpers/custom-validators/user-notifications'
 import { AbuseModel } from '../abuse/abuse'
-import { VideoAbuseModel } from '../abuse/video-abuse'
-import { VideoCommentAbuseModel } from '../abuse/video-comment-abuse'
 import { AccountModel } from '../account/account'
-import { ActorModel } from '../actor/actor'
 import { ActorFollowModel } from '../actor/actor-follow'
-import { ActorImageModel } from '../actor/actor-image'
 import { ApplicationModel } from '../application/application'
 import { PluginModel } from '../server/plugin'
-import { ServerModel } from '../server/server'
-import { getSort, throwIfNotValid } from '../utils'
+import { throwIfNotValid } from '../shared'
 import { VideoModel } from '../video/video'
 import { VideoBlacklistModel } from '../video/video-blacklist'
-import { VideoChannelModel } from '../video/video-channel'
 import { VideoCommentModel } from '../video/video-comment'
 import { VideoImportModel } from '../video/video-import'
+import { UserNotificationListQueryBuilder } from './sql/user-notitication-list-query-builder'
 import { UserModel } from './user'
+import { UserRegistrationModel } from './user-registration'
 
-enum ScopeNames {
-  WITH_ALL = 'WITH_ALL'
-}
-
-function buildActorWithAvatarInclude () {
-  return {
-    attributes: [ 'preferredUsername' ],
-    model: ActorModel.unscoped(),
-    required: true,
-    include: [
-      {
-        attributes: [ 'filename' ],
-        as: 'Avatar',
-        model: ActorImageModel.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', 'originCommentId' ],
-        model: VideoCommentModel.unscoped(),
-        required: false,
-        include: [
-          buildAccountInclude(true, true),
-          buildVideoInclude(true)
-        ]
-      },
-
-      {
-        attributes: [ 'id', 'state' ],
-        model: AbuseModel.unscoped(),
-        required: false,
-        include: [
-          {
-            attributes: [ 'id' ],
-            model: VideoAbuseModel.unscoped(),
-            required: false,
-            include: [ buildVideoInclude(false) ]
-          },
-          {
-            attributes: [ 'id' ],
-            model: VideoCommentAbuseModel.unscoped(),
-            required: false,
-            include: [
-              {
-                attributes: [ 'id', 'originCommentId' ],
-                model: VideoCommentModel.unscoped(),
-                required: false,
-                include: [
-                  {
-                    attributes: [ 'id', 'name', 'uuid' ],
-                    model: VideoModel.unscoped(),
-                    required: false
-                  }
-                ]
-              }
-            ]
-          },
-          {
-            model: AccountModel,
-            as: 'FlaggedAccount',
-            required: false,
-            include: [ buildActorWithAvatarInclude() ]
-          }
-        ]
-      },
-
-      {
-        attributes: [ 'id' ],
-        model: VideoBlacklistModel.unscoped(),
-        required: false,
-        include: [ buildVideoInclude(true) ]
-      },
-
-      {
-        attributes: [ 'id', 'magnetUri', 'targetUrl', 'torrentName' ],
-        model: VideoImportModel.unscoped(),
-        required: false,
-        include: [ buildVideoInclude(false) ]
-      },
-
-      {
-        attributes: [ 'id', 'name', 'type', 'latestVersion' ],
-        model: PluginModel.unscoped(),
-        required: false
-      },
-
-      {
-        attributes: [ 'id', 'latestPeerTubeVersion' ],
-        model: ApplicationModel.unscoped(),
-        required: false
-      },
-
-      {
-        attributes: [ 'id', 'state' ],
-        model: ActorFollowModel.unscoped(),
-        required: false,
-        include: [
-          {
-            attributes: [ 'preferredUsername' ],
-            model: ActorModel.unscoped(),
-            required: true,
-            as: 'ActorFollower',
-            include: [
-              {
-                attributes: [ 'id', 'name' ],
-                model: AccountModel.unscoped(),
-                required: true
-              },
-              {
-                attributes: [ 'filename' ],
-                as: 'Avatar',
-                model: ActorImageModel.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: [
@@ -283,10 +99,18 @@ function buildAccountInclude (required: boolean, withActor = false) {
           [Op.ne]: null
         }
       }
+    },
+    {
+      fields: [ 'userRegistrationId' ],
+      where: {
+        userRegistrationId: {
+          [Op.ne]: null
+        }
+      }
     }
   ] as (ModelIndexesOptions & { where?: WhereOptions })[]
 })
-export class UserNotificationModel extends Model {
+export class UserNotificationModel extends Model<Partial<AttributesOnly<UserNotificationModel>>> {
 
   @AllowNull(false)
   @Default(null)
@@ -340,7 +164,7 @@ export class UserNotificationModel extends Model {
     },
     onDelete: 'cascade'
   })
-  Comment: VideoCommentModel
+  VideoComment: VideoCommentModel
 
   @ForeignKey(() => AbuseModel)
   @Column
@@ -426,13 +250,27 @@ export class UserNotificationModel extends Model {
   })
   Application: ApplicationModel
 
+  @ForeignKey(() => UserRegistrationModel)
+  @Column
+  userRegistrationId: number
+
+  @BelongsTo(() => UserRegistrationModel, {
+    foreignKey: {
+      allowNull: true
+    },
+    onDelete: 'cascade'
+  })
+  UserRegistration: UserRegistrationModel
+
   static listForApi (userId: number, start: number, count: number, sort: string, unread?: boolean) {
     const where = { userId }
 
-    const query: FindOptions = {
+    const query = {
+      userId,
+      unread,
       offset: start,
       limit: count,
-      order: getSort(sort),
+      sort,
       where
     }
 
@@ -443,8 +281,8 @@ export class UserNotificationModel extends Model {
         .then(count => count || 0),
 
       count === 0
-        ? []
-        : UserNotificationModel.scope(ScopeNames.WITH_ALL).findAll(query)
+        ? [] as UserNotificationModelForApi[]
+        : new UserNotificationListQueryBuilder(this.sequelize, query).listNotifications()
     ]).then(([ total, data ]) => ({ total, data }))
   }
 
@@ -468,7 +306,7 @@ export class UserNotificationModel extends Model {
   }
 
   static removeNotificationsOf (options: { id: number, type: 'account' | 'server', forUserId?: number }) {
-    const id = parseInt(options.id + '', 10)
+    const id = forceNumber(options.id)
 
     function buildAccountWhereQuery (base: string) {
       const whereSuffix = options.forUserId
@@ -522,25 +360,31 @@ export class UserNotificationModel extends Model {
 
   toFormattedJSON (this: UserNotificationModelForApi): UserNotification {
     const video = this.Video
-      ? Object.assign(this.formatVideo(this.Video), { channel: this.formatActor(this.Video.VideoChannel) })
+      ? {
+        ...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,
+        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
+    const comment = this.VideoComment
       ? {
-        id: this.Comment.id,
-        threadId: this.Comment.getThreadId(),
-        account: this.formatActor(this.Comment.Account),
-        video: this.formatVideo(this.Comment.Video)
+        id: this.VideoComment.id,
+        threadId: this.VideoComment.getThreadId(),
+        account: this.formatActor(this.VideoComment.Account),
+        video: this.formatVideo(this.VideoComment.Video)
       }
       : undefined
 
@@ -568,8 +412,9 @@ export class UserNotificationModel extends Model {
           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()
+          host: this.ActorFollow.ActorFollower.getHost(),
+
+          ...this.formatAvatars(this.ActorFollow.ActorFollower.Avatars)
         },
         following: {
           type: actorFollowingType[this.ActorFollow.ActorFollowing.type],
@@ -592,6 +437,10 @@ export class UserNotificationModel extends Model {
       ? { latestVersion: this.Application.latestPeerTubeVersion }
       : undefined
 
+    const registration = this.UserRegistration
+      ? { id: this.UserRegistration.id, username: this.UserRegistration.username }
+      : undefined
+
     return {
       id: this.id,
       type: this.type,
@@ -605,20 +454,22 @@ export class UserNotificationModel extends Model {
       actorFollow,
       plugin,
       peertube,
+      registration,
       createdAt: this.createdAt.toISOString(),
       updatedAt: this.updatedAt.toISOString()
     }
   }
 
-  formatVideo (this: UserNotificationModelForApi, video: UserNotificationIncludes.VideoInclude) {
+  formatVideo (video: UserNotificationIncludes.VideoInclude) {
     return {
       id: video.id,
       uuid: video.uuid,
+      shortUUID: uuidToShort(video.uuid),
       name: video.name
     }
   }
 
-  formatAbuse (this: UserNotificationModelForApi, abuse: UserNotificationIncludes.AbuseInclude) {
+  formatAbuse (abuse: UserNotificationIncludes.AbuseInclude) {
     const commentAbuse = abuse.VideoCommentAbuse?.VideoComment
       ? {
         threadId: abuse.VideoCommentAbuse.VideoComment.getThreadId(),
@@ -627,15 +478,20 @@ export class UserNotificationModel extends Model {
           ? {
             id: abuse.VideoCommentAbuse.VideoComment.Video.id,
             name: abuse.VideoCommentAbuse.VideoComment.Video.name,
+            shortUUID: uuidToShort(abuse.VideoCommentAbuse.VideoComment.Video.uuid),
             uuid: abuse.VideoCommentAbuse.VideoComment.Video.uuid
           }
           : undefined
       }
       : undefined
 
-    const videoAbuse = abuse.VideoAbuse?.Video ? this.formatVideo(abuse.VideoAbuse.Video) : undefined
+    const videoAbuse = abuse.VideoAbuse?.Video
+      ? this.formatVideo(abuse.VideoAbuse.Video)
+      : undefined
 
-    const accountAbuse = (!commentAbuse && !videoAbuse && abuse.FlaggedAccount) ? this.formatActor(abuse.FlaggedAccount) : undefined
+    const accountAbuse = (!commentAbuse && !videoAbuse && abuse.FlaggedAccount)
+      ? this.formatActor(abuse.FlaggedAccount)
+      : undefined
 
     return {
       id: abuse.id,
@@ -647,19 +503,32 @@ export class UserNotificationModel extends Model {
   }
 
   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
+
+      ...this.formatAvatars(accountOrChannel.Actor.Avatars)
+    }
+  }
+
+  formatAvatars (avatars: UserNotificationIncludes.ActorImageInclude[]) {
+    if (!avatars || avatars.length === 0) return { avatar: undefined, avatars: [] }
+
+    return {
+      avatar: this.formatAvatar(getBiggestActorImage(avatars)),
+
+      avatars: avatars.map(a => this.formatAvatar(a))
+    }
+  }
+
+  formatAvatar (a: UserNotificationIncludes.ActorImageInclude) {
+    return {
+      path: a.getStaticPath(),
+      width: a.width
     }
   }
 }