Add mutingType field to Muting model and update related code
Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
This commit is contained in:
parent
1810d6e837
commit
685847e1b6
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class AddMutingType1762516776421 {
|
||||
name = 'AddMutingType1762516776421'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "muting" ADD "mutingType" varchar(128) NOT NULL DEFAULT 'all'`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "muting" DROP COLUMN "mutingType"`);
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import * as Redis from 'ioredis';
|
||||
import type { BlockingsRepository, FollowingsRepository, MutingsRepository, RenoteMutingsRepository, MiUserProfile, UserProfilesRepository, UsersRepository, MiFollowing } from '@/models/_.js';
|
||||
import type { BlockingsRepository, FollowingsRepository, MutingsRepository, RenoteMutingsRepository, MiUserProfile, UserProfilesRepository, UsersRepository, MiFollowing, MiMuting } from '@/models/_.js';
|
||||
import { MemoryKVCache, RedisKVCache } from '@/misc/cache.js';
|
||||
import type { MiLocalUser, MiUser } from '@/models/User.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
|
|
@ -21,7 +21,7 @@ export class CacheService implements OnApplicationShutdown {
|
|||
public localUserByIdCache: MemoryKVCache<MiLocalUser>;
|
||||
public uriPersonCache: MemoryKVCache<MiUser | null>;
|
||||
public userProfileCache: RedisKVCache<MiUserProfile>;
|
||||
public userMutingsCache: RedisKVCache<Set<string>>;
|
||||
public userMutingsCache: RedisKVCache<Map<string, Pick<MiMuting, 'mutingType'>>>;
|
||||
public userBlockingCache: RedisKVCache<Set<string>>;
|
||||
public userBlockedCache: RedisKVCache<Set<string>>; // NOTE: 「被」Blockキャッシュ
|
||||
public renoteMutingsCache: RedisKVCache<Set<string>>;
|
||||
|
|
@ -69,12 +69,12 @@ export class CacheService implements OnApplicationShutdown {
|
|||
fromRedisConverter: (value) => JSON.parse(value), // TODO: date型の考慮
|
||||
});
|
||||
|
||||
this.userMutingsCache = new RedisKVCache<Set<string>>(this.redisClient, 'userMutings', {
|
||||
this.userMutingsCache = new RedisKVCache<Map<string, Pick<MiMuting, 'mutingType'>>>(this.redisClient, 'userMutings', {
|
||||
lifetime: 1000 * 60 * 30, // 30m
|
||||
memoryCacheLifetime: 1000 * 60, // 1m
|
||||
fetcher: (key) => this.mutingsRepository.find({ where: { muterId: key }, select: ['muteeId'] }).then(xs => new Set(xs.map(x => x.muteeId))),
|
||||
toRedisConverter: (value) => JSON.stringify(Array.from(value)),
|
||||
fromRedisConverter: (value) => new Set(JSON.parse(value)),
|
||||
fetcher: (key) => this.mutingsRepository.find({ where: { muterId: key }, select: ['muteeId', 'mutingType'] }).then(xs => new Map(xs.map(x => [x.muteeId, { mutingType: x.mutingType }]))),
|
||||
toRedisConverter: (value) => JSON.stringify(Array.from(value.entries())),
|
||||
fromRedisConverter: (value) => new Map(JSON.parse(value)),
|
||||
});
|
||||
|
||||
this.userBlockingCache = new RedisKVCache<Set<string>>(this.redisClient, 'userBlocking', {
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ export class FanoutTimelineEndpointService {
|
|||
if (ps.me) {
|
||||
const me = ps.me;
|
||||
const [
|
||||
userIdsWhoMeMuting,
|
||||
userIdsWhoMeMutingMap,
|
||||
userIdsWhoMeMutingRenotes,
|
||||
userIdsWhoBlockingMe,
|
||||
userMutedInstances,
|
||||
|
|
@ -124,6 +124,8 @@ export class FanoutTimelineEndpointService {
|
|||
this.channelMutingService.mutingChannelsCache.fetch(me.id),
|
||||
]);
|
||||
|
||||
const userIdsWhoMeMuting = new Set(userIdsWhoMeMutingMap.keys());
|
||||
|
||||
const parentFilter = filter;
|
||||
filter = (note) => {
|
||||
if (isUserRelated(note, userIdsWhoBlockingMe, ps.ignoreAuthorFromBlock)) return false;
|
||||
|
|
|
|||
|
|
@ -108,7 +108,8 @@ export class NotificationService implements OnApplicationShutdown {
|
|||
}
|
||||
|
||||
const mutings = await this.cacheService.userMutingsCache.fetch(notifieeId);
|
||||
if (mutings.has(notifierId)) {
|
||||
const muting = mutings.get(notifierId);
|
||||
if (muting && muting.mutingType === 'all') {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -286,14 +286,16 @@ export class SearchService {
|
|||
}
|
||||
|
||||
const [
|
||||
userIdsWhoMeMuting,
|
||||
userIdsWhoMeMutingMap,
|
||||
userIdsWhoBlockingMe,
|
||||
] = me
|
||||
? await Promise.all([
|
||||
this.cacheService.userMutingsCache.fetch(me.id),
|
||||
this.cacheService.userBlockedCache.fetch(me.id),
|
||||
])
|
||||
: [new Set<string>(), new Set<string>()];
|
||||
: [new Map<string, { mutingType: 'all' | 'timelineOnly' }>(), new Set<string>()];
|
||||
|
||||
const userIdsWhoMeMuting = new Set(userIdsWhoMeMutingMap.keys());
|
||||
|
||||
const query = this.notesRepository.createQueryBuilder('note')
|
||||
.innerJoinAndSelect('note.user', 'user')
|
||||
|
|
|
|||
|
|
@ -24,12 +24,13 @@ export class UserMutingService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
public async mute(user: MiUser, target: MiUser, expiresAt: Date | null = null): Promise<void> {
|
||||
public async mute(user: MiUser, target: MiUser, expiresAt: Date | null = null, mutingType: 'all' | 'timelineOnly' = 'all'): Promise<void> {
|
||||
await this.mutingsRepository.insert({
|
||||
id: this.idService.gen(),
|
||||
expiresAt: expiresAt ?? null,
|
||||
muterId: user.id,
|
||||
muteeId: target.id,
|
||||
mutingType: mutingType,
|
||||
});
|
||||
|
||||
this.cacheService.userMutingsCache.refresh(user.id);
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ export class MutingEntityService {
|
|||
id: muting.id,
|
||||
createdAt: this.idService.parse(muting.id).date.toISOString(),
|
||||
expiresAt: muting.expiresAt ? muting.expiresAt.toISOString() : null,
|
||||
mutingType: muting.mutingType,
|
||||
muteeId: muting.muteeId,
|
||||
mutee: hints?.packedMutee ?? this.userEntityService.pack(muting.muteeId, me, {
|
||||
schema: 'UserDetailedNotMe',
|
||||
|
|
|
|||
|
|
@ -289,12 +289,13 @@ export class NotificationEntityService implements OnModuleInit {
|
|||
*/
|
||||
#validateNotifier <T extends MiNotification | MiGroupedNotification> (
|
||||
notification: T,
|
||||
userIdsWhoMeMuting: Set<MiUser['id']>,
|
||||
userIdsWhoMeMutingMap: Map<MiUser['id'], { mutingType: 'all' | 'timelineOnly' }>,
|
||||
userMutedInstances: Set<string>,
|
||||
notifiers: MiUser[],
|
||||
): boolean {
|
||||
if (!('notifierId' in notification)) return true;
|
||||
if (userIdsWhoMeMuting.has(notification.notifierId)) return false;
|
||||
const muting = userIdsWhoMeMutingMap.get(notification.notifierId);
|
||||
if (muting && muting.mutingType === 'all') return false;
|
||||
|
||||
const notifier = notifiers.find(x => x.id === notification.notifierId) ?? null;
|
||||
|
||||
|
|
@ -324,7 +325,7 @@ export class NotificationEntityService implements OnModuleInit {
|
|||
meId: MiUser['id'],
|
||||
): Promise<T[]> {
|
||||
const [
|
||||
userIdsWhoMeMuting,
|
||||
userIdsWhoMeMutingMap,
|
||||
userMutedInstances,
|
||||
] = await Promise.all([
|
||||
this.cacheService.userMutingsCache.fetch(meId),
|
||||
|
|
@ -337,7 +338,7 @@ export class NotificationEntityService implements OnModuleInit {
|
|||
}) : [];
|
||||
|
||||
const filteredNotifications = ((await Promise.all(notifications.map(async (notification) => {
|
||||
const isValid = this.#validateNotifier(notification, userIdsWhoMeMuting, userMutedInstances, notifiers);
|
||||
const isValid = this.#validateNotifier(notification, userIdsWhoMeMutingMap, userMutedInstances, notifiers);
|
||||
return isValid ? notification : null;
|
||||
}))) as [T | null] ).filter(x => x != null);
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,12 @@ export class MiMuting {
|
|||
})
|
||||
public expiresAt: Date | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128,
|
||||
default: 'all',
|
||||
})
|
||||
public mutingType: 'all' | 'timelineOnly';
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
|
|
|
|||
|
|
@ -22,6 +22,11 @@ export const packedMutingSchema = {
|
|||
optional: false, nullable: true,
|
||||
format: 'date-time',
|
||||
},
|
||||
mutingType: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
enum: ['all', 'timelineOnly'],
|
||||
},
|
||||
muteeId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
|
|
|
|||
|
|
@ -55,6 +55,12 @@ export const paramDef = {
|
|||
nullable: true,
|
||||
description: 'A Unix Epoch timestamp that must lie in the future. `null` means an indefinite mute.',
|
||||
},
|
||||
mutingType: {
|
||||
type: 'string',
|
||||
enum: ['all', 'timelineOnly'],
|
||||
default: 'all',
|
||||
description: 'Type of muting. `all` mutes everything including notifications. `timelineOnly` mutes only timeline and search, but allows notifications.',
|
||||
},
|
||||
},
|
||||
required: ['userId'],
|
||||
} as const;
|
||||
|
|
@ -98,7 +104,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
return;
|
||||
}
|
||||
|
||||
await this.userMutingService.mute(muter, mutee, ps.expiresAt ? new Date(ps.expiresAt) : null);
|
||||
await this.userMutingService.mute(muter, mutee, ps.expiresAt ? new Date(ps.expiresAt) : null, ps.mutingType ?? 'all');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,12 +80,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
}
|
||||
|
||||
const [
|
||||
userIdsWhoMeMuting,
|
||||
userIdsWhoMeMutingMap,
|
||||
userIdsWhoBlockingMe,
|
||||
] = me ? await Promise.all([
|
||||
this.cacheService.userMutingsCache.fetch(me.id),
|
||||
this.cacheService.userBlockedCache.fetch(me.id),
|
||||
]) : [new Set<string>(), new Set<string>()];
|
||||
]) : [new Map<string, { mutingType: 'all' | 'timelineOnly' }>(), new Set<string>()];
|
||||
|
||||
const userIdsWhoMeMuting = new Set(userIdsWhoMeMutingMap.keys());
|
||||
|
||||
const query = this.notesRepository.createQueryBuilder('note')
|
||||
.where('note.id IN (:...noteIds)', { noteIds: noteIds })
|
||||
|
|
|
|||
|
|
@ -73,10 +73,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
}
|
||||
|
||||
const [
|
||||
userIdsWhoMeMuting,
|
||||
userIdsWhoMeMutingMap,
|
||||
] = me ? await Promise.all([
|
||||
this.cacheService.userMutingsCache.fetch(me.id),
|
||||
]) : [new Set<string>()];
|
||||
]) : [new Map<string, { mutingType: 'all' | 'timelineOnly' }>()];
|
||||
|
||||
// Convert to Set for backward compatibility with isUserRelated
|
||||
const userIdsWhoMeMuting = new Set(userIdsWhoMeMutingMap.keys());
|
||||
|
||||
const query = this.notesRepository.createQueryBuilder('note')
|
||||
.where('note.id IN (:...noteIds)', { noteIds: noteIds })
|
||||
|
|
|
|||
|
|
@ -94,7 +94,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
}
|
||||
}
|
||||
|
||||
const userIdsWhoMeMuting = me ? await this.cacheService.userMutingsCache.fetch(me.id) : new Set<string>();
|
||||
const userIdsWhoMeMutingMap = me ? await this.cacheService.userMutingsCache.fetch(me.id) : new Map<string, { mutingType: 'all' | 'timelineOnly' }>();
|
||||
const userIdsWhoMeMuting = new Set(userIdsWhoMeMutingMap.keys());
|
||||
|
||||
const query = this.queryService.makePaginationQuery(this.noteReactionsRepository.createQueryBuilder('reaction'),
|
||||
ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ export default class Connection {
|
|||
public followingChannels: Set<string> = new Set();
|
||||
public mutingChannels: Set<string> = new Set();
|
||||
public userIdsWhoMeMuting: Set<string> = new Set();
|
||||
public userIdsWhoMeMutingMap: Map<string, { mutingType: 'all' | 'timelineOnly' }> = new Map();
|
||||
public userIdsWhoBlockingMe: Set<string> = new Set();
|
||||
public userIdsWhoMeMutingRenotes: Set<string> = new Set();
|
||||
public userMutedInstances: Set<string> = new Set();
|
||||
|
|
@ -64,7 +65,7 @@ export default class Connection {
|
|||
following,
|
||||
followingChannels,
|
||||
mutingChannels,
|
||||
userIdsWhoMeMuting,
|
||||
userIdsWhoMeMutingMap,
|
||||
userIdsWhoBlockingMe,
|
||||
userIdsWhoMeMutingRenotes,
|
||||
] = await Promise.all([
|
||||
|
|
@ -80,7 +81,8 @@ export default class Connection {
|
|||
this.following = following;
|
||||
this.followingChannels = followingChannels;
|
||||
this.mutingChannels = mutingChannels;
|
||||
this.userIdsWhoMeMuting = userIdsWhoMeMuting;
|
||||
this.userIdsWhoMeMutingMap = userIdsWhoMeMutingMap;
|
||||
this.userIdsWhoMeMuting = new Set(userIdsWhoMeMutingMap.keys());
|
||||
this.userIdsWhoBlockingMe = userIdsWhoBlockingMe;
|
||||
this.userIdsWhoMeMutingRenotes = userIdsWhoMeMutingRenotes;
|
||||
this.userMutedInstances = new Set(userProfile.mutedInstances);
|
||||
|
|
|
|||
Loading…
Reference in New Issue