aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/lib/notifier.ts40
-rw-r--r--server/models/account/account-blocklist.ts5
-rw-r--r--server/models/server/server-blocklist.ts26
-rw-r--r--server/tests/api/users/blocklist.ts210
4 files changed, 262 insertions, 19 deletions
diff --git a/server/lib/notifier.ts b/server/lib/notifier.ts
index b7cc2607d..679b9bcf6 100644
--- a/server/lib/notifier.ts
+++ b/server/lib/notifier.ts
@@ -17,14 +17,16 @@ import {
17 MVideoFullLight 17 MVideoFullLight
18} from '../typings/models/video' 18} from '../typings/models/video'
19import { 19import {
20 MUser, 20 MUser, MUserAccount,
21 MUserDefault, 21 MUserDefault,
22 MUserNotifSettingAccount, 22 MUserNotifSettingAccount,
23 MUserWithNotificationSetting, 23 MUserWithNotificationSetting,
24 UserNotificationModelForApi 24 UserNotificationModelForApi
25} from '@server/typings/models/user' 25} from '@server/typings/models/user'
26import { MActorFollowFull } from '../typings/models' 26import { MAccountDefault, MActorFollowFull } from '../typings/models'
27import { MVideoImportVideo } from '@server/typings/models/video/video-import' 27import { MVideoImportVideo } from '@server/typings/models/video/video-import'
28import { ServerBlocklistModel } from '@server/models/server/server-blocklist'
29import { getServerActor } from '@server/helpers/utils'
28 30
29class Notifier { 31class Notifier {
30 32
@@ -164,8 +166,7 @@ class Notifier {
164 // Not our user or user comments its own video 166 // Not our user or user comments its own video
165 if (!user || comment.Account.userId === user.id) return 167 if (!user || comment.Account.userId === user.id) return
166 168
167 const accountMuted = await AccountBlocklistModel.isAccountMutedBy(user.Account.id, comment.accountId) 169 if (await this.isBlockedByServerOrAccount(user, comment.Account)) return
168 if (accountMuted) return
169 170
170 logger.info('Notifying user %s of new comment %s.', user.username, comment.url) 171 logger.info('Notifying user %s of new comment %s.', user.username, comment.url)
171 172
@@ -210,12 +211,22 @@ class Notifier {
210 211
211 if (users.length === 0) return 212 if (users.length === 0) return
212 213
213 const accountMutedHash = await AccountBlocklistModel.isAccountMutedByMulti(users.map(u => u.Account.id), comment.accountId) 214 const serverAccountId = (await getServerActor()).Account.id
215 const sourceAccounts = users.map(u => u.Account.id).concat([ serverAccountId ])
216
217 const accountMutedHash = await AccountBlocklistModel.isAccountMutedByMulti(sourceAccounts, comment.accountId)
218 const instanceMutedHash = await ServerBlocklistModel.isServerMutedByMulti(sourceAccounts, comment.Account.Actor.serverId)
214 219
215 logger.info('Notifying %d users of new comment %s.', users.length, comment.url) 220 logger.info('Notifying %d users of new comment %s.', users.length, comment.url)
216 221
217 function settingGetter (user: MUserNotifSettingAccount) { 222 function settingGetter (user: MUserNotifSettingAccount) {
218 if (accountMutedHash[user.Account.id] === true) return UserNotificationSettingValue.NONE 223 const accountId = user.Account.id
224 if (
225 accountMutedHash[accountId] === true || instanceMutedHash[accountId] === true ||
226 accountMutedHash[serverAccountId] === true || instanceMutedHash[serverAccountId] === true
227 ) {
228 return UserNotificationSettingValue.NONE
229 }
219 230
220 return user.NotificationSetting.commentMention 231 return user.NotificationSetting.commentMention
221 } 232 }
@@ -254,9 +265,9 @@ class Notifier {
254 if (!user) return 265 if (!user) return
255 266
256 const followerAccount = actorFollow.ActorFollower.Account 267 const followerAccount = actorFollow.ActorFollower.Account
268 const followerAccountWithActor = Object.assign(followerAccount, { Actor: actorFollow.ActorFollower })
257 269
258 const accountMuted = await AccountBlocklistModel.isAccountMutedBy(user.Account.id, followerAccount.id) 270 if (await this.isBlockedByServerOrAccount(user, followerAccountWithActor)) return
259 if (accountMuted) return
260 271
261 logger.info('Notifying user %s of new follower: %s.', user.username, followerAccount.getDisplayName()) 272 logger.info('Notifying user %s of new follower: %s.', user.username, followerAccount.getDisplayName())
262 273
@@ -572,6 +583,19 @@ class Notifier {
572 return value & UserNotificationSettingValue.WEB 583 return value & UserNotificationSettingValue.WEB
573 } 584 }
574 585
586 private async isBlockedByServerOrAccount (user: MUserAccount, targetAccount: MAccountDefault) {
587 const serverAccountId = (await getServerActor()).Account.id
588 const sourceAccounts = [ serverAccountId, user.Account.id ]
589
590 const accountMutedHash = await AccountBlocklistModel.isAccountMutedByMulti(sourceAccounts, targetAccount.id)
591 if (accountMutedHash[serverAccountId] || accountMutedHash[user.Account.id]) return true
592
593 const instanceMutedHash = await ServerBlocklistModel.isServerMutedByMulti(sourceAccounts, targetAccount.Actor.serverId)
594 if (instanceMutedHash[serverAccountId] || instanceMutedHash[user.Account.id]) return true
595
596 return false
597 }
598
575 static get Instance () { 599 static get Instance () {
576 return this.instance || (this.instance = new this()) 600 return this.instance || (this.instance = new this())
577 } 601 }
diff --git a/server/models/account/account-blocklist.ts b/server/models/account/account-blocklist.ts
index 8bcaca828..6ebe32556 100644
--- a/server/models/account/account-blocklist.ts
+++ b/server/models/account/account-blocklist.ts
@@ -75,11 +75,6 @@ export class AccountBlocklistModel extends Model<AccountBlocklistModel> {
75 }) 75 })
76 BlockedAccount: AccountModel 76 BlockedAccount: AccountModel
77 77
78 static isAccountMutedBy (accountId: number, targetAccountId: number) {
79 return AccountBlocklistModel.isAccountMutedByMulti([ accountId ], targetAccountId)
80 .then(result => result[accountId])
81 }
82
83 static isAccountMutedByMulti (accountIds: number[], targetAccountId: number) { 78 static isAccountMutedByMulti (accountIds: number[], targetAccountId: number) {
84 const query = { 79 const query = {
85 attributes: [ 'accountId', 'id' ], 80 attributes: [ 'accountId', 'id' ],
diff --git a/server/models/server/server-blocklist.ts b/server/models/server/server-blocklist.ts
index 3e9687191..b88df4fd5 100644
--- a/server/models/server/server-blocklist.ts
+++ b/server/models/server/server-blocklist.ts
@@ -5,6 +5,7 @@ import { ServerBlock } from '../../../shared/models/blocklist'
5import { getSort } from '../utils' 5import { getSort } from '../utils'
6import * as Bluebird from 'bluebird' 6import * as Bluebird from 'bluebird'
7import { MServerBlocklist, MServerBlocklistAccountServer, MServerBlocklistFormattable } from '@server/typings/models' 7import { MServerBlocklist, MServerBlocklistAccountServer, MServerBlocklistFormattable } from '@server/typings/models'
8import { Op } from 'sequelize'
8 9
9enum ScopeNames { 10enum ScopeNames {
10 WITH_ACCOUNT = 'WITH_ACCOUNT', 11 WITH_ACCOUNT = 'WITH_ACCOUNT',
@@ -75,6 +76,31 @@ export class ServerBlocklistModel extends Model<ServerBlocklistModel> {
75 }) 76 })
76 BlockedServer: ServerModel 77 BlockedServer: ServerModel
77 78
79 static isServerMutedByMulti (accountIds: number[], targetServerId: number) {
80 const query = {
81 attributes: [ 'accountId', 'id' ],
82 where: {
83 accountId: {
84 [Op.in]: accountIds // FIXME: sequelize ANY seems broken
85 },
86 targetServerId
87 },
88 raw: true
89 }
90
91 return ServerBlocklistModel.unscoped()
92 .findAll(query)
93 .then(rows => {
94 const result: { [accountId: number]: boolean } = {}
95
96 for (const accountId of accountIds) {
97 result[accountId] = !!rows.find(r => r.accountId === accountId)
98 }
99
100 return result
101 })
102 }
103
78 static loadByAccountAndHost (accountId: number, host: string): Bluebird<MServerBlocklist> { 104 static loadByAccountAndHost (accountId: number, host: string): Bluebird<MServerBlocklist> {
79 const query = { 105 const query = {
80 where: { 106 where: {
diff --git a/server/tests/api/users/blocklist.ts b/server/tests/api/users/blocklist.ts
index c25e85ada..09e72d068 100644
--- a/server/tests/api/users/blocklist.ts
+++ b/server/tests/api/users/blocklist.ts
@@ -2,10 +2,10 @@
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
5import { AccountBlock, ServerBlock, Video } from '../../../../shared/index' 5import { AccountBlock, ServerBlock, UserNotificationType, Video } from '../../../../shared/index'
6import { 6import {
7 cleanupTests, 7 cleanupTests,
8 createUser, 8 createUser, deleteVideoComment,
9 doubleFollow, 9 doubleFollow,
10 flushAndRunMultipleServers, 10 flushAndRunMultipleServers,
11 flushTests, 11 flushTests,
@@ -38,6 +38,7 @@ import {
38 removeServerFromAccountBlocklist, 38 removeServerFromAccountBlocklist,
39 removeServerFromServerBlocklist 39 removeServerFromServerBlocklist
40} from '../../../../shared/extra-utils/users/blocklist' 40} from '../../../../shared/extra-utils/users/blocklist'
41import { getUserNotifications } from '@shared/extra-utils/users/user-notifications'
41 42
42const expect = chai.expect 43const expect = chai.expect
43 44
@@ -56,9 +57,10 @@ async function checkAllVideos (url: string, token: string) {
56} 57}
57 58
58async function checkAllComments (url: string, token: string, videoUUID: string) { 59async function checkAllComments (url: string, token: string, videoUUID: string) {
59 const resThreads = await getVideoCommentThreads(url, videoUUID, 0, 5, '-createdAt', token) 60 const resThreads = await getVideoCommentThreads(url, videoUUID, 0, 25, '-createdAt', token)
60 61
61 const threads: VideoComment[] = resThreads.body.data 62 const allThreads: VideoComment[] = resThreads.body.data
63 const threads = allThreads.filter(t => t.isDeleted === false)
62 expect(threads).to.have.lengthOf(2) 64 expect(threads).to.have.lengthOf(2)
63 65
64 for (const thread of threads) { 66 for (const thread of threads) {
@@ -69,6 +71,28 @@ async function checkAllComments (url: string, token: string, videoUUID: string)
69 } 71 }
70} 72}
71 73
74async function checkCommentNotification (
75 mainServer: ServerInfo,
76 comment: { server: ServerInfo, token: string, videoUUID: string, text: string },
77 check: 'presence' | 'absence'
78) {
79 const resComment = await addVideoCommentThread(comment.server.url, comment.token, comment.videoUUID, comment.text)
80 const threadId = resComment.body.comment.id
81
82 await waitJobs([ mainServer, comment.server])
83
84 const res = await getUserNotifications(mainServer.url, mainServer.accessToken, 0, 30)
85 const commentNotifications = res.body.data
86 .filter(n => n.comment && n.comment.id === threadId)
87
88 if (check === 'presence') expect(commentNotifications).to.have.lengthOf(1)
89 else expect(commentNotifications).to.have.lengthOf(0)
90
91 await deleteVideoComment(comment.server.url, comment.token, comment.videoUUID, threadId)
92
93 await waitJobs([ mainServer, comment.server])
94}
95
72describe('Test blocklist', function () { 96describe('Test blocklist', function () {
73 let servers: ServerInfo[] 97 let servers: ServerInfo[]
74 let videoUUID1: string 98 let videoUUID1: string
@@ -189,6 +213,25 @@ describe('Test blocklist', function () {
189 } 213 }
190 }) 214 })
191 215
216 it('Should not have notifications from blocked accounts', async function () {
217 this.timeout(20000)
218
219 {
220 const comment = { server: servers[ 0 ], token: userToken1, videoUUID: videoUUID1, text: 'hidden comment' }
221 await checkCommentNotification(servers[ 0 ], comment, 'absence')
222 }
223
224 {
225 const comment = {
226 server: servers[ 0 ],
227 token: userToken1,
228 videoUUID: videoUUID2,
229 text: 'hello @root@localhost:' + servers[ 0 ].port
230 }
231 await checkCommentNotification(servers[ 0 ], comment, 'absence')
232 }
233 })
234
192 it('Should list all the videos with another user', async function () { 235 it('Should list all the videos with another user', async function () {
193 return checkAllVideos(servers[ 0 ].url, userToken1) 236 return checkAllVideos(servers[ 0 ].url, userToken1)
194 }) 237 })
@@ -248,6 +291,25 @@ describe('Test blocklist', function () {
248 it('Should display its comments', function () { 291 it('Should display its comments', function () {
249 return checkAllComments(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1) 292 return checkAllComments(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1)
250 }) 293 })
294
295 it('Should have a notification from a non blocked account', async function () {
296 this.timeout(20000)
297
298 {
299 const comment = { server: servers[ 1 ], token: userToken2, videoUUID: videoUUID1, text: 'displayed comment' }
300 await checkCommentNotification(servers[ 0 ], comment, 'presence')
301 }
302
303 {
304 const comment = {
305 server: servers[ 0 ],
306 token: userToken1,
307 videoUUID: videoUUID2,
308 text: 'hello @root@localhost:' + servers[ 0 ].port
309 }
310 await checkCommentNotification(servers[ 0 ], comment, 'presence')
311 }
312 })
251 }) 313 })
252 314
253 describe('When managing server blocklist', function () { 315 describe('When managing server blocklist', function () {
@@ -280,7 +342,37 @@ describe('Test blocklist', function () {
280 return checkAllVideos(servers[ 0 ].url, userToken1) 342 return checkAllVideos(servers[ 0 ].url, userToken1)
281 }) 343 })
282 344
283 it('Should hide its comments') 345 it('Should hide its comments', async function () {
346 this.timeout(10000)
347
348 const resThreads = await addVideoCommentThread(servers[ 1 ].url, userToken2, videoUUID1, 'hidden comment 2')
349 const threadId = resThreads.body.comment.id
350
351 await waitJobs(servers)
352
353 await checkAllComments(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1)
354
355 await deleteVideoComment(servers[ 1 ].url, userToken2, videoUUID1, threadId)
356 })
357
358 it('Should not have notifications from blocked server', async function () {
359 this.timeout(20000)
360
361 {
362 const comment = { server: servers[ 1 ], token: userToken2, videoUUID: videoUUID1, text: 'hidden comment' }
363 await checkCommentNotification(servers[ 0 ], comment, 'absence')
364 }
365
366 {
367 const comment = {
368 server: servers[ 1 ],
369 token: userToken2,
370 videoUUID: videoUUID1,
371 text: 'hello @root@localhost:' + servers[ 0 ].port
372 }
373 await checkCommentNotification(servers[ 0 ], comment, 'absence')
374 }
375 })
284 376
285 it('Should list blocked servers', async function () { 377 it('Should list blocked servers', async function () {
286 const res = await getServerBlocklistByAccount(servers[ 0 ].url, servers[ 0 ].accessToken, 0, 1, 'createdAt') 378 const res = await getServerBlocklistByAccount(servers[ 0 ].url, servers[ 0 ].accessToken, 0, 1, 'createdAt')
@@ -305,6 +397,25 @@ describe('Test blocklist', function () {
305 it('Should display its comments', function () { 397 it('Should display its comments', function () {
306 return checkAllComments(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1) 398 return checkAllComments(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1)
307 }) 399 })
400
401 it('Should have notification from unblocked server', async function () {
402 this.timeout(20000)
403
404 {
405 const comment = { server: servers[ 1 ], token: userToken2, videoUUID: videoUUID1, text: 'displayed comment' }
406 await checkCommentNotification(servers[ 0 ], comment, 'presence')
407 }
408
409 {
410 const comment = {
411 server: servers[ 1 ],
412 token: userToken2,
413 videoUUID: videoUUID1,
414 text: 'hello @root@localhost:' + servers[ 0 ].port
415 }
416 await checkCommentNotification(servers[ 0 ], comment, 'presence')
417 }
418 })
308 }) 419 })
309 }) 420 })
310 421
@@ -375,6 +486,25 @@ describe('Test blocklist', function () {
375 } 486 }
376 }) 487 })
377 488
489 it('Should not have notification from blocked accounts by instance', async function () {
490 this.timeout(20000)
491
492 {
493 const comment = { server: servers[ 0 ], token: userToken1, videoUUID: videoUUID1, text: 'hidden comment' }
494 await checkCommentNotification(servers[ 0 ], comment, 'absence')
495 }
496
497 {
498 const comment = {
499 server: servers[ 1 ],
500 token: userToken2,
501 videoUUID: videoUUID1,
502 text: 'hello @root@localhost:' + servers[ 0 ].port
503 }
504 await checkCommentNotification(servers[ 0 ], comment, 'absence')
505 }
506 })
507
378 it('Should list blocked accounts', async function () { 508 it('Should list blocked accounts', async function () {
379 { 509 {
380 const res = await getAccountBlocklistByServer(servers[ 0 ].url, servers[ 0 ].accessToken, 0, 1, 'createdAt') 510 const res = await getAccountBlocklistByServer(servers[ 0 ].url, servers[ 0 ].accessToken, 0, 1, 'createdAt')
@@ -430,6 +560,25 @@ describe('Test blocklist', function () {
430 await checkAllComments(servers[ 0 ].url, token, videoUUID1) 560 await checkAllComments(servers[ 0 ].url, token, videoUUID1)
431 } 561 }
432 }) 562 })
563
564 it('Should have notifications from unblocked accounts', async function () {
565 this.timeout(20000)
566
567 {
568 const comment = { server: servers[ 0 ], token: userToken1, videoUUID: videoUUID1, text: 'displayed comment' }
569 await checkCommentNotification(servers[ 0 ], comment, 'presence')
570 }
571
572 {
573 const comment = {
574 server: servers[ 1 ],
575 token: userToken2,
576 videoUUID: videoUUID1,
577 text: 'hello @root@localhost:' + servers[ 0 ].port
578 }
579 await checkCommentNotification(servers[ 0 ], comment, 'presence')
580 }
581 })
433 }) 582 })
434 583
435 describe('When managing server blocklist', function () { 584 describe('When managing server blocklist', function () {
@@ -467,7 +616,37 @@ describe('Test blocklist', function () {
467 } 616 }
468 }) 617 })
469 618
470 it('Should hide its comments') 619 it('Should hide its comments', async function () {
620 this.timeout(10000)
621
622 const resThreads = await addVideoCommentThread(servers[ 1 ].url, userToken2, videoUUID1, 'hidden comment 2')
623 const threadId = resThreads.body.comment.id
624
625 await waitJobs(servers)
626
627 await checkAllComments(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1)
628
629 await deleteVideoComment(servers[ 1 ].url, userToken2, videoUUID1, threadId)
630 })
631
632 it('Should not have notification from blocked instances by instance', async function () {
633 this.timeout(20000)
634
635 {
636 const comment = { server: servers[ 1 ], token: userToken2, videoUUID: videoUUID1, text: 'hidden comment' }
637 await checkCommentNotification(servers[ 0 ], comment, 'absence')
638 }
639
640 {
641 const comment = {
642 server: servers[ 1 ],
643 token: userToken2,
644 videoUUID: videoUUID1,
645 text: 'hello @root@localhost:' + servers[ 0 ].port
646 }
647 await checkCommentNotification(servers[ 0 ], comment, 'absence')
648 }
649 })
471 650
472 it('Should list blocked servers', async function () { 651 it('Should list blocked servers', async function () {
473 const res = await getServerBlocklistByServer(servers[ 0 ].url, servers[ 0 ].accessToken, 0, 1, 'createdAt') 652 const res = await getServerBlocklistByServer(servers[ 0 ].url, servers[ 0 ].accessToken, 0, 1, 'createdAt')
@@ -496,6 +675,25 @@ describe('Test blocklist', function () {
496 await checkAllComments(servers[ 0 ].url, token, videoUUID1) 675 await checkAllComments(servers[ 0 ].url, token, videoUUID1)
497 } 676 }
498 }) 677 })
678
679 it('Should have notification from unblocked instances', async function () {
680 this.timeout(20000)
681
682 {
683 const comment = { server: servers[ 1 ], token: userToken2, videoUUID: videoUUID1, text: 'displayed comment' }
684 await checkCommentNotification(servers[ 0 ], comment, 'presence')
685 }
686
687 {
688 const comment = {
689 server: servers[ 1 ],
690 token: userToken2,
691 videoUUID: videoUUID1,
692 text: 'hello @root@localhost:' + servers[ 0 ].port
693 }
694 await checkCommentNotification(servers[ 0 ], comment, 'presence')
695 }
696 })
499 }) 697 })
500 }) 698 })
501 699