X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=server%2Fmodels%2Faccount%2Fuser-notification.ts;h=25c5232032132ea977217697b4553456b0c49d3a;hb=32a18cbf33a7cdbbe3d4885d32e4b67e19cdc1cf;hp=5a725187a2918f1cb5ed36df1e223379e4d686e3;hpb=610d0be13b3d01f653ef269271dd667a57c85ef2;p=github%2FChocobozzz%2FPeerTube.git diff --git a/server/models/account/user-notification.ts b/server/models/account/user-notification.ts index 5a725187a..25c523203 100644 --- a/server/models/account/user-notification.ts +++ b/server/models/account/user-notification.ts @@ -1,22 +1,26 @@ +import { FindOptions, ModelIndexesOptions, Op, WhereOptions } from 'sequelize' import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' +import { UserNotificationIncludes, UserNotificationModelForApi } from '@server/types/models/user' import { UserNotification, UserNotificationType } from '../../../shared' -import { getSort, throwIfNotValid } from '../utils' import { isBooleanValid } from '../../helpers/custom-validators/misc' import { isUserNotificationTypeValid } from '../../helpers/custom-validators/user-notifications' -import { UserModel } from './user' -import { VideoModel } from '../video/video' -import { VideoCommentModel } from '../video/video-comment' -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 { AbuseModel } from '../abuse/abuse' +import { VideoAbuseModel } from '../abuse/video-abuse' +import { VideoCommentAbuseModel } from '../abuse/video-comment-abuse' import { ActorModel } from '../activitypub/actor' import { ActorFollowModel } from '../activitypub/actor-follow' +import { ApplicationModel } from '../application/application' import { AvatarModel } from '../avatar/avatar' +import { PluginModel } from '../server/plugin' import { ServerModel } from '../server/server' -import { UserNotificationIncludes, UserNotificationModelForApi } from '@server/typings/models/user' +import { getSort, throwIfNotValid } from '../utils' +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 { AccountModel } from './account' +import { UserModel } from './user' enum ScopeNames { WITH_ALL = 'WITH_ALL' @@ -86,10 +90,42 @@ function buildAccountInclude (required: boolean, withActor = false) { }, { - attributes: [ 'id' ], - model: VideoAbuseModel.unscoped(), + attributes: [ 'id', 'state' ], + model: AbuseModel.unscoped(), required: false, - include: [ buildVideoInclude(true) ] + 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() ] + } + ] }, { @@ -106,6 +142,18 @@ function buildAccountInclude (required: boolean, withActor = 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(), @@ -179,9 +227,9 @@ function buildAccountInclude (required: boolean, withActor = false) { } }, { - fields: [ 'videoAbuseId' ], + fields: [ 'abuseId' ], where: { - videoAbuseId: { + abuseId: { [Op.ne]: null } } @@ -217,10 +265,26 @@ function buildAccountInclude (required: boolean, withActor = false) { [Op.ne]: null } } + }, + { + fields: [ 'pluginId' ], + where: { + pluginId: { + [Op.ne]: null + } + } + }, + { + fields: [ 'applicationId' ], + where: { + applicationId: { + [Op.ne]: null + } + } } ] as (ModelIndexesOptions & { where?: WhereOptions })[] }) -export class UserNotificationModel extends Model { +export class UserNotificationModel extends Model { @AllowNull(false) @Default(null) @@ -276,17 +340,17 @@ export class UserNotificationModel extends Model { }) Comment: VideoCommentModel - @ForeignKey(() => VideoAbuseModel) + @ForeignKey(() => AbuseModel) @Column - videoAbuseId: number + abuseId: number - @BelongsTo(() => VideoAbuseModel, { + @BelongsTo(() => AbuseModel, { foreignKey: { allowNull: true }, onDelete: 'cascade' }) - VideoAbuse: VideoAbuseModel + Abuse: AbuseModel @ForeignKey(() => VideoBlacklistModel) @Column @@ -336,6 +400,30 @@ export class UserNotificationModel extends Model { }) ActorFollow: ActorFollowModel + @ForeignKey(() => PluginModel) + @Column + pluginId: number + + @BelongsTo(() => PluginModel, { + foreignKey: { + allowNull: true + }, + onDelete: 'cascade' + }) + Plugin: PluginModel + + @ForeignKey(() => ApplicationModel) + @Column + applicationId: number + + @BelongsTo(() => ApplicationModel, { + foreignKey: { + allowNull: true + }, + onDelete: 'cascade' + }) + Application: ApplicationModel + static listForApi (userId: number, start: number, count: number, sort: string, unread?: boolean) { const where = { userId } @@ -377,35 +465,91 @@ export class UserNotificationModel extends Model { return UserNotificationModel.update({ read: true }, query) } + static removeNotificationsOf (options: { id: number, type: 'account' | 'server', forUserId?: number }) { + const id = parseInt(options.id + '', 10) + + function buildAccountWhereQuery (base: string) { + const whereSuffix = options.forUserId + ? ` AND "userNotification"."userId" = ${options.forUserId}` + : '' + + if (options.type === 'account') { + return base + + ` WHERE "account"."id" = ${id} ${whereSuffix}` + } + + return base + + ` WHERE "actor"."serverId" = ${id} ${whereSuffix}` + } + + const queries = [ + buildAccountWhereQuery( + `SELECT "userNotification"."id" FROM "userNotification" ` + + `INNER JOIN "account" ON "userNotification"."accountId" = "account"."id" ` + + `INNER JOIN actor ON "actor"."id" = "account"."actorId" ` + ), + + // Remove notifications from muted accounts that followed ours + buildAccountWhereQuery( + `SELECT "userNotification"."id" FROM "userNotification" ` + + `INNER JOIN "actorFollow" ON "actorFollow".id = "userNotification"."actorFollowId" ` + + `INNER JOIN actor ON actor.id = "actorFollow"."actorId" ` + + `INNER JOIN account ON account."actorId" = actor.id ` + ), + + // Remove notifications from muted accounts that commented something + buildAccountWhereQuery( + `SELECT "userNotification"."id" FROM "userNotification" ` + + `INNER JOIN "actorFollow" ON "actorFollow".id = "userNotification"."actorFollowId" ` + + `INNER JOIN actor ON actor.id = "actorFollow"."actorId" ` + + `INNER JOIN account ON account."actorId" = actor.id ` + ), + + buildAccountWhereQuery( + `SELECT "userNotification"."id" FROM "userNotification" ` + + `INNER JOIN "videoComment" ON "videoComment".id = "userNotification"."commentId" ` + + `INNER JOIN account ON account.id = "videoComment"."accountId" ` + + `INNER JOIN actor ON "actor"."id" = "account"."actorId" ` + ) + ] + + const query = `DELETE FROM "userNotification" WHERE id IN (${queries.join(' UNION ')})` + + return UserNotificationModel.sequelize.query(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, - 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: this.formatVideo(this.VideoAbuse.Video) - } : undefined - - const videoBlacklist = this.VideoBlacklist ? { - id: this.VideoBlacklist.id, - video: this.formatVideo(this.VideoBlacklist.Video) - } : 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, + threadId: this.Comment.getThreadId(), + account: this.formatActor(this.Comment.Account), + video: this.formatVideo(this.Comment.Video) + } + : undefined + + const abuse = this.Abuse ? this.formatAbuse(this.Abuse) : undefined + + const videoBlacklist = this.VideoBlacklist + ? { + id: this.VideoBlacklist.id, + video: this.formatVideo(this.VideoBlacklist.Video) + } + : undefined const account = this.Account ? this.formatActor(this.Account) : undefined @@ -414,23 +558,37 @@ export class UserNotificationModel extends Model { 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() + 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 + : undefined + + const plugin = this.Plugin + ? { + name: this.Plugin.name, + type: this.Plugin.type, + latestVersion: this.Plugin.latestVersion + } + : undefined + + const peertube = this.Application + ? { latestVersion: this.Application.latestPeerTubeVersion } + : undefined return { id: this.id, @@ -439,10 +597,12 @@ export class UserNotificationModel extends Model { video, videoImport, comment, - videoAbuse, + abuse, videoBlacklist, account, actorFollow, + plugin, + peertube, createdAt: this.createdAt.toISOString(), updatedAt: this.updatedAt.toISOString() } @@ -456,6 +616,34 @@ export class UserNotificationModel extends Model { } } + formatAbuse (this: UserNotificationModelForApi, abuse: UserNotificationIncludes.AbuseInclude) { + const commentAbuse = abuse.VideoCommentAbuse?.VideoComment + ? { + threadId: abuse.VideoCommentAbuse.VideoComment.getThreadId(), + + video: abuse.VideoCommentAbuse.VideoComment.Video + ? { + id: abuse.VideoCommentAbuse.VideoComment.Video.id, + name: abuse.VideoCommentAbuse.VideoComment.Video.name, + uuid: abuse.VideoCommentAbuse.VideoComment.Video.uuid + } + : undefined + } + : undefined + + const videoAbuse = abuse.VideoAbuse?.Video ? this.formatVideo(abuse.VideoAbuse.Video) : undefined + + const accountAbuse = (!commentAbuse && !videoAbuse && abuse.FlaggedAccount) ? this.formatActor(abuse.FlaggedAccount) : undefined + + return { + id: abuse.id, + state: abuse.state, + video: videoAbuse, + comment: commentAbuse, + account: accountAbuse + } + } + formatActor ( this: UserNotificationModelForApi, accountOrChannel: UserNotificationIncludes.AccountIncludeActor | UserNotificationIncludes.VideoChannelIncludeActor