mirror of
https://codeberg.org/yeentown/barkey.git
synced 2025-07-07 12:36:57 +00:00
refactor QueryService to use EXISTS instead of IN for most queries
This commit is contained in:
parent
825f219368
commit
cbefbd2a33
1 changed files with 221 additions and 155 deletions
|
@ -4,13 +4,13 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { Brackets, ObjectLiteral } from 'typeorm';
|
import { Brackets, Not, WhereExpressionBuilder } from 'typeorm';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { MiUser } from '@/models/User.js';
|
import type { MiUser } from '@/models/User.js';
|
||||||
import type { UserProfilesRepository, FollowingsRepository, ChannelFollowingsRepository, BlockingsRepository, NoteThreadMutingsRepository, MutingsRepository, RenoteMutingsRepository, MiMeta } from '@/models/_.js';
|
import type { UserProfilesRepository, FollowingsRepository, ChannelFollowingsRepository, BlockingsRepository, NoteThreadMutingsRepository, MutingsRepository, RenoteMutingsRepository, MiMeta, InstancesRepository, MiInstance } from '@/models/_.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
import type { SelectQueryBuilder } from 'typeorm';
|
import type { SelectQueryBuilder, FindOptionsWhere, ObjectLiteral } from 'typeorm';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class QueryService {
|
export class QueryService {
|
||||||
|
@ -36,6 +36,9 @@ export class QueryService {
|
||||||
@Inject(DI.renoteMutingsRepository)
|
@Inject(DI.renoteMutingsRepository)
|
||||||
private renoteMutingsRepository: RenoteMutingsRepository,
|
private renoteMutingsRepository: RenoteMutingsRepository,
|
||||||
|
|
||||||
|
@Inject(DI.instancesRepository)
|
||||||
|
private readonly instancesRepository: InstancesRepository,
|
||||||
|
|
||||||
@Inject(DI.meta)
|
@Inject(DI.meta)
|
||||||
private meta: MiMeta,
|
private meta: MiMeta,
|
||||||
|
|
||||||
|
@ -72,215 +75,278 @@ export class QueryService {
|
||||||
|
|
||||||
// ここでいうBlockedは被Blockedの意
|
// ここでいうBlockedは被Blockedの意
|
||||||
@bindThis
|
@bindThis
|
||||||
public generateBlockedUserQueryForNotes(q: SelectQueryBuilder<any>, me: { id: MiUser['id'] }): void {
|
public generateBlockedUserQueryForNotes<E extends ObjectLiteral>(q: SelectQueryBuilder<E>, me: { id: MiUser['id'] }): SelectQueryBuilder<E> {
|
||||||
const blockingQuery = this.blockingsRepository.createQueryBuilder('blocking')
|
|
||||||
.select('blocking.blockerId')
|
|
||||||
.where('blocking.blockeeId = :blockeeId', { blockeeId: me.id });
|
|
||||||
|
|
||||||
// 投稿の作者にブロックされていない かつ
|
// 投稿の作者にブロックされていない かつ
|
||||||
// 投稿の返信先の作者にブロックされていない かつ
|
// 投稿の返信先の作者にブロックされていない かつ
|
||||||
// 投稿の引用元の作者にブロックされていない
|
// 投稿の引用元の作者にブロックされていない
|
||||||
q
|
return this.excludeBlockingUser(q, 'note.userId', ':meId')
|
||||||
.andWhere(`note.userId NOT IN (${ blockingQuery.getQuery() })`)
|
|
||||||
.andWhere(new Brackets(qb => {
|
.andWhere(new Brackets(qb => {
|
||||||
qb
|
this.excludeBlockingUser(qb, 'note.replyUserId', ':meId')
|
||||||
.where('note.replyUserId IS NULL')
|
.orWhere('note.replyUserId IS NULL');
|
||||||
.orWhere(`note.replyUserId NOT IN (${ blockingQuery.getQuery() })`);
|
|
||||||
}))
|
}))
|
||||||
.andWhere(new Brackets(qb => {
|
.andWhere(new Brackets(qb => {
|
||||||
qb
|
this.excludeBlockingUser(qb, 'note.renoteUserId', ':meId')
|
||||||
.where('note.renoteUserId IS NULL')
|
.orWhere('note.renoteUserId IS NULL');
|
||||||
.orWhere(`note.renoteUserId NOT IN (${ blockingQuery.getQuery() })`);
|
}))
|
||||||
}));
|
.setParameters({ meId: me.id });
|
||||||
|
|
||||||
q.setParameters(blockingQuery.getParameters());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public generateBlockQueryForUsers(q: SelectQueryBuilder<any>, me: { id: MiUser['id'] }): void {
|
public generateBlockQueryForUsers<E extends ObjectLiteral>(q: SelectQueryBuilder<E>, me: { id: MiUser['id'] }): SelectQueryBuilder<E> {
|
||||||
const blockingQuery = this.blockingsRepository.createQueryBuilder('blocking')
|
this.excludeBlockingUser(q, ':meId', 'user.id');
|
||||||
.select('blocking.blockeeId')
|
this.excludeBlockingUser(q, 'user.id', ':me.id');
|
||||||
.where('blocking.blockerId = :blockerId', { blockerId: me.id });
|
return q.setParameters({ meId: me.id });
|
||||||
|
|
||||||
const blockedQuery = this.blockingsRepository.createQueryBuilder('blocking')
|
|
||||||
.select('blocking.blockerId')
|
|
||||||
.where('blocking.blockeeId = :blockeeId', { blockeeId: me.id });
|
|
||||||
|
|
||||||
q.andWhere(`user.id NOT IN (${ blockingQuery.getQuery() })`);
|
|
||||||
q.setParameters(blockingQuery.getParameters());
|
|
||||||
|
|
||||||
q.andWhere(`user.id NOT IN (${ blockedQuery.getQuery() })`);
|
|
||||||
q.setParameters(blockedQuery.getParameters());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public generateMutedNoteThreadQuery(q: SelectQueryBuilder<any>, me: { id: MiUser['id'] }): void {
|
public generateMutedNoteThreadQuery<E extends ObjectLiteral>(q: SelectQueryBuilder<E>, me: { id: MiUser['id'] }): SelectQueryBuilder<E> {
|
||||||
const mutedQuery = this.noteThreadMutingsRepository.createQueryBuilder('threadMuted')
|
return this.excludeMutingThread(q, ':meId', 'note.id')
|
||||||
.select('threadMuted.threadId')
|
.andWhere(new Brackets(qb => {
|
||||||
.where('threadMuted.userId = :userId', { userId: me.id });
|
this.excludeMutingThread(qb, ':meId', 'note.threadId')
|
||||||
|
.orWhere('note.threadId IS NULL');
|
||||||
q.andWhere(`note.id NOT IN (${ mutedQuery.getQuery() })`);
|
}))
|
||||||
q.andWhere(new Brackets(qb => {
|
.setParameters({ meId: me.id });
|
||||||
qb
|
|
||||||
.where('note.threadId IS NULL')
|
|
||||||
.orWhere(`note.threadId NOT IN (${ mutedQuery.getQuery() })`);
|
|
||||||
}));
|
|
||||||
|
|
||||||
q.setParameters(mutedQuery.getParameters());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public generateMutedUserQueryForNotes(q: SelectQueryBuilder<any>, me: { id: MiUser['id'] }, exclude?: { id: MiUser['id'] }): void {
|
public generateMutedUserQueryForNotes<E extends ObjectLiteral>(q: SelectQueryBuilder<E>, me: { id: MiUser['id'] }, exclude?: { id: MiUser['id'] }): SelectQueryBuilder<E> {
|
||||||
const mutingQuery = this.mutingsRepository.createQueryBuilder('muting')
|
|
||||||
.select('muting.muteeId')
|
|
||||||
.where('muting.muterId = :muterId', { muterId: me.id });
|
|
||||||
|
|
||||||
if (exclude) {
|
|
||||||
mutingQuery.andWhere('muting.muteeId != :excludeId', { excludeId: exclude.id });
|
|
||||||
}
|
|
||||||
|
|
||||||
const mutingInstanceQuery = this.userProfilesRepository.createQueryBuilder('user_profile')
|
|
||||||
.select('user_profile.mutedInstances')
|
|
||||||
.where('user_profile.userId = :muterId', { muterId: me.id });
|
|
||||||
|
|
||||||
// 投稿の作者をミュートしていない かつ
|
// 投稿の作者をミュートしていない かつ
|
||||||
// 投稿の返信先の作者をミュートしていない かつ
|
// 投稿の返信先の作者をミュートしていない かつ
|
||||||
// 投稿の引用元の作者をミュートしていない
|
// 投稿の引用元の作者をミュートしていない
|
||||||
q
|
this.excludeMutingUser(q, ':meId', 'note.userId', exclude)
|
||||||
.andWhere(`note.userId NOT IN (${ mutingQuery.getQuery() })`)
|
|
||||||
.andWhere(new Brackets(qb => {
|
.andWhere(new Brackets(qb => {
|
||||||
qb
|
this.excludeMutingUser(qb, ':meId', 'note.replyUserId', exclude)
|
||||||
.where('note.replyUserId IS NULL')
|
.orWhere('note.replyUserId IS NULL');
|
||||||
.orWhere(`note.replyUserId NOT IN (${ mutingQuery.getQuery() })`);
|
|
||||||
}))
|
}))
|
||||||
.andWhere(new Brackets(qb => {
|
.andWhere(new Brackets(qb => {
|
||||||
qb
|
this.excludeMutingUser(qb, ':meId', 'note.renoteUserId', exclude)
|
||||||
.where('note.renoteUserId IS NULL')
|
.orWhere('note.renoteUserId IS NULL');
|
||||||
.orWhere(`note.renoteUserId NOT IN (${ mutingQuery.getQuery() })`);
|
}));
|
||||||
}))
|
|
||||||
// mute instances
|
// mute instances
|
||||||
|
this.excludeMutingInstance(q, ':meId', 'note.userHost')
|
||||||
.andWhere(new Brackets(qb => {
|
.andWhere(new Brackets(qb => {
|
||||||
qb
|
this.excludeMutingInstance(qb, ':meId', 'note.replyUserHost')
|
||||||
.andWhere('note.userHost IS NULL')
|
.orWhere('note.replyUserHost IS NULL');
|
||||||
.orWhere(`NOT ((${ mutingInstanceQuery.getQuery() })::jsonb ? note.userHost)`);
|
|
||||||
}))
|
}))
|
||||||
.andWhere(new Brackets(qb => {
|
.andWhere(new Brackets(qb => {
|
||||||
qb
|
this.excludeMutingInstance(qb, ':meId', 'note.renoteUserHost')
|
||||||
.where('note.replyUserHost IS NULL')
|
.orWhere('note.renoteUserHost IS NULL');
|
||||||
.orWhere(`NOT ((${ mutingInstanceQuery.getQuery() })::jsonb ? note.replyUserHost)`);
|
|
||||||
}))
|
|
||||||
.andWhere(new Brackets(qb => {
|
|
||||||
qb
|
|
||||||
.where('note.renoteUserHost IS NULL')
|
|
||||||
.orWhere(`NOT ((${ mutingInstanceQuery.getQuery() })::jsonb ? note.renoteUserHost)`);
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
q.setParameters(mutingQuery.getParameters());
|
return q.setParameters({ meId: me.id });
|
||||||
q.setParameters(mutingInstanceQuery.getParameters());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public generateMutedUserQueryForUsers(q: SelectQueryBuilder<any>, me: { id: MiUser['id'] }): void {
|
public generateMutedUserQueryForUsers<E extends ObjectLiteral>(q: SelectQueryBuilder<E>, me: { id: MiUser['id'] }): SelectQueryBuilder<E> {
|
||||||
const mutingQuery = this.mutingsRepository.createQueryBuilder('muting')
|
return this.excludeMutingUser(q, ':meId', 'user.id')
|
||||||
.select('muting.muteeId')
|
.setParameters({ meId: me.id });
|
||||||
.where('muting.muterId = :muterId', { muterId: me.id });
|
|
||||||
|
|
||||||
q.andWhere(`user.id NOT IN (${ mutingQuery.getQuery() })`);
|
|
||||||
|
|
||||||
q.setParameters(mutingQuery.getParameters());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This intentionally skips isSuspended, isDeleted, makeNotesFollowersOnlyBefore, makeNotesHiddenBefore, and requireSigninToViewContents.
|
||||||
|
// NoteEntityService checks these automatically and calls hideNote() to hide them without breaking threads.
|
||||||
|
// For moderation purposes, you can set isSilenced to forcibly hide existing posts by a user.
|
||||||
@bindThis
|
@bindThis
|
||||||
public generateVisibilityQuery(q: SelectQueryBuilder<any>, me?: { id: MiUser['id'] } | null): void {
|
public generateVisibilityQuery<E extends ObjectLiteral>(q: SelectQueryBuilder<E>, me?: { id: MiUser['id'] } | null): SelectQueryBuilder<E> {
|
||||||
// This code must always be synchronized with the checks in Notes.isVisibleForMe.
|
// This code must always be synchronized with the checks in Notes.isVisibleForMe.
|
||||||
if (me == null) {
|
return q.andWhere(new Brackets(qb => {
|
||||||
q.andWhere(new Brackets(qb => {
|
// Public post
|
||||||
qb
|
qb.orWhere('note.visibility = \'public\'')
|
||||||
.where('note.visibility = \'public\'')
|
|
||||||
.orWhere('note.visibility = \'home\'');
|
.orWhere('note.visibility = \'home\'');
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
const followingQuery = this.followingsRepository.createQueryBuilder('following')
|
|
||||||
.select('following.followeeId')
|
|
||||||
.where('following.followerId = :meId');
|
|
||||||
|
|
||||||
q.andWhere(new Brackets(qb => {
|
if (me != null) {
|
||||||
qb
|
qb
|
||||||
// 公開投稿である
|
// My post
|
||||||
.where(new Brackets(qb => {
|
.orWhere(':meId = note.userId')
|
||||||
qb
|
// Reply to me
|
||||||
.where('note.visibility = \'public\'')
|
.orWhere(':meId = note.replyUserId')
|
||||||
.orWhere('note.visibility = \'home\'');
|
// DM to me
|
||||||
}))
|
.orWhere(':meId = ANY (note.visibleUserIds)')
|
||||||
// または 自分自身
|
// Mentions me
|
||||||
.orWhere('note.userId = :meId')
|
.orWhere(':meId = ANY (note.mentions)')
|
||||||
// または 自分宛て
|
// Followers-only post
|
||||||
.orWhere(':meIdAsList <@ note.visibleUserIds')
|
|
||||||
.orWhere(new Brackets(qb => {
|
.orWhere(new Brackets(qb => {
|
||||||
qb
|
|
||||||
// または フォロワー宛ての投稿であり、
|
// または フォロワー宛ての投稿であり、
|
||||||
.where('note.visibility = \'followers\'')
|
this.addFollowingUser(qb, ':meId', 'note.userId')
|
||||||
.andWhere(new Brackets(qb => {
|
.andWhere('note.visibility = \'followers\'');
|
||||||
qb
|
|
||||||
// 自分がフォロワーである
|
|
||||||
.where(`note.userId IN (${ followingQuery.getQuery() })`)
|
|
||||||
// または 自分の投稿へのリプライ
|
|
||||||
.orWhere('note.replyUserId = :meId')
|
|
||||||
.orWhere(':meIdAsList <@ note.mentions');
|
|
||||||
}));
|
|
||||||
}));
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
q.setParameters({ meId: me.id, meIdAsList: [me.id] });
|
q.setParameters({ meId: me.id });
|
||||||
}
|
}
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public generateMutedUserRenotesQueryForNotes(q: SelectQueryBuilder<any>, me: { id: MiUser['id'] }): void {
|
public generateMutedUserRenotesQueryForNotes<E extends ObjectLiteral>(q: SelectQueryBuilder<E>, me: { id: MiUser['id'] }): SelectQueryBuilder<E> {
|
||||||
const mutingQuery = this.renoteMutingsRepository.createQueryBuilder('renote_muting')
|
return q.andWhere(new Brackets(qb => {
|
||||||
.select('renote_muting.muteeId')
|
this.excludeMutingRenote(qb, ':meId', 'note.userId')
|
||||||
.where('renote_muting.muterId = :muterId', { muterId: me.id });
|
|
||||||
|
|
||||||
q.andWhere(new Brackets(qb => {
|
|
||||||
qb
|
|
||||||
.orWhere('note.renoteId IS NULL')
|
.orWhere('note.renoteId IS NULL')
|
||||||
.orWhere('note.text IS NOT NULL')
|
.orWhere('note.text IS NOT NULL')
|
||||||
.orWhere('note.cw IS NOT NULL')
|
.orWhere('note.cw IS NOT NULL')
|
||||||
.orWhere('note.replyId IS NOT NULL')
|
.orWhere('note.replyId IS NOT NULL')
|
||||||
.orWhere('note.hasPoll = false')
|
.orWhere('note.hasPoll = true')
|
||||||
.orWhere('note.fileIds != \'{}\'')
|
.orWhere('note.fileIds != \'{}\'');
|
||||||
.orWhere(`note.userId NOT IN (${ mutingQuery.getQuery() })`);
|
}))
|
||||||
}));
|
.setParameters({ meId: me.id });
|
||||||
|
|
||||||
q.setParameters(mutingQuery.getParameters());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO replace allowSilenced with matchingHostQuery
|
||||||
@bindThis
|
@bindThis
|
||||||
public generateBlockedHostQueryForNote(q: SelectQueryBuilder<any>, excludeAuthor?: boolean, allowSilenced = true): void {
|
public generateBlockedHostQueryForNote<E extends ObjectLiteral>(q: SelectQueryBuilder<E>, excludeAuthor?: boolean, allowSilenced = true): SelectQueryBuilder<E> {
|
||||||
function checkFor(key: 'user' | 'replyUser' | 'renoteUser') {
|
const checkFor = (key: 'user' | 'replyUser' | 'renoteUser') => {
|
||||||
q.leftJoin(`note.${key}Instance`, `${key}Instance`);
|
|
||||||
q.andWhere(new Brackets(qb => {
|
q.andWhere(new Brackets(qb => {
|
||||||
qb.orWhere(`note.${key}Id IS NULL`) // no corresponding user
|
qb.orWhere(`note.${key}Host IS NULL`); // local
|
||||||
.orWhere(`note.${key}Host IS NULL`); // local
|
|
||||||
|
|
||||||
if (allowSilenced) {
|
if (key !== 'user') {
|
||||||
qb.orWhere(`${key}Instance.isBlocked = false`); // not blocked
|
// note.userId always exists and is non-null
|
||||||
} else {
|
qb.orWhere(`note.${key}Id IS NULL`); // no corresponding user
|
||||||
qb.orWhere(new Brackets(qbb => qbb
|
|
||||||
.andWhere(`${key}Instance.isBlocked = false`) // not blocked
|
|
||||||
.andWhere(`${key}Instance.isSilenced = false`))); // not silenced
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// note.userId always equals note.userId
|
||||||
if (excludeAuthor) {
|
if (excludeAuthor) {
|
||||||
qb.orWhere(`note.userId = note.${key}Id`); // author
|
qb.orWhere(`note.userId = note.${key}Id`); // author
|
||||||
}
|
}
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (allowSilenced) {
|
||||||
|
// not blocked
|
||||||
|
this.excludeInstanceWhere(qb, `note.${key}Host`, {
|
||||||
|
isBlocked: false,
|
||||||
|
}, 'orWhere');
|
||||||
|
} else {
|
||||||
|
// not blocked or silenced
|
||||||
|
this.excludeInstanceWhere(qb, `note.${key}Host`, {
|
||||||
|
isBlocked: false,
|
||||||
|
isSilenced: false,
|
||||||
|
}, 'orWhere');
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
if (!excludeAuthor) {
|
if (!excludeAuthor) {
|
||||||
checkFor('user');
|
checkFor('user');
|
||||||
}
|
}
|
||||||
checkFor('replyUser');
|
checkFor('replyUser');
|
||||||
checkFor('renoteUser');
|
checkFor('renoteUser');
|
||||||
|
|
||||||
|
return q;
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public generateMatchingHostQueryForNote<E extends ObjectLiteral>(q: SelectQueryBuilder<E>, filters: FindOptionsWhere<MiInstance> | FindOptionsWhere<MiInstance>[], hostProp = 'note.userHost'): SelectQueryBuilder<E> {
|
||||||
|
return this.includeInstanceWhere(q, hostProp, filters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds condition that hostProp (instance host) matches the given filters.
|
||||||
|
* The prop should be an expression, not raw values.
|
||||||
|
*/
|
||||||
|
@bindThis
|
||||||
|
public includeInstanceWhere<Q extends WhereExpressionBuilder>(q: Q, hostProp: string, filters: FindOptionsWhere<MiInstance> | FindOptionsWhere<MiInstance>[], join: 'andWhere' | 'orWhere' = 'andWhere'): Q {
|
||||||
|
const instancesQuery = this.instancesRepository.createQueryBuilder('instance')
|
||||||
|
.select('1')
|
||||||
|
.andWhere(`instance.host = ${hostProp}`)
|
||||||
|
.andWhere(filters);
|
||||||
|
|
||||||
|
return q[join](`EXISTS (${instancesQuery.getQuery()})`, instancesQuery.getParameters());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds condition that hostProp (instance host) matches the given filters.
|
||||||
|
* The prop should be an expression, not raw values.
|
||||||
|
*/
|
||||||
|
@bindThis
|
||||||
|
public excludeInstanceWhere<Q extends WhereExpressionBuilder>(q: Q, hostProp: string, filters: FindOptionsWhere<MiInstance> | FindOptionsWhere<MiInstance>[], join: 'andWhere' | 'orWhere' = 'andWhere'): Q {
|
||||||
|
const instancesQuery = this.instancesRepository.createQueryBuilder('instance')
|
||||||
|
.select('1')
|
||||||
|
.andWhere(`instance.host = ${hostProp}`)
|
||||||
|
.andWhere(filters);
|
||||||
|
|
||||||
|
return q[join](`NOT EXISTS (${instancesQuery.getQuery()})`, instancesQuery.getParameters());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds condition that followerProp (user ID) is following followeeProp (user ID).
|
||||||
|
* Both props should be expressions, not raw values.
|
||||||
|
*/
|
||||||
|
public addFollowingUser<Q extends WhereExpressionBuilder>(q: Q, followerProp: string, followeeProp: string): Q {
|
||||||
|
const followingQuery = this.followingsRepository.createQueryBuilder('following')
|
||||||
|
.select('1')
|
||||||
|
.andWhere(`following.followerId = ${followerProp}`)
|
||||||
|
.andWhere(`following.followeeId = ${followeeProp}`);
|
||||||
|
|
||||||
|
return q.andWhere(`EXISTS (${followingQuery.getQuery()})`, followingQuery.getParameters());
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds condition that blockerProp (user ID) is not blocking blockeeProp (user ID).
|
||||||
|
* Both props should be expressions, not raw values.
|
||||||
|
*/
|
||||||
|
@bindThis
|
||||||
|
public excludeBlockingUser<Q extends WhereExpressionBuilder>(q: Q, blockerProp: string, blockeeProp: string): Q {
|
||||||
|
const blockingQuery = this.blockingsRepository.createQueryBuilder('blocking')
|
||||||
|
.select('1')
|
||||||
|
.andWhere(`blocking.blockerId = ${blockerProp}`)
|
||||||
|
.andWhere(`blocking.blockeeId = ${blockeeProp}`);
|
||||||
|
|
||||||
|
return q.andWhere(`NOT EXISTS (${blockingQuery.getQuery()})`, blockingQuery.getParameters());
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds condition that muterProp (user ID) is not muting muteeProp (user ID).
|
||||||
|
* Both props should be expressions, not raw values.
|
||||||
|
*/
|
||||||
|
@bindThis
|
||||||
|
public excludeMutingUser<Q extends WhereExpressionBuilder>(q: Q, muterProp: string, muteeProp: string, exclude?: { id: MiUser['id'] }): Q {
|
||||||
|
const mutingQuery = this.mutingsRepository.createQueryBuilder('muting')
|
||||||
|
.select('1')
|
||||||
|
.andWhere(`muting.muterId = ${muterProp}`)
|
||||||
|
.andWhere(`muting.muteeId = ${muteeProp}`);
|
||||||
|
|
||||||
|
if (exclude) {
|
||||||
|
mutingQuery.andWhere({ muteeId: Not(exclude.id) });
|
||||||
|
}
|
||||||
|
|
||||||
|
return q.andWhere(`NOT EXISTS (${mutingQuery.getQuery()})`, mutingQuery.getParameters());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds condition that muterProp (user ID) is not muting renotes by muteeProp (user ID).
|
||||||
|
* Both props should be expressions, not raw values.
|
||||||
|
*/
|
||||||
|
public excludeMutingRenote<Q extends WhereExpressionBuilder>(q: Q, muterProp: string, muteeProp: string): Q {
|
||||||
|
const mutingQuery = this.renoteMutingsRepository.createQueryBuilder('renote_muting')
|
||||||
|
.select('1')
|
||||||
|
.andWhere(`renote_muting.muterId = ${muterProp}`)
|
||||||
|
.andWhere(`renote_muting.muteeId = ${muteeProp}`);
|
||||||
|
|
||||||
|
return q.andWhere(`NOT EXISTS (${mutingQuery.getQuery()})`, mutingQuery.getParameters());
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds condition that muterProp (user ID) is not muting muteeProp (instance host).
|
||||||
|
* Both props should be expressions, not raw values.
|
||||||
|
*/
|
||||||
|
@bindThis
|
||||||
|
public excludeMutingInstance<Q extends WhereExpressionBuilder>(q: Q, muterProp: string, muteeProp: string): Q {
|
||||||
|
const mutingInstanceQuery = this.userProfilesRepository.createQueryBuilder('user_profile')
|
||||||
|
.select('1')
|
||||||
|
.andWhere(`user_profile.userId = ${muterProp}`)
|
||||||
|
.andWhere(`"user_profile"."mutedInstances"::jsonb ? ${muteeProp}`);
|
||||||
|
|
||||||
|
return q.andWhere(`NOT EXISTS (${mutingInstanceQuery.getQuery()})`, mutingInstanceQuery.getParameters());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds condition that muterProp (user ID) is not muting muteeProp (note ID).
|
||||||
|
* Both props should be expressions, not raw values.
|
||||||
|
*/
|
||||||
|
@bindThis
|
||||||
|
public excludeMutingThread<Q extends WhereExpressionBuilder>(q: Q, muterProp: string, muteeProp: string): Q {
|
||||||
|
const threadMutedQuery = this.noteThreadMutingsRepository.createQueryBuilder('threadMuted')
|
||||||
|
.select('1')
|
||||||
|
.andWhere(`threadMuted.userId = ${muterProp}`)
|
||||||
|
.andWhere(`threadMuted.threadId = ${muteeProp}`);
|
||||||
|
|
||||||
|
return q.andWhere(`NOT EXISTS (${threadMutedQuery.getQuery()})`, threadMutedQuery.getParameters());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue