diff --git a/packages/backend/src/core/QueryService.ts b/packages/backend/src/core/QueryService.ts index e1bfe8d3b9..d0de136938 100644 --- a/packages/backend/src/core/QueryService.ts +++ b/packages/backend/src/core/QueryService.ts @@ -7,7 +7,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Brackets, Not, WhereExpressionBuilder } from 'typeorm'; import { DI } from '@/di-symbols.js'; import type { MiUser } from '@/models/User.js'; -import { MiInstance } from '@/models/Instance.js'; import type { UserProfilesRepository, FollowingsRepository, ChannelFollowingsRepository, BlockingsRepository, NoteThreadMutingsRepository, MutingsRepository, RenoteMutingsRepository, MiMeta, InstancesRepository } from '@/models/_.js'; import { bindThis } from '@/decorators.js'; import { IdService } from '@/core/IdService.js'; @@ -204,7 +203,7 @@ export class QueryService { @bindThis public generateBlockedHostQueryForNote(q: SelectQueryBuilder, excludeAuthor?: boolean): SelectQueryBuilder { const checkFor = (key: 'user' | 'replyUser' | 'renoteUser') => this - .leftJoinInstance(q, `note.${key}Instance`, `${key}Instance`) + .leftJoin(q, `note.${key}Instance`, `${key}Instance`) .andWhere(new Brackets(qb => { qb .orWhere(`"${key}Instance" IS NULL`) // local @@ -225,31 +224,53 @@ export class QueryService { } @bindThis - public generateSilencedUserQueryForNotes(q: SelectQueryBuilder, me?: { id: MiUser['id'] } | null): SelectQueryBuilder { - if (!me) { - return q.andWhere('user.isSilenced = false'); + public generateSilencedUserQueryForNotes(q: SelectQueryBuilder, me?: { id: MiUser['id'] } | null, excludeAuthor = false): SelectQueryBuilder { + const checkFor = (key: 'user' | 'replyUser' | 'renoteUser') => { + // These are de-duplicated, since most call sites already provide some of them. + this.leftJoin(q, `note.${key}Instance`, `${key}Instance`); // note->instance + this.leftJoin(q, `note.${key}`, key); // note->user + + q.andWhere(new Brackets(qb => { + // case 1: user does not exist (note is not reply/renote) + qb.orWhere(`note.${key}Id IS NULL`); + + // case 2: user not silenced AND instance not silenced + qb.orWhere(new Brackets(qbb => qbb + .andWhere(new Brackets(qbbb => qbbb + .orWhere(`"${key}Instance"."isSilenced" = false`) + .orWhere(`"${key}Instance" IS NULL`))) + .andWhere(`"${key}"."isSilenced" = false`))); + + if (me) { + // case 3: we are the author + qb.orWhere(`note.${key}Id = :meId`); + + // case 4: we are following the user + this.orFollowingUser(qb, ':meId', `note.${key}Id`); + } + })); + }; + + // Set parameters only once + if (me) { + q.setParameters({ meId: me.id }); } - return this - .leftJoinInstance(q, 'note.userInstance', 'userInstance') - .andWhere(new Brackets(qb => this - // case 1: we are following the user - .orFollowingUser(qb, ':meId', 'note.userId') - // case 2: user not silenced AND instance not silenced - .orWhere(new Brackets(qbb => qbb - .andWhere(new Brackets(qbbb => qbbb - .orWhere('"userInstance"."isSilenced" = false') - .orWhere('"userInstance" IS NULL'))) - .andWhere('user.isSilenced = false'))))) - .setParameters({ meId: me.id }); + if (!excludeAuthor) { + checkFor('user'); + } + checkFor('replyUser'); + checkFor('renoteUser'); + + return q; } /** - * Left-joins an instance in to the query with a given alias and optional condition. - * These calls are de-duplicated - multiple uses of the same alias are skipped. + * Left-joins a relation into the query with a given alias and optional condition. + * These calls are de-duplicated - multiple uses of the same relation+alias are skipped. */ @bindThis - public leftJoinInstance(q: SelectQueryBuilder, relation: string | typeof MiInstance, alias: string, condition?: string): SelectQueryBuilder { + public leftJoin(q: SelectQueryBuilder, relation: string, alias: string, condition?: string): SelectQueryBuilder { // Skip if it's already joined, otherwise we'll get an error if (!q.expressionMap.joinAttributes.some(j => j.alias.name === alias)) { q.leftJoin(relation, alias, condition); diff --git a/packages/backend/src/core/SearchService.ts b/packages/backend/src/core/SearchService.ts index 9b9ae27739..1f308a2234 100644 --- a/packages/backend/src/core/SearchService.ts +++ b/packages/backend/src/core/SearchService.ts @@ -302,6 +302,7 @@ export class SearchService { this.queryService.generateVisibilityQuery(query, me); this.queryService.generateBlockedHostQueryForNote(query); this.queryService.generateSuspendedUserQueryForNote(query); + this.queryService.generateSilencedUserQueryForNotes(query, me); if (me) this.queryService.generateMutedUserQueryForNotes(query, me); if (me) this.queryService.generateBlockedUserQueryForNotes(query, me); @@ -380,6 +381,7 @@ export class SearchService { this.queryService.generateBlockedHostQueryForNote(query); this.queryService.generateSuspendedUserQueryForNote(query); + this.queryService.generateSilencedUserQueryForNotes(query, me); const notes = (await query.getMany()).filter(note => { if (me && isUserRelated(note, userIdsWhoBlockingMe)) return false; diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts index f8c12298cd..0aeceda038 100644 --- a/packages/backend/src/server/api/endpoints/antennas/notes.ts +++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts @@ -122,6 +122,7 @@ export default class extends Endpoint { // eslint- this.queryService.generateBlockedHostQueryForNote(query); this.queryService.generateSuspendedUserQueryForNote(query); + this.queryService.generateSilencedUserQueryForNotes(query, me); this.queryService.generateVisibilityQuery(query, me); this.queryService.generateMutedUserQueryForNotes(query, me); this.queryService.generateBlockedUserQueryForNotes(query, me); diff --git a/packages/backend/src/server/api/endpoints/notes/bubble-timeline.ts b/packages/backend/src/server/api/endpoints/notes/bubble-timeline.ts index 6bb5856f96..e31b9be702 100644 --- a/packages/backend/src/server/api/endpoints/notes/bubble-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/bubble-timeline.ts @@ -93,7 +93,7 @@ export default class extends Endpoint { // eslint- .leftJoin('(select "host" from "instance" where "isBubbled" = true)', 'bubbleInstance', '"bubbleInstance"."host" = "note"."userHost"') .andWhere('"bubbleInstance" IS NOT NULL'); this.queryService - .leftJoinInstance(query, 'note.userInstance', 'userInstance', '"userInstance"."host" = "bubbleInstance"."host"'); + .leftJoin(query, 'note.userInstance', 'userInstance', '"userInstance"."host" = "bubbleInstance"."host"'); this.queryService.generateBlockedHostQueryForNote(query); this.queryService.generateSilencedUserQueryForNotes(query, me); diff --git a/packages/backend/src/server/api/endpoints/notes/featured.ts b/packages/backend/src/server/api/endpoints/notes/featured.ts index 12d928c9a7..abb7941d47 100644 --- a/packages/backend/src/server/api/endpoints/notes/featured.ts +++ b/packages/backend/src/server/api/endpoints/notes/featured.ts @@ -122,6 +122,7 @@ export default class extends Endpoint { // eslint- this.queryService.generateBlockedHostQueryForNote(query); this.queryService.generateSuspendedUserQueryForNote(query); + this.queryService.generateSilencedUserQueryForNotes(query, me); const notes = (await query.getMany()).filter(note => { if (me && isUserRelated(note, userIdsWhoBlockingMe)) return false; diff --git a/packages/backend/src/server/api/endpoints/notes/renotes.ts b/packages/backend/src/server/api/endpoints/notes/renotes.ts index 4a4ef0b77e..71c40421b9 100644 --- a/packages/backend/src/server/api/endpoints/notes/renotes.ts +++ b/packages/backend/src/server/api/endpoints/notes/renotes.ts @@ -93,6 +93,7 @@ export default class extends Endpoint { // eslint- this.queryService.generateVisibilityQuery(query, me); this.queryService.generateBlockedHostQueryForNote(query); this.queryService.generateSuspendedUserQueryForNote(query); + this.queryService.generateSilencedUserQueryForNotes(query, me); if (me) { this.queryService.generateMutedUserQueryForNotes(query, me); this.queryService.generateBlockedUserQueryForNotes(query, me); diff --git a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts index d54a24f0a1..ff37e0aaba 100644 --- a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts @@ -177,6 +177,7 @@ export default class extends Endpoint { // eslint- this.queryService.generateVisibilityQuery(query, me); this.queryService.generateBlockedHostQueryForNote(query); this.queryService.generateSuspendedUserQueryForNote(query); + this.queryService.generateSilencedUserQueryForNotes(query, me, true); this.queryService.generateMutedUserQueryForNotes(query, me); this.queryService.generateBlockedUserQueryForNotes(query, me); diff --git a/packages/backend/src/server/api/endpoints/users/featured-notes.ts b/packages/backend/src/server/api/endpoints/users/featured-notes.ts index 8be5bb0de9..f77e81708a 100644 --- a/packages/backend/src/server/api/endpoints/users/featured-notes.ts +++ b/packages/backend/src/server/api/endpoints/users/featured-notes.ts @@ -95,6 +95,7 @@ export default class extends Endpoint { // eslint- this.queryService.generateBlockedHostQueryForNote(query); this.queryService.generateSuspendedUserQueryForNote(query); + this.queryService.generateSilencedUserQueryForNotes(query, me, true); const notes = (await query.getMany()).filter(note => { if (me && isUserRelated(note, userIdsWhoBlockingMe, false)) return false; diff --git a/packages/backend/src/server/api/endpoints/users/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts index 5b16ef32a4..b87d4abd7c 100644 --- a/packages/backend/src/server/api/endpoints/users/notes.ts +++ b/packages/backend/src/server/api/endpoints/users/notes.ts @@ -221,6 +221,7 @@ export default class extends Endpoint { // eslint- this.queryService.generateVisibilityQuery(query, me); this.queryService.generateBlockedHostQueryForNote(query, true); this.queryService.generateSuspendedUserQueryForNote(query, true); + this.queryService.generateSilencedUserQueryForNotes(query, me, true); if (me) { this.queryService.generateMutedUserQueryForNotes(query, me, { id: ps.userId }); this.queryService.generateBlockedUserQueryForNotes(query, me); diff --git a/packages/backend/src/server/api/endpoints/users/reactions.ts b/packages/backend/src/server/api/endpoints/users/reactions.ts index f0d2000f58..16e19fc1b5 100644 --- a/packages/backend/src/server/api/endpoints/users/reactions.ts +++ b/packages/backend/src/server/api/endpoints/users/reactions.ts @@ -115,6 +115,7 @@ export default class extends Endpoint { // eslint- this.queryService.generateVisibilityQuery(query, me); this.queryService.generateBlockedHostQueryForNote(query); this.queryService.generateSuspendedUserQueryForNote(query); + this.queryService.generateSilencedUserQueryForNotes(query, me, true); if (me) { this.queryService.generateMutedUserQueryForNotes(query, me); this.queryService.generateBlockedUserQueryForNotes(query, me);