diff options
author | Chocobozzz <me@florianbigard.com> | 2021-12-06 16:53:00 +0100 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2021-12-07 09:46:01 +0100 |
commit | 80badf493afca026bc542260f353210e605a1715 (patch) | |
tree | fcecf7ebb3acdd4ce598dc71c3df4873d88ca244 /server | |
parent | 33675a4775d1e1e9dcb58e7e53f5027b81262622 (diff) | |
download | PeerTube-80badf493afca026bc542260f353210e605a1715.tar.gz PeerTube-80badf493afca026bc542260f353210e605a1715.tar.zst PeerTube-80badf493afca026bc542260f353210e605a1715.zip |
Add mute status in account and channel pages
Diffstat (limited to 'server')
-rw-r--r-- | server/controllers/api/blocklist.ts | 108 | ||||
-rw-r--r-- | server/controllers/api/index.ts | 2 | ||||
-rw-r--r-- | server/controllers/api/users/my-subscriptions.ts | 21 | ||||
-rw-r--r-- | server/helpers/actors.ts | 17 | ||||
-rw-r--r-- | server/lib/blocklist.ts | 4 | ||||
-rw-r--r-- | server/lib/notifier/shared/comment/comment-mention.ts | 4 | ||||
-rw-r--r-- | server/middlewares/validators/blocklist.ts | 29 | ||||
-rw-r--r-- | server/models/account/account-blocklist.ts | 40 | ||||
-rw-r--r-- | server/models/server/server-blocklist.ts | 19 | ||||
-rw-r--r-- | server/tests/api/check-params/blocklist.ts | 72 | ||||
-rw-r--r-- | server/tests/api/moderation/blocklist.ts | 107 |
11 files changed, 397 insertions, 26 deletions
diff --git a/server/controllers/api/blocklist.ts b/server/controllers/api/blocklist.ts new file mode 100644 index 000000000..1e936ad10 --- /dev/null +++ b/server/controllers/api/blocklist.ts | |||
@@ -0,0 +1,108 @@ | |||
1 | import express from 'express' | ||
2 | import { handleToNameAndHost } from '@server/helpers/actors' | ||
3 | import { AccountBlocklistModel } from '@server/models/account/account-blocklist' | ||
4 | import { getServerActor } from '@server/models/application/application' | ||
5 | import { ServerBlocklistModel } from '@server/models/server/server-blocklist' | ||
6 | import { MActorAccountId, MUserAccountId } from '@server/types/models' | ||
7 | import { BlockStatus } from '@shared/models' | ||
8 | import { asyncMiddleware, blocklistStatusValidator, optionalAuthenticate } from '../../middlewares' | ||
9 | import { logger } from '@server/helpers/logger' | ||
10 | |||
11 | const blocklistRouter = express.Router() | ||
12 | |||
13 | blocklistRouter.get('/status', | ||
14 | optionalAuthenticate, | ||
15 | blocklistStatusValidator, | ||
16 | asyncMiddleware(getBlocklistStatus) | ||
17 | ) | ||
18 | |||
19 | // --------------------------------------------------------------------------- | ||
20 | |||
21 | export { | ||
22 | blocklistRouter | ||
23 | } | ||
24 | |||
25 | // --------------------------------------------------------------------------- | ||
26 | |||
27 | async function getBlocklistStatus (req: express.Request, res: express.Response) { | ||
28 | const hosts = req.query.hosts as string[] | ||
29 | const accounts = req.query.accounts as string[] | ||
30 | const user = res.locals.oauth?.token.User | ||
31 | |||
32 | const serverActor = await getServerActor() | ||
33 | |||
34 | const byAccountIds = [ serverActor.Account.id ] | ||
35 | if (user) byAccountIds.push(user.Account.id) | ||
36 | |||
37 | const status: BlockStatus = { | ||
38 | accounts: {}, | ||
39 | hosts: {} | ||
40 | } | ||
41 | |||
42 | const baseOptions = { | ||
43 | byAccountIds, | ||
44 | user, | ||
45 | serverActor, | ||
46 | status | ||
47 | } | ||
48 | |||
49 | await Promise.all([ | ||
50 | populateServerBlocklistStatus({ ...baseOptions, hosts }), | ||
51 | populateAccountBlocklistStatus({ ...baseOptions, accounts }) | ||
52 | ]) | ||
53 | |||
54 | return res.json(status) | ||
55 | } | ||
56 | |||
57 | async function populateServerBlocklistStatus (options: { | ||
58 | byAccountIds: number[] | ||
59 | user?: MUserAccountId | ||
60 | serverActor: MActorAccountId | ||
61 | hosts: string[] | ||
62 | status: BlockStatus | ||
63 | }) { | ||
64 | const { byAccountIds, user, serverActor, hosts, status } = options | ||
65 | |||
66 | if (!hosts || hosts.length === 0) return | ||
67 | |||
68 | const serverBlocklistStatus = await ServerBlocklistModel.getBlockStatus(byAccountIds, hosts) | ||
69 | |||
70 | logger.debug('Got server blocklist status.', { serverBlocklistStatus, byAccountIds, hosts }) | ||
71 | |||
72 | for (const host of hosts) { | ||
73 | const block = serverBlocklistStatus.find(b => b.host === host) | ||
74 | |||
75 | status.hosts[host] = getStatus(block, serverActor, user) | ||
76 | } | ||
77 | } | ||
78 | |||
79 | async function populateAccountBlocklistStatus (options: { | ||
80 | byAccountIds: number[] | ||
81 | user?: MUserAccountId | ||
82 | serverActor: MActorAccountId | ||
83 | accounts: string[] | ||
84 | status: BlockStatus | ||
85 | }) { | ||
86 | const { byAccountIds, user, serverActor, accounts, status } = options | ||
87 | |||
88 | if (!accounts || accounts.length === 0) return | ||
89 | |||
90 | const accountBlocklistStatus = await AccountBlocklistModel.getBlockStatus(byAccountIds, accounts) | ||
91 | |||
92 | logger.debug('Got account blocklist status.', { accountBlocklistStatus, byAccountIds, accounts }) | ||
93 | |||
94 | for (const account of accounts) { | ||
95 | const sanitizedHandle = handleToNameAndHost(account) | ||
96 | |||
97 | const block = accountBlocklistStatus.find(b => b.name === sanitizedHandle.name && b.host === sanitizedHandle.host) | ||
98 | |||
99 | status.accounts[sanitizedHandle.handle] = getStatus(block, serverActor, user) | ||
100 | } | ||
101 | } | ||
102 | |||
103 | function getStatus (block: { accountId: number }, serverActor: MActorAccountId, user?: MUserAccountId) { | ||
104 | return { | ||
105 | blockedByServer: !!(block && block.accountId === serverActor.Account.id), | ||
106 | blockedByUser: !!(block && user && block.accountId === user.Account.id) | ||
107 | } | ||
108 | } | ||
diff --git a/server/controllers/api/index.ts b/server/controllers/api/index.ts index 9949b378a..5f49336b1 100644 --- a/server/controllers/api/index.ts +++ b/server/controllers/api/index.ts | |||
@@ -6,6 +6,7 @@ import { badRequest } from '../../helpers/express-utils' | |||
6 | import { CONFIG } from '../../initializers/config' | 6 | import { CONFIG } from '../../initializers/config' |
7 | import { abuseRouter } from './abuse' | 7 | import { abuseRouter } from './abuse' |
8 | import { accountsRouter } from './accounts' | 8 | import { accountsRouter } from './accounts' |
9 | import { blocklistRouter } from './blocklist' | ||
9 | import { bulkRouter } from './bulk' | 10 | import { bulkRouter } from './bulk' |
10 | import { configRouter } from './config' | 11 | import { configRouter } from './config' |
11 | import { customPageRouter } from './custom-page' | 12 | import { customPageRouter } from './custom-page' |
@@ -49,6 +50,7 @@ apiRouter.use('/search', searchRouter) | |||
49 | apiRouter.use('/overviews', overviewsRouter) | 50 | apiRouter.use('/overviews', overviewsRouter) |
50 | apiRouter.use('/plugins', pluginRouter) | 51 | apiRouter.use('/plugins', pluginRouter) |
51 | apiRouter.use('/custom-pages', customPageRouter) | 52 | apiRouter.use('/custom-pages', customPageRouter) |
53 | apiRouter.use('/blocklist', blocklistRouter) | ||
52 | apiRouter.use('/ping', pong) | 54 | apiRouter.use('/ping', pong) |
53 | apiRouter.use('/*', badRequest) | 55 | apiRouter.use('/*', badRequest) |
54 | 56 | ||
diff --git a/server/controllers/api/users/my-subscriptions.ts b/server/controllers/api/users/my-subscriptions.ts index 6799ca8c5..fb1f68635 100644 --- a/server/controllers/api/users/my-subscriptions.ts +++ b/server/controllers/api/users/my-subscriptions.ts | |||
@@ -1,5 +1,6 @@ | |||
1 | import 'multer' | 1 | import 'multer' |
2 | import express from 'express' | 2 | import express from 'express' |
3 | import { handlesToNameAndHost } from '@server/helpers/actors' | ||
3 | import { pickCommonVideoQuery } from '@server/helpers/query' | 4 | import { pickCommonVideoQuery } from '@server/helpers/query' |
4 | import { sendUndoFollow } from '@server/lib/activitypub/send' | 5 | import { sendUndoFollow } from '@server/lib/activitypub/send' |
5 | import { guessAdditionalAttributesFromQuery } from '@server/models/video/formatter/video-format-utils' | 6 | import { guessAdditionalAttributesFromQuery } from '@server/models/video/formatter/video-format-utils' |
@@ -7,7 +8,6 @@ import { VideoChannelModel } from '@server/models/video/video-channel' | |||
7 | import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes' | 8 | import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes' |
8 | import { buildNSFWFilter, getCountVideos } from '../../../helpers/express-utils' | 9 | import { buildNSFWFilter, getCountVideos } from '../../../helpers/express-utils' |
9 | import { getFormattedObjects } from '../../../helpers/utils' | 10 | import { getFormattedObjects } from '../../../helpers/utils' |
10 | import { WEBSERVER } from '../../../initializers/constants' | ||
11 | import { sequelizeTypescript } from '../../../initializers/database' | 11 | import { sequelizeTypescript } from '../../../initializers/database' |
12 | import { JobQueue } from '../../../lib/job-queue' | 12 | import { JobQueue } from '../../../lib/job-queue' |
13 | import { | 13 | import { |
@@ -89,28 +89,23 @@ async function areSubscriptionsExist (req: express.Request, res: express.Respons | |||
89 | const uris = req.query.uris as string[] | 89 | const uris = req.query.uris as string[] |
90 | const user = res.locals.oauth.token.User | 90 | const user = res.locals.oauth.token.User |
91 | 91 | ||
92 | const handles = uris.map(u => { | 92 | const sanitizedHandles = handlesToNameAndHost(uris) |
93 | let [ name, host ] = u.split('@') | ||
94 | if (host === WEBSERVER.HOST) host = null | ||
95 | 93 | ||
96 | return { name, host, uri: u } | 94 | const results = await ActorFollowModel.listSubscriptionsOf(user.Account.Actor.id, sanitizedHandles) |
97 | }) | ||
98 | |||
99 | const results = await ActorFollowModel.listSubscriptionsOf(user.Account.Actor.id, handles) | ||
100 | 95 | ||
101 | const existObject: { [id: string ]: boolean } = {} | 96 | const existObject: { [id: string ]: boolean } = {} |
102 | for (const handle of handles) { | 97 | for (const sanitizedHandle of sanitizedHandles) { |
103 | const obj = results.find(r => { | 98 | const obj = results.find(r => { |
104 | const server = r.ActorFollowing.Server | 99 | const server = r.ActorFollowing.Server |
105 | 100 | ||
106 | return r.ActorFollowing.preferredUsername === handle.name && | 101 | return r.ActorFollowing.preferredUsername === sanitizedHandle.name && |
107 | ( | 102 | ( |
108 | (!server && !handle.host) || | 103 | (!server && !sanitizedHandle.host) || |
109 | (server.host === handle.host) | 104 | (server.host === sanitizedHandle.host) |
110 | ) | 105 | ) |
111 | }) | 106 | }) |
112 | 107 | ||
113 | existObject[handle.uri] = obj !== undefined | 108 | existObject[sanitizedHandle.handle] = obj !== undefined |
114 | } | 109 | } |
115 | 110 | ||
116 | return res.json(existObject) | 111 | return res.json(existObject) |
diff --git a/server/helpers/actors.ts b/server/helpers/actors.ts new file mode 100644 index 000000000..c31fe6f8e --- /dev/null +++ b/server/helpers/actors.ts | |||
@@ -0,0 +1,17 @@ | |||
1 | import { WEBSERVER } from '@server/initializers/constants' | ||
2 | |||
3 | function handleToNameAndHost (handle: string) { | ||
4 | let [ name, host ] = handle.split('@') | ||
5 | if (host === WEBSERVER.HOST) host = null | ||
6 | |||
7 | return { name, host, handle } | ||
8 | } | ||
9 | |||
10 | function handlesToNameAndHost (handles: string[]) { | ||
11 | return handles.map(h => handleToNameAndHost(h)) | ||
12 | } | ||
13 | |||
14 | export { | ||
15 | handleToNameAndHost, | ||
16 | handlesToNameAndHost | ||
17 | } | ||
diff --git a/server/lib/blocklist.ts b/server/lib/blocklist.ts index d6b684015..98273a6ea 100644 --- a/server/lib/blocklist.ts +++ b/server/lib/blocklist.ts | |||
@@ -40,12 +40,12 @@ async function isBlockedByServerOrAccount (targetAccount: MAccountServer, userAc | |||
40 | 40 | ||
41 | if (userAccount) sourceAccounts.push(userAccount.id) | 41 | if (userAccount) sourceAccounts.push(userAccount.id) |
42 | 42 | ||
43 | const accountMutedHash = await AccountBlocklistModel.isAccountMutedByMulti(sourceAccounts, targetAccount.id) | 43 | const accountMutedHash = await AccountBlocklistModel.isAccountMutedByAccounts(sourceAccounts, targetAccount.id) |
44 | if (accountMutedHash[serverAccountId] || (userAccount && accountMutedHash[userAccount.id])) { | 44 | if (accountMutedHash[serverAccountId] || (userAccount && accountMutedHash[userAccount.id])) { |
45 | return true | 45 | return true |
46 | } | 46 | } |
47 | 47 | ||
48 | const instanceMutedHash = await ServerBlocklistModel.isServerMutedByMulti(sourceAccounts, targetAccount.Actor.serverId) | 48 | const instanceMutedHash = await ServerBlocklistModel.isServerMutedByAccounts(sourceAccounts, targetAccount.Actor.serverId) |
49 | if (instanceMutedHash[serverAccountId] || (userAccount && instanceMutedHash[userAccount.id])) { | 49 | if (instanceMutedHash[serverAccountId] || (userAccount && instanceMutedHash[userAccount.id])) { |
50 | return true | 50 | return true |
51 | } | 51 | } |
diff --git a/server/lib/notifier/shared/comment/comment-mention.ts b/server/lib/notifier/shared/comment/comment-mention.ts index 4f84d8dea..765cbaad9 100644 --- a/server/lib/notifier/shared/comment/comment-mention.ts +++ b/server/lib/notifier/shared/comment/comment-mention.ts | |||
@@ -47,8 +47,8 @@ export class CommentMention extends AbstractNotification <MCommentOwnerVideo, MU | |||
47 | 47 | ||
48 | const sourceAccounts = this.users.map(u => u.Account.id).concat([ this.serverAccountId ]) | 48 | const sourceAccounts = this.users.map(u => u.Account.id).concat([ this.serverAccountId ]) |
49 | 49 | ||
50 | this.accountMutedHash = await AccountBlocklistModel.isAccountMutedByMulti(sourceAccounts, this.payload.accountId) | 50 | this.accountMutedHash = await AccountBlocklistModel.isAccountMutedByAccounts(sourceAccounts, this.payload.accountId) |
51 | this.instanceMutedHash = await ServerBlocklistModel.isServerMutedByMulti(sourceAccounts, this.payload.Account.Actor.serverId) | 51 | this.instanceMutedHash = await ServerBlocklistModel.isServerMutedByAccounts(sourceAccounts, this.payload.Account.Actor.serverId) |
52 | } | 52 | } |
53 | 53 | ||
54 | log () { | 54 | log () { |
diff --git a/server/middlewares/validators/blocklist.ts b/server/middlewares/validators/blocklist.ts index b7749e204..12980ced4 100644 --- a/server/middlewares/validators/blocklist.ts +++ b/server/middlewares/validators/blocklist.ts | |||
@@ -1,8 +1,10 @@ | |||
1 | import express from 'express' | 1 | import express from 'express' |
2 | import { body, param } from 'express-validator' | 2 | import { body, param, query } from 'express-validator' |
3 | import { areValidActorHandles } from '@server/helpers/custom-validators/activitypub/actor' | ||
4 | import { toArray } from '@server/helpers/custom-validators/misc' | ||
3 | import { getServerActor } from '@server/models/application/application' | 5 | import { getServerActor } from '@server/models/application/application' |
4 | import { HttpStatusCode } from '../../../shared/models/http/http-error-codes' | 6 | import { HttpStatusCode } from '../../../shared/models/http/http-error-codes' |
5 | import { isHostValid } from '../../helpers/custom-validators/servers' | 7 | import { isEachUniqueHostValid, isHostValid } from '../../helpers/custom-validators/servers' |
6 | import { logger } from '../../helpers/logger' | 8 | import { logger } from '../../helpers/logger' |
7 | import { WEBSERVER } from '../../initializers/constants' | 9 | import { WEBSERVER } from '../../initializers/constants' |
8 | import { AccountBlocklistModel } from '../../models/account/account-blocklist' | 10 | import { AccountBlocklistModel } from '../../models/account/account-blocklist' |
@@ -123,6 +125,26 @@ const unblockServerByServerValidator = [ | |||
123 | } | 125 | } |
124 | ] | 126 | ] |
125 | 127 | ||
128 | const blocklistStatusValidator = [ | ||
129 | query('hosts') | ||
130 | .optional() | ||
131 | .customSanitizer(toArray) | ||
132 | .custom(isEachUniqueHostValid).withMessage('Should have a valid hosts array'), | ||
133 | |||
134 | query('accounts') | ||
135 | .optional() | ||
136 | .customSanitizer(toArray) | ||
137 | .custom(areValidActorHandles).withMessage('Should have a valid accounts array'), | ||
138 | |||
139 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
140 | logger.debug('Checking blocklistStatusValidator parameters', { query: req.query }) | ||
141 | |||
142 | if (areValidationErrors(req, res)) return | ||
143 | |||
144 | return next() | ||
145 | } | ||
146 | ] | ||
147 | |||
126 | // --------------------------------------------------------------------------- | 148 | // --------------------------------------------------------------------------- |
127 | 149 | ||
128 | export { | 150 | export { |
@@ -131,7 +153,8 @@ export { | |||
131 | unblockAccountByAccountValidator, | 153 | unblockAccountByAccountValidator, |
132 | unblockServerByAccountValidator, | 154 | unblockServerByAccountValidator, |
133 | unblockAccountByServerValidator, | 155 | unblockAccountByServerValidator, |
134 | unblockServerByServerValidator | 156 | unblockServerByServerValidator, |
157 | blocklistStatusValidator | ||
135 | } | 158 | } |
136 | 159 | ||
137 | // --------------------------------------------------------------------------- | 160 | // --------------------------------------------------------------------------- |
diff --git a/server/models/account/account-blocklist.ts b/server/models/account/account-blocklist.ts index b2375b006..21983428a 100644 --- a/server/models/account/account-blocklist.ts +++ b/server/models/account/account-blocklist.ts | |||
@@ -1,11 +1,12 @@ | |||
1 | import { Op } from 'sequelize' | 1 | import { Op, QueryTypes } from 'sequelize' |
2 | import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' | 2 | import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' |
3 | import { handlesToNameAndHost } from '@server/helpers/actors' | ||
3 | import { MAccountBlocklist, MAccountBlocklistAccounts, MAccountBlocklistFormattable } from '@server/types/models' | 4 | import { MAccountBlocklist, MAccountBlocklistAccounts, MAccountBlocklistFormattable } from '@server/types/models' |
4 | import { AttributesOnly } from '@shared/core-utils' | 5 | import { AttributesOnly } from '@shared/core-utils' |
5 | import { AccountBlock } from '../../../shared/models' | 6 | import { AccountBlock } from '../../../shared/models' |
6 | import { ActorModel } from '../actor/actor' | 7 | import { ActorModel } from '../actor/actor' |
7 | import { ServerModel } from '../server/server' | 8 | import { ServerModel } from '../server/server' |
8 | import { getSort, searchAttribute } from '../utils' | 9 | import { createSafeIn, getSort, searchAttribute } from '../utils' |
9 | import { AccountModel } from './account' | 10 | import { AccountModel } from './account' |
10 | 11 | ||
11 | enum ScopeNames { | 12 | enum ScopeNames { |
@@ -77,7 +78,7 @@ export class AccountBlocklistModel extends Model<Partial<AttributesOnly<AccountB | |||
77 | }) | 78 | }) |
78 | BlockedAccount: AccountModel | 79 | BlockedAccount: AccountModel |
79 | 80 | ||
80 | static isAccountMutedByMulti (accountIds: number[], targetAccountId: number) { | 81 | static isAccountMutedByAccounts (accountIds: number[], targetAccountId: number) { |
81 | const query = { | 82 | const query = { |
82 | attributes: [ 'accountId', 'id' ], | 83 | attributes: [ 'accountId', 'id' ], |
83 | where: { | 84 | where: { |
@@ -187,6 +188,39 @@ export class AccountBlocklistModel extends Model<Partial<AttributesOnly<AccountB | |||
187 | .then(entries => entries.map(e => `${e.BlockedAccount.Actor.preferredUsername}@${e.BlockedAccount.Actor.Server.host}`)) | 188 | .then(entries => entries.map(e => `${e.BlockedAccount.Actor.preferredUsername}@${e.BlockedAccount.Actor.Server.host}`)) |
188 | } | 189 | } |
189 | 190 | ||
191 | static getBlockStatus (byAccountIds: number[], handles: string[]): Promise<{ name: string, host: string, accountId: number }[]> { | ||
192 | const sanitizedHandles = handlesToNameAndHost(handles) | ||
193 | |||
194 | const localHandles = sanitizedHandles.filter(h => !h.host) | ||
195 | .map(h => h.name) | ||
196 | |||
197 | const remoteHandles = sanitizedHandles.filter(h => !!h.host) | ||
198 | .map(h => ([ h.name, h.host ])) | ||
199 | |||
200 | const handlesWhere: string[] = [] | ||
201 | |||
202 | if (localHandles.length !== 0) { | ||
203 | handlesWhere.push(`("actor"."preferredUsername" IN (:localHandles) AND "server"."id" IS NULL)`) | ||
204 | } | ||
205 | |||
206 | if (remoteHandles.length !== 0) { | ||
207 | handlesWhere.push(`(("actor"."preferredUsername", "server"."host") IN (:remoteHandles))`) | ||
208 | } | ||
209 | |||
210 | const rawQuery = `SELECT "accountBlocklist"."accountId", "actor"."preferredUsername" AS "name", "server"."host" ` + | ||
211 | `FROM "accountBlocklist" ` + | ||
212 | `INNER JOIN "account" ON "account"."id" = "accountBlocklist"."targetAccountId" ` + | ||
213 | `INNER JOIN "actor" ON "actor"."id" = "account"."actorId" ` + | ||
214 | `LEFT JOIN "server" ON "server"."id" = "actor"."serverId" ` + | ||
215 | `WHERE "accountBlocklist"."accountId" IN (${createSafeIn(AccountBlocklistModel.sequelize, byAccountIds)}) ` + | ||
216 | `AND (${handlesWhere.join(' OR ')})` | ||
217 | |||
218 | return AccountBlocklistModel.sequelize.query(rawQuery, { | ||
219 | type: QueryTypes.SELECT as QueryTypes.SELECT, | ||
220 | replacements: { byAccountIds, localHandles, remoteHandles } | ||
221 | }) | ||
222 | } | ||
223 | |||
190 | toFormattedJSON (this: MAccountBlocklistFormattable): AccountBlock { | 224 | toFormattedJSON (this: MAccountBlocklistFormattable): AccountBlock { |
191 | return { | 225 | return { |
192 | byAccount: this.ByAccount.toFormattedJSON(), | 226 | byAccount: this.ByAccount.toFormattedJSON(), |
diff --git a/server/models/server/server-blocklist.ts b/server/models/server/server-blocklist.ts index b3579d589..092998db3 100644 --- a/server/models/server/server-blocklist.ts +++ b/server/models/server/server-blocklist.ts | |||
@@ -1,10 +1,10 @@ | |||
1 | import { Op } from 'sequelize' | 1 | import { Op, QueryTypes } from 'sequelize' |
2 | import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' | 2 | import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' |
3 | import { MServerBlocklist, MServerBlocklistAccountServer, MServerBlocklistFormattable } from '@server/types/models' | 3 | import { MServerBlocklist, MServerBlocklistAccountServer, MServerBlocklistFormattable } from '@server/types/models' |
4 | import { AttributesOnly } from '@shared/core-utils' | 4 | import { AttributesOnly } from '@shared/core-utils' |
5 | import { ServerBlock } from '@shared/models' | 5 | import { ServerBlock } from '@shared/models' |
6 | import { AccountModel } from '../account/account' | 6 | import { AccountModel } from '../account/account' |
7 | import { getSort, searchAttribute } from '../utils' | 7 | import { createSafeIn, getSort, searchAttribute } from '../utils' |
8 | import { ServerModel } from './server' | 8 | import { ServerModel } from './server' |
9 | 9 | ||
10 | enum ScopeNames { | 10 | enum ScopeNames { |
@@ -76,7 +76,7 @@ export class ServerBlocklistModel extends Model<Partial<AttributesOnly<ServerBlo | |||
76 | }) | 76 | }) |
77 | BlockedServer: ServerModel | 77 | BlockedServer: ServerModel |
78 | 78 | ||
79 | static isServerMutedByMulti (accountIds: number[], targetServerId: number) { | 79 | static isServerMutedByAccounts (accountIds: number[], targetServerId: number) { |
80 | const query = { | 80 | const query = { |
81 | attributes: [ 'accountId', 'id' ], | 81 | attributes: [ 'accountId', 'id' ], |
82 | where: { | 82 | where: { |
@@ -141,6 +141,19 @@ export class ServerBlocklistModel extends Model<Partial<AttributesOnly<ServerBlo | |||
141 | .then(entries => entries.map(e => e.BlockedServer.host)) | 141 | .then(entries => entries.map(e => e.BlockedServer.host)) |
142 | } | 142 | } |
143 | 143 | ||
144 | static getBlockStatus (byAccountIds: number[], hosts: string[]): Promise<{ host: string, accountId: number }[]> { | ||
145 | const rawQuery = `SELECT "server"."host", "serverBlocklist"."accountId" ` + | ||
146 | `FROM "serverBlocklist" ` + | ||
147 | `INNER JOIN "server" ON "server"."id" = "serverBlocklist"."targetServerId" ` + | ||
148 | `WHERE "server"."host" IN (:hosts) ` + | ||
149 | `AND "serverBlocklist"."accountId" IN (${createSafeIn(ServerBlocklistModel.sequelize, byAccountIds)})` | ||
150 | |||
151 | return ServerBlocklistModel.sequelize.query(rawQuery, { | ||
152 | type: QueryTypes.SELECT as QueryTypes.SELECT, | ||
153 | replacements: { hosts } | ||
154 | }) | ||
155 | } | ||
156 | |||
144 | static listForApi (parameters: { | 157 | static listForApi (parameters: { |
145 | start: number | 158 | start: number |
146 | count: number | 159 | count: number |
diff --git a/server/tests/api/check-params/blocklist.ts b/server/tests/api/check-params/blocklist.ts index 7d5fae5cf..f72a892e2 100644 --- a/server/tests/api/check-params/blocklist.ts +++ b/server/tests/api/check-params/blocklist.ts | |||
@@ -481,6 +481,78 @@ describe('Test blocklist API validators', function () { | |||
481 | }) | 481 | }) |
482 | }) | 482 | }) |
483 | 483 | ||
484 | describe('When getting blocklist status', function () { | ||
485 | const path = '/api/v1/blocklist/status' | ||
486 | |||
487 | it('Should fail with a bad token', async function () { | ||
488 | await makeGetRequest({ | ||
489 | url: server.url, | ||
490 | path, | ||
491 | token: 'false', | ||
492 | expectedStatus: HttpStatusCode.UNAUTHORIZED_401 | ||
493 | }) | ||
494 | }) | ||
495 | |||
496 | it('Should fail with a bad accounts field', async function () { | ||
497 | await makeGetRequest({ | ||
498 | url: server.url, | ||
499 | path, | ||
500 | query: { | ||
501 | accounts: 1 | ||
502 | }, | ||
503 | expectedStatus: HttpStatusCode.BAD_REQUEST_400 | ||
504 | }) | ||
505 | |||
506 | await makeGetRequest({ | ||
507 | url: server.url, | ||
508 | path, | ||
509 | query: { | ||
510 | accounts: [ 1 ] | ||
511 | }, | ||
512 | expectedStatus: HttpStatusCode.BAD_REQUEST_400 | ||
513 | }) | ||
514 | }) | ||
515 | |||
516 | it('Should fail with a bad hosts field', async function () { | ||
517 | await makeGetRequest({ | ||
518 | url: server.url, | ||
519 | path, | ||
520 | query: { | ||
521 | hosts: 1 | ||
522 | }, | ||
523 | expectedStatus: HttpStatusCode.BAD_REQUEST_400 | ||
524 | }) | ||
525 | |||
526 | await makeGetRequest({ | ||
527 | url: server.url, | ||
528 | path, | ||
529 | query: { | ||
530 | hosts: [ 1 ] | ||
531 | }, | ||
532 | expectedStatus: HttpStatusCode.BAD_REQUEST_400 | ||
533 | }) | ||
534 | }) | ||
535 | |||
536 | it('Should succeed with the correct parameters', async function () { | ||
537 | await makeGetRequest({ | ||
538 | url: server.url, | ||
539 | path, | ||
540 | query: {}, | ||
541 | expectedStatus: HttpStatusCode.OK_200 | ||
542 | }) | ||
543 | |||
544 | await makeGetRequest({ | ||
545 | url: server.url, | ||
546 | path, | ||
547 | query: { | ||
548 | hosts: [ 'example.com' ], | ||
549 | accounts: [ 'john@example.com' ] | ||
550 | }, | ||
551 | expectedStatus: HttpStatusCode.OK_200 | ||
552 | }) | ||
553 | }) | ||
554 | }) | ||
555 | |||
484 | after(async function () { | 556 | after(async function () { |
485 | await cleanupTests(servers) | 557 | await cleanupTests(servers) |
486 | }) | 558 | }) |
diff --git a/server/tests/api/moderation/blocklist.ts b/server/tests/api/moderation/blocklist.ts index 089af8b15..b3fd8ecac 100644 --- a/server/tests/api/moderation/blocklist.ts +++ b/server/tests/api/moderation/blocklist.ts | |||
@@ -254,6 +254,45 @@ describe('Test blocklist', function () { | |||
254 | } | 254 | } |
255 | }) | 255 | }) |
256 | 256 | ||
257 | it('Should get blocked status', async function () { | ||
258 | const remoteHandle = 'user2@' + servers[1].host | ||
259 | const localHandle = 'user1@' + servers[0].host | ||
260 | const unknownHandle = 'user5@' + servers[0].host | ||
261 | |||
262 | { | ||
263 | const status = await command.getStatus({ accounts: [ remoteHandle ] }) | ||
264 | expect(Object.keys(status.accounts)).to.have.lengthOf(1) | ||
265 | expect(status.accounts[remoteHandle].blockedByUser).to.be.false | ||
266 | expect(status.accounts[remoteHandle].blockedByServer).to.be.false | ||
267 | |||
268 | expect(Object.keys(status.hosts)).to.have.lengthOf(0) | ||
269 | } | ||
270 | |||
271 | { | ||
272 | const status = await command.getStatus({ token: servers[0].accessToken, accounts: [ remoteHandle ] }) | ||
273 | expect(Object.keys(status.accounts)).to.have.lengthOf(1) | ||
274 | expect(status.accounts[remoteHandle].blockedByUser).to.be.true | ||
275 | expect(status.accounts[remoteHandle].blockedByServer).to.be.false | ||
276 | |||
277 | expect(Object.keys(status.hosts)).to.have.lengthOf(0) | ||
278 | } | ||
279 | |||
280 | { | ||
281 | const status = await command.getStatus({ token: servers[0].accessToken, accounts: [ localHandle, remoteHandle, unknownHandle ] }) | ||
282 | expect(Object.keys(status.accounts)).to.have.lengthOf(3) | ||
283 | |||
284 | for (const handle of [ localHandle, remoteHandle ]) { | ||
285 | expect(status.accounts[handle].blockedByUser).to.be.true | ||
286 | expect(status.accounts[handle].blockedByServer).to.be.false | ||
287 | } | ||
288 | |||
289 | expect(status.accounts[unknownHandle].blockedByUser).to.be.false | ||
290 | expect(status.accounts[unknownHandle].blockedByServer).to.be.false | ||
291 | |||
292 | expect(Object.keys(status.hosts)).to.have.lengthOf(0) | ||
293 | } | ||
294 | }) | ||
295 | |||
257 | it('Should not allow a remote blocked user to comment my videos', async function () { | 296 | it('Should not allow a remote blocked user to comment my videos', async function () { |
258 | this.timeout(60000) | 297 | this.timeout(60000) |
259 | 298 | ||
@@ -434,6 +473,35 @@ describe('Test blocklist', function () { | |||
434 | expect(block.blockedServer.host).to.equal('localhost:' + servers[1].port) | 473 | expect(block.blockedServer.host).to.equal('localhost:' + servers[1].port) |
435 | }) | 474 | }) |
436 | 475 | ||
476 | it('Should get blocklist status', async function () { | ||
477 | const blockedServer = servers[1].host | ||
478 | const notBlockedServer = 'example.com' | ||
479 | |||
480 | { | ||
481 | const status = await command.getStatus({ hosts: [ blockedServer, notBlockedServer ] }) | ||
482 | expect(Object.keys(status.accounts)).to.have.lengthOf(0) | ||
483 | |||
484 | expect(Object.keys(status.hosts)).to.have.lengthOf(2) | ||
485 | expect(status.hosts[blockedServer].blockedByUser).to.be.false | ||
486 | expect(status.hosts[blockedServer].blockedByServer).to.be.false | ||
487 | |||
488 | expect(status.hosts[notBlockedServer].blockedByUser).to.be.false | ||
489 | expect(status.hosts[notBlockedServer].blockedByServer).to.be.false | ||
490 | } | ||
491 | |||
492 | { | ||
493 | const status = await command.getStatus({ token: servers[0].accessToken, hosts: [ blockedServer, notBlockedServer ] }) | ||
494 | expect(Object.keys(status.accounts)).to.have.lengthOf(0) | ||
495 | |||
496 | expect(Object.keys(status.hosts)).to.have.lengthOf(2) | ||
497 | expect(status.hosts[blockedServer].blockedByUser).to.be.true | ||
498 | expect(status.hosts[blockedServer].blockedByServer).to.be.false | ||
499 | |||
500 | expect(status.hosts[notBlockedServer].blockedByUser).to.be.false | ||
501 | expect(status.hosts[notBlockedServer].blockedByServer).to.be.false | ||
502 | } | ||
503 | }) | ||
504 | |||
437 | it('Should unblock the remote server', async function () { | 505 | it('Should unblock the remote server', async function () { |
438 | await command.removeFromMyBlocklist({ server: 'localhost:' + servers[1].port }) | 506 | await command.removeFromMyBlocklist({ server: 'localhost:' + servers[1].port }) |
439 | }) | 507 | }) |
@@ -575,6 +643,27 @@ describe('Test blocklist', function () { | |||
575 | } | 643 | } |
576 | }) | 644 | }) |
577 | 645 | ||
646 | it('Should get blocked status', async function () { | ||
647 | const remoteHandle = 'user2@' + servers[1].host | ||
648 | const localHandle = 'user1@' + servers[0].host | ||
649 | const unknownHandle = 'user5@' + servers[0].host | ||
650 | |||
651 | for (const token of [ undefined, servers[0].accessToken ]) { | ||
652 | const status = await command.getStatus({ token, accounts: [ localHandle, remoteHandle, unknownHandle ] }) | ||
653 | expect(Object.keys(status.accounts)).to.have.lengthOf(3) | ||
654 | |||
655 | for (const handle of [ localHandle, remoteHandle ]) { | ||
656 | expect(status.accounts[handle].blockedByUser).to.be.false | ||
657 | expect(status.accounts[handle].blockedByServer).to.be.true | ||
658 | } | ||
659 | |||
660 | expect(status.accounts[unknownHandle].blockedByUser).to.be.false | ||
661 | expect(status.accounts[unknownHandle].blockedByServer).to.be.false | ||
662 | |||
663 | expect(Object.keys(status.hosts)).to.have.lengthOf(0) | ||
664 | } | ||
665 | }) | ||
666 | |||
578 | it('Should unblock the remote account', async function () { | 667 | it('Should unblock the remote account', async function () { |
579 | await command.removeFromServerBlocklist({ account: 'user2@localhost:' + servers[1].port }) | 668 | await command.removeFromServerBlocklist({ account: 'user2@localhost:' + servers[1].port }) |
580 | }) | 669 | }) |
@@ -620,6 +709,7 @@ describe('Test blocklist', function () { | |||
620 | }) | 709 | }) |
621 | 710 | ||
622 | describe('When managing server blocklist', function () { | 711 | describe('When managing server blocklist', function () { |
712 | |||
623 | it('Should list all videos', async function () { | 713 | it('Should list all videos', async function () { |
624 | for (const token of [ userModeratorToken, servers[0].accessToken ]) { | 714 | for (const token of [ userModeratorToken, servers[0].accessToken ]) { |
625 | await checkAllVideos(servers[0], token) | 715 | await checkAllVideos(servers[0], token) |
@@ -713,6 +803,23 @@ describe('Test blocklist', function () { | |||
713 | expect(block.blockedServer.host).to.equal('localhost:' + servers[1].port) | 803 | expect(block.blockedServer.host).to.equal('localhost:' + servers[1].port) |
714 | }) | 804 | }) |
715 | 805 | ||
806 | it('Should get blocklist status', async function () { | ||
807 | const blockedServer = servers[1].host | ||
808 | const notBlockedServer = 'example.com' | ||
809 | |||
810 | for (const token of [ undefined, servers[0].accessToken ]) { | ||
811 | const status = await command.getStatus({ token, hosts: [ blockedServer, notBlockedServer ] }) | ||
812 | expect(Object.keys(status.accounts)).to.have.lengthOf(0) | ||
813 | |||
814 | expect(Object.keys(status.hosts)).to.have.lengthOf(2) | ||
815 | expect(status.hosts[blockedServer].blockedByUser).to.be.false | ||
816 | expect(status.hosts[blockedServer].blockedByServer).to.be.true | ||
817 | |||
818 | expect(status.hosts[notBlockedServer].blockedByUser).to.be.false | ||
819 | expect(status.hosts[notBlockedServer].blockedByServer).to.be.false | ||
820 | } | ||
821 | }) | ||
822 | |||
716 | it('Should unblock the remote server', async function () { | 823 | it('Should unblock the remote server', async function () { |
717 | await command.removeFromServerBlocklist({ server: 'localhost:' + servers[1].port }) | 824 | await command.removeFromServerBlocklist({ server: 'localhost:' + servers[1].port }) |
718 | }) | 825 | }) |