X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=server%2Fmodels%2Fuser%2Fuser-notification.ts;h=667ee7f5f8e6e917d9157320e18e8190eed7930d;hb=2f63f629add5d24f8c01f309c7cae43b667b0c2a;hp=f7f9ac867c5e240abe7e6fb6f7df3b28039e30e0;hpb=7d9ba5c08999c6482f0bc5e0c09c6f55b7724090;p=github%2FChocobozzz%2FPeerTube.git diff --git a/server/models/user/user-notification.ts b/server/models/user/user-notification.ts index f7f9ac867..667ee7f5f 100644 --- a/server/models/user/user-notification.ts +++ b/server/models/user/user-notification.ts @@ -1,211 +1,27 @@ -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>> { @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 } } }