Remove notifications of muted accounts/servers
This commit is contained in:
parent
aa9cf3b98d
commit
ea3674d04d
|
@ -1,6 +1,11 @@
|
||||||
import * as express from 'express'
|
|
||||||
import 'multer'
|
import 'multer'
|
||||||
|
import * as express from 'express'
|
||||||
|
import { logger } from '@server/helpers/logger'
|
||||||
|
import { UserNotificationModel } from '@server/models/account/user-notification'
|
||||||
|
import { getServerActor } from '@server/models/application/application'
|
||||||
|
import { UserRight } from '../../../../shared/models/users'
|
||||||
import { getFormattedObjects } from '../../../helpers/utils'
|
import { getFormattedObjects } from '../../../helpers/utils'
|
||||||
|
import { addAccountInBlocklist, addServerInBlocklist, removeAccountFromBlocklist, removeServerFromBlocklist } from '../../../lib/blocklist'
|
||||||
import {
|
import {
|
||||||
asyncMiddleware,
|
asyncMiddleware,
|
||||||
asyncRetryTransactionMiddleware,
|
asyncRetryTransactionMiddleware,
|
||||||
|
@ -19,10 +24,7 @@ import {
|
||||||
unblockServerByServerValidator
|
unblockServerByServerValidator
|
||||||
} from '../../../middlewares/validators'
|
} from '../../../middlewares/validators'
|
||||||
import { AccountBlocklistModel } from '../../../models/account/account-blocklist'
|
import { AccountBlocklistModel } from '../../../models/account/account-blocklist'
|
||||||
import { addAccountInBlocklist, addServerInBlocklist, removeAccountFromBlocklist, removeServerFromBlocklist } from '../../../lib/blocklist'
|
|
||||||
import { ServerBlocklistModel } from '../../../models/server/server-blocklist'
|
import { ServerBlocklistModel } from '../../../models/server/server-blocklist'
|
||||||
import { UserRight } from '../../../../shared/models/users'
|
|
||||||
import { getServerActor } from '@server/models/application/application'
|
|
||||||
|
|
||||||
const serverBlocklistRouter = express.Router()
|
const serverBlocklistRouter = express.Router()
|
||||||
|
|
||||||
|
@ -100,6 +102,12 @@ async function blockAccount (req: express.Request, res: express.Response) {
|
||||||
|
|
||||||
await addAccountInBlocklist(serverActor.Account.id, accountToBlock.id)
|
await addAccountInBlocklist(serverActor.Account.id, accountToBlock.id)
|
||||||
|
|
||||||
|
UserNotificationModel.removeNotificationsOf({
|
||||||
|
id: accountToBlock.id,
|
||||||
|
type: 'account',
|
||||||
|
forUserId: null // For all users
|
||||||
|
}).catch(err => logger.error('Cannot remove notifications after an account mute.', { err }))
|
||||||
|
|
||||||
return res.status(204).end()
|
return res.status(204).end()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,6 +139,12 @@ async function blockServer (req: express.Request, res: express.Response) {
|
||||||
|
|
||||||
await addServerInBlocklist(serverActor.Account.id, serverToBlock.id)
|
await addServerInBlocklist(serverActor.Account.id, serverToBlock.id)
|
||||||
|
|
||||||
|
UserNotificationModel.removeNotificationsOf({
|
||||||
|
id: serverToBlock.id,
|
||||||
|
type: 'server',
|
||||||
|
forUserId: null // For all users
|
||||||
|
}).catch(err => logger.error('Cannot remove notifications after a server mute.', { err }))
|
||||||
|
|
||||||
return res.status(204).end()
|
return res.status(204).end()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,8 @@ import {
|
||||||
import { AccountBlocklistModel } from '../../../models/account/account-blocklist'
|
import { AccountBlocklistModel } from '../../../models/account/account-blocklist'
|
||||||
import { addAccountInBlocklist, addServerInBlocklist, removeAccountFromBlocklist, removeServerFromBlocklist } from '../../../lib/blocklist'
|
import { addAccountInBlocklist, addServerInBlocklist, removeAccountFromBlocklist, removeServerFromBlocklist } from '../../../lib/blocklist'
|
||||||
import { ServerBlocklistModel } from '../../../models/server/server-blocklist'
|
import { ServerBlocklistModel } from '../../../models/server/server-blocklist'
|
||||||
|
import { UserNotificationModel } from '@server/models/account/user-notification'
|
||||||
|
import { logger } from '@server/helpers/logger'
|
||||||
|
|
||||||
const myBlocklistRouter = express.Router()
|
const myBlocklistRouter = express.Router()
|
||||||
|
|
||||||
|
@ -91,6 +93,12 @@ async function blockAccount (req: express.Request, res: express.Response) {
|
||||||
|
|
||||||
await addAccountInBlocklist(user.Account.id, accountToBlock.id)
|
await addAccountInBlocklist(user.Account.id, accountToBlock.id)
|
||||||
|
|
||||||
|
UserNotificationModel.removeNotificationsOf({
|
||||||
|
id: accountToBlock.id,
|
||||||
|
type: 'account',
|
||||||
|
forUserId: user.id
|
||||||
|
}).catch(err => logger.error('Cannot remove notifications after an account mute.', { err }))
|
||||||
|
|
||||||
return res.status(204).end()
|
return res.status(204).end()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,6 +130,12 @@ async function blockServer (req: express.Request, res: express.Response) {
|
||||||
|
|
||||||
await addServerInBlocklist(user.Account.id, serverToBlock.id)
|
await addServerInBlocklist(user.Account.id, serverToBlock.id)
|
||||||
|
|
||||||
|
UserNotificationModel.removeNotificationsOf({
|
||||||
|
id: serverToBlock.id,
|
||||||
|
type: 'server',
|
||||||
|
forUserId: user.id
|
||||||
|
}).catch(err => logger.error('Cannot remove notifications after a server mute.', { err }))
|
||||||
|
|
||||||
return res.status(204).end()
|
return res.status(204).end()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ import {
|
||||||
} from '../../../middlewares/validators'
|
} from '../../../middlewares/validators'
|
||||||
import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
|
import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
|
||||||
import { VideoModel } from '../../../models/video/video'
|
import { VideoModel } from '../../../models/video/video'
|
||||||
|
import { sendUndoFollow } from '@server/lib/activitypub/send'
|
||||||
|
|
||||||
const mySubscriptionsRouter = express.Router()
|
const mySubscriptionsRouter = express.Router()
|
||||||
|
|
||||||
|
@ -138,6 +139,8 @@ async function deleteUserSubscription (req: express.Request, res: express.Respon
|
||||||
const subscription = res.locals.subscription
|
const subscription = res.locals.subscription
|
||||||
|
|
||||||
await sequelizeTypescript.transaction(async t => {
|
await sequelizeTypescript.transaction(async t => {
|
||||||
|
if (subscription.state === 'accepted') await sendUndoFollow(subscription, t)
|
||||||
|
|
||||||
return subscription.destroy({ transaction: t })
|
return subscription.destroy({ transaction: t })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -105,7 +105,7 @@ function buildAccountInclude (required: boolean, withActor = false) {
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
attributes: [ 'id', 'originCommentId' ],
|
attributes: [ 'id', 'originCommentId' ],
|
||||||
model: VideoCommentModel,
|
model: VideoCommentModel.unscoped(),
|
||||||
required: true,
|
required: true,
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
|
@ -411,6 +411,59 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
|
||||||
return UserNotificationModel.update({ read: true }, query)
|
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 {
|
toFormattedJSON (this: UserNotificationModelForApi): UserNotification {
|
||||||
const video = this.Video
|
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) })
|
||||||
|
|
|
@ -0,0 +1,258 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
|
||||||
|
|
||||||
|
import 'mocha'
|
||||||
|
import * as chai from 'chai'
|
||||||
|
import { getUserNotifications, markAsReadAllNotifications } from '@shared/extra-utils/users/user-notifications'
|
||||||
|
import { addUserSubscription, removeUserSubscription } from '@shared/extra-utils/users/user-subscriptions'
|
||||||
|
import { UserNotification, UserNotificationType } from '@shared/models'
|
||||||
|
import {
|
||||||
|
cleanupTests,
|
||||||
|
createUser,
|
||||||
|
doubleFollow,
|
||||||
|
flushAndRunMultipleServers,
|
||||||
|
ServerInfo,
|
||||||
|
uploadVideo,
|
||||||
|
userLogin
|
||||||
|
} from '../../../../shared/extra-utils/index'
|
||||||
|
import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
|
||||||
|
import {
|
||||||
|
addAccountToAccountBlocklist,
|
||||||
|
addAccountToServerBlocklist,
|
||||||
|
addServerToAccountBlocklist,
|
||||||
|
addServerToServerBlocklist,
|
||||||
|
removeAccountFromAccountBlocklist,
|
||||||
|
removeAccountFromServerBlocklist,
|
||||||
|
removeServerFromAccountBlocklist
|
||||||
|
} from '../../../../shared/extra-utils/users/blocklist'
|
||||||
|
import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/login'
|
||||||
|
import { addVideoCommentThread } from '../../../../shared/extra-utils/videos/video-comments'
|
||||||
|
|
||||||
|
const expect = chai.expect
|
||||||
|
|
||||||
|
async function checkNotifications (url: string, token: string, expected: UserNotificationType[]) {
|
||||||
|
const res = await getUserNotifications(url, token, 0, 10, true)
|
||||||
|
|
||||||
|
const notifications: UserNotification[] = res.body.data
|
||||||
|
|
||||||
|
expect(notifications).to.have.lengthOf(expected.length)
|
||||||
|
|
||||||
|
for (const type of expected) {
|
||||||
|
expect(notifications.find(n => n.type === type)).to.exist
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Test blocklist', function () {
|
||||||
|
let servers: ServerInfo[]
|
||||||
|
let videoUUID: string
|
||||||
|
|
||||||
|
let userToken1: string
|
||||||
|
let userToken2: string
|
||||||
|
let remoteUserToken: string
|
||||||
|
|
||||||
|
async function resetState () {
|
||||||
|
try {
|
||||||
|
await removeUserSubscription(servers[1].url, remoteUserToken, 'user1_channel@' + servers[0].host)
|
||||||
|
await removeUserSubscription(servers[1].url, remoteUserToken, 'user2_channel@' + servers[0].host)
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
await waitJobs(servers)
|
||||||
|
|
||||||
|
await markAsReadAllNotifications(servers[0].url, userToken1)
|
||||||
|
await markAsReadAllNotifications(servers[0].url, userToken2)
|
||||||
|
|
||||||
|
{
|
||||||
|
const res = await uploadVideo(servers[0].url, userToken1, { name: 'video' })
|
||||||
|
videoUUID = res.body.video.uuid
|
||||||
|
|
||||||
|
await waitJobs(servers)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
await addVideoCommentThread(servers[1].url, remoteUserToken, videoUUID, '@user2@' + servers[0].host + ' hello')
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
await addUserSubscription(servers[1].url, remoteUserToken, 'user1_channel@' + servers[0].host)
|
||||||
|
await addUserSubscription(servers[1].url, remoteUserToken, 'user2_channel@' + servers[0].host)
|
||||||
|
}
|
||||||
|
|
||||||
|
await waitJobs(servers)
|
||||||
|
}
|
||||||
|
|
||||||
|
before(async function () {
|
||||||
|
this.timeout(60000)
|
||||||
|
|
||||||
|
servers = await flushAndRunMultipleServers(2)
|
||||||
|
await setAccessTokensToServers(servers)
|
||||||
|
|
||||||
|
{
|
||||||
|
const user = { username: 'user1', password: 'password' }
|
||||||
|
await createUser({
|
||||||
|
url: servers[0].url,
|
||||||
|
accessToken: servers[0].accessToken,
|
||||||
|
username: user.username,
|
||||||
|
password: user.password,
|
||||||
|
videoQuota: -1,
|
||||||
|
videoQuotaDaily: -1
|
||||||
|
})
|
||||||
|
|
||||||
|
userToken1 = await userLogin(servers[0], user)
|
||||||
|
await uploadVideo(servers[0].url, userToken1, { name: 'video user 1' })
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const user = { username: 'user2', password: 'password' }
|
||||||
|
await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: user.username, password: user.password })
|
||||||
|
|
||||||
|
userToken2 = await userLogin(servers[0], user)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const user = { username: 'user3', password: 'password' }
|
||||||
|
await createUser({ url: servers[1].url, accessToken: servers[1].accessToken, username: user.username, password: user.password })
|
||||||
|
|
||||||
|
remoteUserToken = await userLogin(servers[1], user)
|
||||||
|
}
|
||||||
|
|
||||||
|
await doubleFollow(servers[0], servers[1])
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('User blocks another user', function () {
|
||||||
|
|
||||||
|
before(async function () {
|
||||||
|
this.timeout(30000)
|
||||||
|
|
||||||
|
await resetState()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should have appropriate notifications', async function () {
|
||||||
|
const notifs = [ UserNotificationType.NEW_COMMENT_ON_MY_VIDEO, UserNotificationType.NEW_FOLLOW ]
|
||||||
|
await checkNotifications(servers[0].url, userToken1, notifs)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should block an account', async function () {
|
||||||
|
this.timeout(10000)
|
||||||
|
|
||||||
|
await addAccountToAccountBlocklist(servers[0].url, userToken1, 'user3@' + servers[1].host)
|
||||||
|
await waitJobs(servers)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should not have notifications from this account', async function () {
|
||||||
|
await checkNotifications(servers[0].url, userToken1, [])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should have notifications of this account on user 2', async function () {
|
||||||
|
const notifs = [ UserNotificationType.COMMENT_MENTION, UserNotificationType.NEW_FOLLOW ]
|
||||||
|
|
||||||
|
await checkNotifications(servers[0].url, userToken2, notifs)
|
||||||
|
|
||||||
|
await removeAccountFromAccountBlocklist(servers[0].url, userToken1, 'user3@' + servers[1].host)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('User blocks another server', function () {
|
||||||
|
|
||||||
|
before(async function () {
|
||||||
|
this.timeout(30000)
|
||||||
|
|
||||||
|
await resetState()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should have appropriate notifications', async function () {
|
||||||
|
const notifs = [ UserNotificationType.NEW_COMMENT_ON_MY_VIDEO, UserNotificationType.NEW_FOLLOW ]
|
||||||
|
await checkNotifications(servers[0].url, userToken1, notifs)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should block an account', async function () {
|
||||||
|
this.timeout(10000)
|
||||||
|
|
||||||
|
await addServerToAccountBlocklist(servers[0].url, userToken1, servers[1].host)
|
||||||
|
await waitJobs(servers)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should not have notifications from this account', async function () {
|
||||||
|
await checkNotifications(servers[0].url, userToken1, [])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should have notifications of this account on user 2', async function () {
|
||||||
|
const notifs = [ UserNotificationType.COMMENT_MENTION, UserNotificationType.NEW_FOLLOW ]
|
||||||
|
|
||||||
|
await checkNotifications(servers[0].url, userToken2, notifs)
|
||||||
|
|
||||||
|
await removeServerFromAccountBlocklist(servers[0].url, userToken1, servers[1].host)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Server blocks a user', function () {
|
||||||
|
|
||||||
|
before(async function () {
|
||||||
|
this.timeout(30000)
|
||||||
|
|
||||||
|
await resetState()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should have appropriate notifications', async function () {
|
||||||
|
{
|
||||||
|
const notifs = [ UserNotificationType.NEW_COMMENT_ON_MY_VIDEO, UserNotificationType.NEW_FOLLOW ]
|
||||||
|
await checkNotifications(servers[0].url, userToken1, notifs)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const notifs = [ UserNotificationType.COMMENT_MENTION, UserNotificationType.NEW_FOLLOW ]
|
||||||
|
await checkNotifications(servers[0].url, userToken2, notifs)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should block an account', async function () {
|
||||||
|
this.timeout(10000)
|
||||||
|
|
||||||
|
await addAccountToServerBlocklist(servers[0].url, servers[0].accessToken, 'user3@' + servers[1].host)
|
||||||
|
await waitJobs(servers)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should not have notifications from this account', async function () {
|
||||||
|
await checkNotifications(servers[0].url, userToken1, [])
|
||||||
|
await checkNotifications(servers[0].url, userToken2, [])
|
||||||
|
|
||||||
|
await removeAccountFromServerBlocklist(servers[0].url, servers[0].accessToken, 'user3@' + servers[1].host)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Server blocks a server', function () {
|
||||||
|
|
||||||
|
before(async function () {
|
||||||
|
this.timeout(30000)
|
||||||
|
|
||||||
|
await resetState()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should have appropriate notifications', async function () {
|
||||||
|
{
|
||||||
|
const notifs = [ UserNotificationType.NEW_COMMENT_ON_MY_VIDEO, UserNotificationType.NEW_FOLLOW ]
|
||||||
|
await checkNotifications(servers[0].url, userToken1, notifs)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const notifs = [ UserNotificationType.COMMENT_MENTION, UserNotificationType.NEW_FOLLOW ]
|
||||||
|
await checkNotifications(servers[0].url, userToken2, notifs)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should block an account', async function () {
|
||||||
|
this.timeout(10000)
|
||||||
|
|
||||||
|
await addServerToServerBlocklist(servers[0].url, servers[0].accessToken, servers[1].host)
|
||||||
|
await waitJobs(servers)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should not have notifications from this account', async function () {
|
||||||
|
await checkNotifications(servers[0].url, userToken1, [])
|
||||||
|
await checkNotifications(servers[0].url, userToken2, [])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
after(async function () {
|
||||||
|
await cleanupTests(servers)
|
||||||
|
})
|
||||||
|
})
|
|
@ -1,3 +1,4 @@
|
||||||
export * from './abuses'
|
export * from './abuses'
|
||||||
|
export * from './blocklist-notification'
|
||||||
export * from './blocklist'
|
export * from './blocklist'
|
||||||
export * from './video-blacklist'
|
export * from './video-blacklist'
|
||||||
|
|
Loading…
Reference in New Issue