enhance(backend): improve cache

This commit is contained in:
syuilo 2023-04-04 17:32:09 +09:00
parent 7f3afac0a2
commit ecaf152b4a
21 changed files with 184 additions and 89 deletions

View File

@ -1,7 +1,7 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import Redis from 'ioredis'; import Redis from 'ioredis';
import type { UsersRepository } from '@/models/index.js'; import type { UserProfile, UsersRepository } from '@/models/index.js';
import { MemoryKVCache } from '@/misc/cache.js'; import { MemoryKVCache, RedisKVCache } from '@/misc/cache.js';
import type { LocalUser, User } from '@/models/entities/User.js'; import type { LocalUser, User } from '@/models/entities/User.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js';
@ -10,13 +10,18 @@ import { StreamMessages } from '@/server/api/stream/types.js';
import type { OnApplicationShutdown } from '@nestjs/common'; import type { OnApplicationShutdown } from '@nestjs/common';
@Injectable() @Injectable()
export class UserCacheService implements OnApplicationShutdown { export class CacheService implements OnApplicationShutdown {
public userByIdCache: MemoryKVCache<User>; public userByIdCache: MemoryKVCache<User>;
public localUserByNativeTokenCache: MemoryKVCache<LocalUser | null>; public localUserByNativeTokenCache: MemoryKVCache<LocalUser | null>;
public localUserByIdCache: MemoryKVCache<LocalUser>; public localUserByIdCache: MemoryKVCache<LocalUser>;
public uriPersonCache: MemoryKVCache<User | null>; public uriPersonCache: MemoryKVCache<User | null>;
public userProfileCache: RedisKVCache<UserProfile>;
public userMutingsCache: RedisKVCache<string[]>;
constructor( constructor(
@Inject(DI.redis)
private redisClient: Redis.Redis,
@Inject(DI.redisSubscriber) @Inject(DI.redisSubscriber)
private redisSubscriber: Redis.Redis, private redisSubscriber: Redis.Redis,
@ -31,6 +36,8 @@ export class UserCacheService implements OnApplicationShutdown {
this.localUserByNativeTokenCache = new MemoryKVCache<LocalUser | null>(Infinity); this.localUserByNativeTokenCache = new MemoryKVCache<LocalUser | null>(Infinity);
this.localUserByIdCache = new MemoryKVCache<LocalUser>(Infinity); this.localUserByIdCache = new MemoryKVCache<LocalUser>(Infinity);
this.uriPersonCache = new MemoryKVCache<User | null>(Infinity); this.uriPersonCache = new MemoryKVCache<User | null>(Infinity);
this.userProfileCache = new RedisKVCache<UserProfile>(this.redisClient, 'userProfile', 1000 * 60 * 60 * 24, 1000 * 60);
this.userMutingsCache = new RedisKVCache<string[]>(this.redisClient, 'userMutings', 1000 * 60 * 60 * 24, 1000 * 60);
this.redisSubscriber.on('message', this.onMessage); this.redisSubscriber.on('message', this.onMessage);
} }
@ -52,7 +59,7 @@ export class UserCacheService implements OnApplicationShutdown {
} }
} }
if (this.userEntityService.isLocalUser(user)) { if (this.userEntityService.isLocalUser(user)) {
this.localUserByNativeTokenCache.set(user.token, user); this.localUserByNativeTokenCache.set(user.token!, user);
this.localUserByIdCache.set(user.id, user); this.localUserByIdCache.set(user.id, user);
} }
break; break;
@ -77,7 +84,7 @@ export class UserCacheService implements OnApplicationShutdown {
} }
@bindThis @bindThis
public findById(userId: User['id']) { public findUserById(userId: User['id']) {
return this.userByIdCache.fetch(userId, () => this.usersRepository.findOneByOrFail({ id: userId })); return this.userByIdCache.fetch(userId, () => this.usersRepository.findOneByOrFail({ id: userId }));
} }

View File

@ -38,7 +38,7 @@ import { S3Service } from './S3Service.js';
import { SignupService } from './SignupService.js'; import { SignupService } from './SignupService.js';
import { TwoFactorAuthenticationService } from './TwoFactorAuthenticationService.js'; import { TwoFactorAuthenticationService } from './TwoFactorAuthenticationService.js';
import { UserBlockingService } from './UserBlockingService.js'; import { UserBlockingService } from './UserBlockingService.js';
import { UserCacheService } from './UserCacheService.js'; import { CacheService } from './CacheService.js';
import { UserFollowingService } from './UserFollowingService.js'; import { UserFollowingService } from './UserFollowingService.js';
import { UserKeypairStoreService } from './UserKeypairStoreService.js'; import { UserKeypairStoreService } from './UserKeypairStoreService.js';
import { UserListService } from './UserListService.js'; import { UserListService } from './UserListService.js';
@ -159,7 +159,7 @@ const $S3Service: Provider = { provide: 'S3Service', useExisting: S3Service };
const $SignupService: Provider = { provide: 'SignupService', useExisting: SignupService }; const $SignupService: Provider = { provide: 'SignupService', useExisting: SignupService };
const $TwoFactorAuthenticationService: Provider = { provide: 'TwoFactorAuthenticationService', useExisting: TwoFactorAuthenticationService }; const $TwoFactorAuthenticationService: Provider = { provide: 'TwoFactorAuthenticationService', useExisting: TwoFactorAuthenticationService };
const $UserBlockingService: Provider = { provide: 'UserBlockingService', useExisting: UserBlockingService }; const $UserBlockingService: Provider = { provide: 'UserBlockingService', useExisting: UserBlockingService };
const $UserCacheService: Provider = { provide: 'UserCacheService', useExisting: UserCacheService }; const $CacheService: Provider = { provide: 'CacheService', useExisting: CacheService };
const $UserFollowingService: Provider = { provide: 'UserFollowingService', useExisting: UserFollowingService }; const $UserFollowingService: Provider = { provide: 'UserFollowingService', useExisting: UserFollowingService };
const $UserKeypairStoreService: Provider = { provide: 'UserKeypairStoreService', useExisting: UserKeypairStoreService }; const $UserKeypairStoreService: Provider = { provide: 'UserKeypairStoreService', useExisting: UserKeypairStoreService };
const $UserListService: Provider = { provide: 'UserListService', useExisting: UserListService }; const $UserListService: Provider = { provide: 'UserListService', useExisting: UserListService };
@ -282,7 +282,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
SignupService, SignupService,
TwoFactorAuthenticationService, TwoFactorAuthenticationService,
UserBlockingService, UserBlockingService,
UserCacheService, CacheService,
UserFollowingService, UserFollowingService,
UserKeypairStoreService, UserKeypairStoreService,
UserListService, UserListService,
@ -399,7 +399,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$SignupService, $SignupService,
$TwoFactorAuthenticationService, $TwoFactorAuthenticationService,
$UserBlockingService, $UserBlockingService,
$UserCacheService, $CacheService,
$UserFollowingService, $UserFollowingService,
$UserKeypairStoreService, $UserKeypairStoreService,
$UserListService, $UserListService,
@ -517,7 +517,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
SignupService, SignupService,
TwoFactorAuthenticationService, TwoFactorAuthenticationService,
UserBlockingService, UserBlockingService,
UserCacheService, CacheService,
UserFollowingService, UserFollowingService,
UserKeypairStoreService, UserKeypairStoreService,
UserListService, UserListService,
@ -633,7 +633,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$SignupService, $SignupService,
$TwoFactorAuthenticationService, $TwoFactorAuthenticationService,
$UserBlockingService, $UserBlockingService,
$UserCacheService, $CacheService,
$UserFollowingService, $UserFollowingService,
$UserKeypairStoreService, $UserKeypairStoreService,
$UserListService, $UserListService,

View File

@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
import { IsNull } from 'typeorm'; import { IsNull } from 'typeorm';
import type { LocalUser } from '@/models/entities/User.js'; import type { LocalUser } from '@/models/entities/User.js';
import type { UsersRepository } from '@/models/index.js'; import type { UsersRepository } from '@/models/index.js';
import { MemoryKVCache } from '@/misc/cache.js'; import { MemoryCache } from '@/misc/cache.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { CreateSystemUserService } from '@/core/CreateSystemUserService.js'; import { CreateSystemUserService } from '@/core/CreateSystemUserService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
@ -11,7 +11,7 @@ const ACTOR_USERNAME = 'instance.actor' as const;
@Injectable() @Injectable()
export class InstanceActorService { export class InstanceActorService {
private cache: MemoryKVCache<LocalUser>; private cache: MemoryCache<LocalUser>;
constructor( constructor(
@Inject(DI.usersRepository) @Inject(DI.usersRepository)
@ -19,12 +19,12 @@ export class InstanceActorService {
private createSystemUserService: CreateSystemUserService, private createSystemUserService: CreateSystemUserService,
) { ) {
this.cache = new MemoryKVCache<LocalUser>(Infinity); this.cache = new MemoryCache<LocalUser>(Infinity);
} }
@bindThis @bindThis
public async getInstanceActor(): Promise<LocalUser> { public async getInstanceActor(): Promise<LocalUser> {
const cached = this.cache.get(null); const cached = this.cache.get();
if (cached) return cached; if (cached) return cached;
const user = await this.usersRepository.findOneBy({ const user = await this.usersRepository.findOneBy({
@ -33,11 +33,11 @@ export class InstanceActorService {
}) as LocalUser | undefined; }) as LocalUser | undefined;
if (user) { if (user) {
this.cache.set(null, user); this.cache.set(user);
return user; return user;
} else { } else {
const created = await this.createSystemUserService.createSystemUser(ACTOR_USERNAME) as LocalUser; const created = await this.createSystemUserService.createSystemUser(ACTOR_USERNAME) as LocalUser;
this.cache.set(null, created); this.cache.set(created);
return created; return created;
} }
} }

View File

@ -20,7 +20,7 @@ import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js
import { checkWordMute } from '@/misc/check-word-mute.js'; import { checkWordMute } from '@/misc/check-word-mute.js';
import type { Channel } from '@/models/entities/Channel.js'; import type { Channel } from '@/models/entities/Channel.js';
import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js';
import { MemoryKVCache } from '@/misc/cache.js'; import { MemoryCache } from '@/misc/cache.js';
import type { UserProfile } from '@/models/entities/UserProfile.js'; import type { UserProfile } from '@/models/entities/UserProfile.js';
import { RelayService } from '@/core/RelayService.js'; import { RelayService } from '@/core/RelayService.js';
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
@ -47,7 +47,7 @@ import { DB_MAX_NOTE_TEXT_LENGTH } from '@/const.js';
import { RoleService } from '@/core/RoleService.js'; import { RoleService } from '@/core/RoleService.js';
import { MetaService } from '@/core/MetaService.js'; import { MetaService } from '@/core/MetaService.js';
const mutedWordsCache = new MemoryKVCache<{ userId: UserProfile['userId']; mutedWords: UserProfile['mutedWords']; }[]>(1000 * 60 * 5); const mutedWordsCache = new MemoryCache<{ userId: UserProfile['userId']; mutedWords: UserProfile['mutedWords']; }[]>(1000 * 60 * 5);
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention'; type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
@ -473,7 +473,7 @@ export class NoteCreateService implements OnApplicationShutdown {
this.incNotesCountOfUser(user); this.incNotesCountOfUser(user);
// Word mute // Word mute
mutedWordsCache.fetch(null, () => this.userProfilesRepository.find({ mutedWordsCache.fetch(() => this.userProfilesRepository.find({
where: { where: {
enableWordMute: true, enableWordMute: true,
}, },

View File

@ -3,7 +3,7 @@ import Redis from 'ioredis';
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import { In } from 'typeorm'; import { In } from 'typeorm';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { MutingsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; import type { MutingsRepository, UserProfile, UserProfilesRepository, UsersRepository } from '@/models/index.js';
import type { User } from '@/models/entities/User.js'; import type { User } from '@/models/entities/User.js';
import type { Notification } from '@/models/entities/Notification.js'; import type { Notification } from '@/models/entities/Notification.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js';
@ -12,6 +12,7 @@ import { GlobalEventService } from '@/core/GlobalEventService.js';
import { PushNotificationService } from '@/core/PushNotificationService.js'; import { PushNotificationService } from '@/core/PushNotificationService.js';
import { NotificationEntityService } from '@/core/entities/NotificationEntityService.js'; import { NotificationEntityService } from '@/core/entities/NotificationEntityService.js';
import { IdService } from '@/core/IdService.js'; import { IdService } from '@/core/IdService.js';
import { CacheService } from '@/core/CacheService.js';
@Injectable() @Injectable()
export class NotificationService implements OnApplicationShutdown { export class NotificationService implements OnApplicationShutdown {
@ -35,6 +36,7 @@ export class NotificationService implements OnApplicationShutdown {
private idService: IdService, private idService: IdService,
private globalEventService: GlobalEventService, private globalEventService: GlobalEventService,
private pushNotificationService: PushNotificationService, private pushNotificationService: PushNotificationService,
private cacheService: CacheService,
) { ) {
} }
@ -49,7 +51,6 @@ export class NotificationService implements OnApplicationShutdown {
'+', '+',
'-', '-',
'COUNT', 1); 'COUNT', 1);
console.log('latestNotificationIdsRes', latestNotificationIdsRes);
const latestNotificationId = latestNotificationIdsRes[0]?.[0]; const latestNotificationId = latestNotificationIdsRes[0]?.[0];
if (latestNotificationId == null) return; if (latestNotificationId == null) return;
@ -72,9 +73,8 @@ export class NotificationService implements OnApplicationShutdown {
type: Notification['type'], type: Notification['type'],
data: Partial<Notification>, data: Partial<Notification>,
): Promise<Notification | null> { ): Promise<Notification | null> {
// TODO: Cache const profile = await this.cacheService.userProfileCache.fetch(notifieeId, () => this.userProfilesRepository.findOneByOrFail({ userId: notifieeId }));
const profile = await this.userProfilesRepository.findOneBy({ userId: notifieeId }); const isMuted = profile.mutingNotificationTypes.includes(type);
const isMuted = profile?.mutingNotificationTypes.includes(type);
if (isMuted) return null; if (isMuted) return null;
if (data.notifierId) { if (data.notifierId) {
@ -82,12 +82,8 @@ export class NotificationService implements OnApplicationShutdown {
return null; return null;
} }
// TODO: cache const mutings = await this.cacheService.userMutingsCache.fetch(notifieeId, () => this.mutingsRepository.findBy({ muterId: notifieeId }).then(xs => xs.map(x => x.muteeId)));
const mutings = await this.mutingsRepository.findOneBy({ if (mutings.includes(data.notifierId)) {
muterId: notifieeId,
muteeId: data.notifierId,
});
if (mutings) {
return null; return null;
} }
} }

View File

@ -3,7 +3,7 @@ import { IsNull } from 'typeorm';
import type { LocalUser, User } from '@/models/entities/User.js'; import type { LocalUser, User } from '@/models/entities/User.js';
import type { RelaysRepository, UsersRepository } from '@/models/index.js'; import type { RelaysRepository, UsersRepository } from '@/models/index.js';
import { IdService } from '@/core/IdService.js'; import { IdService } from '@/core/IdService.js';
import { MemoryKVCache } from '@/misc/cache.js'; import { MemoryCache } from '@/misc/cache.js';
import type { Relay } from '@/models/entities/Relay.js'; import type { Relay } from '@/models/entities/Relay.js';
import { QueueService } from '@/core/QueueService.js'; import { QueueService } from '@/core/QueueService.js';
import { CreateSystemUserService } from '@/core/CreateSystemUserService.js'; import { CreateSystemUserService } from '@/core/CreateSystemUserService.js';
@ -16,7 +16,7 @@ const ACTOR_USERNAME = 'relay.actor' as const;
@Injectable() @Injectable()
export class RelayService { export class RelayService {
private relaysCache: MemoryKVCache<Relay[]>; private relaysCache: MemoryCache<Relay[]>;
constructor( constructor(
@Inject(DI.usersRepository) @Inject(DI.usersRepository)
@ -30,7 +30,7 @@ export class RelayService {
private createSystemUserService: CreateSystemUserService, private createSystemUserService: CreateSystemUserService,
private apRendererService: ApRendererService, private apRendererService: ApRendererService,
) { ) {
this.relaysCache = new MemoryKVCache<Relay[]>(1000 * 60 * 10); this.relaysCache = new MemoryCache<Relay[]>(1000 * 60 * 10);
} }
@bindThis @bindThis
@ -109,7 +109,7 @@ export class RelayService {
public async deliverToRelays(user: { id: User['id']; host: null; }, activity: any): Promise<void> { public async deliverToRelays(user: { id: User['id']; host: null; }, activity: any): Promise<void> {
if (activity == null) return; if (activity == null) return;
const relays = await this.relaysCache.fetch(null, () => this.relaysRepository.findBy({ const relays = await this.relaysCache.fetch(() => this.relaysRepository.findBy({
status: 'accepted', status: 'accepted',
})); }));
if (relays.length === 0) return; if (relays.length === 0) return;

View File

@ -2,12 +2,12 @@ import { Inject, Injectable } from '@nestjs/common';
import Redis from 'ioredis'; import Redis from 'ioredis';
import { In } from 'typeorm'; import { In } from 'typeorm';
import type { Role, RoleAssignment, RoleAssignmentsRepository, RolesRepository, UsersRepository } from '@/models/index.js'; import type { Role, RoleAssignment, RoleAssignmentsRepository, RolesRepository, UsersRepository } from '@/models/index.js';
import { MemoryKVCache } from '@/misc/cache.js'; import { MemoryKVCache, MemoryCache } from '@/misc/cache.js';
import type { User } from '@/models/entities/User.js'; import type { User } from '@/models/entities/User.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { MetaService } from '@/core/MetaService.js'; import { MetaService } from '@/core/MetaService.js';
import { UserCacheService } from '@/core/UserCacheService.js'; import { CacheService } from '@/core/CacheService.js';
import type { RoleCondFormulaValue } from '@/models/entities/Role.js'; import type { RoleCondFormulaValue } from '@/models/entities/Role.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { StreamMessages } from '@/server/api/stream/types.js'; import { StreamMessages } from '@/server/api/stream/types.js';
@ -57,7 +57,7 @@ export const DEFAULT_POLICIES: RolePolicies = {
@Injectable() @Injectable()
export class RoleService implements OnApplicationShutdown { export class RoleService implements OnApplicationShutdown {
private rolesCache: MemoryKVCache<Role[]>; private rolesCache: MemoryCache<Role[]>;
private roleAssignmentByUserIdCache: MemoryKVCache<RoleAssignment[]>; private roleAssignmentByUserIdCache: MemoryKVCache<RoleAssignment[]>;
public static AlreadyAssignedError = class extends Error {}; public static AlreadyAssignedError = class extends Error {};
@ -77,14 +77,14 @@ export class RoleService implements OnApplicationShutdown {
private roleAssignmentsRepository: RoleAssignmentsRepository, private roleAssignmentsRepository: RoleAssignmentsRepository,
private metaService: MetaService, private metaService: MetaService,
private userCacheService: UserCacheService, private cacheService: CacheService,
private userEntityService: UserEntityService, private userEntityService: UserEntityService,
private globalEventService: GlobalEventService, private globalEventService: GlobalEventService,
private idService: IdService, private idService: IdService,
) { ) {
//this.onMessage = this.onMessage.bind(this); //this.onMessage = this.onMessage.bind(this);
this.rolesCache = new MemoryKVCache<Role[]>(Infinity); this.rolesCache = new MemoryCache<Role[]>(Infinity);
this.roleAssignmentByUserIdCache = new MemoryKVCache<RoleAssignment[]>(Infinity); this.roleAssignmentByUserIdCache = new MemoryKVCache<RoleAssignment[]>(Infinity);
this.redisSubscriber.on('message', this.onMessage); this.redisSubscriber.on('message', this.onMessage);
@ -98,7 +98,7 @@ export class RoleService implements OnApplicationShutdown {
const { type, body } = obj.message as StreamMessages['internal']['payload']; const { type, body } = obj.message as StreamMessages['internal']['payload'];
switch (type) { switch (type) {
case 'roleCreated': { case 'roleCreated': {
const cached = this.rolesCache.get(null); const cached = this.rolesCache.get();
if (cached) { if (cached) {
cached.push({ cached.push({
...body, ...body,
@ -110,7 +110,7 @@ export class RoleService implements OnApplicationShutdown {
break; break;
} }
case 'roleUpdated': { case 'roleUpdated': {
const cached = this.rolesCache.get(null); const cached = this.rolesCache.get();
if (cached) { if (cached) {
const i = cached.findIndex(x => x.id === body.id); const i = cached.findIndex(x => x.id === body.id);
if (i > -1) { if (i > -1) {
@ -125,9 +125,9 @@ export class RoleService implements OnApplicationShutdown {
break; break;
} }
case 'roleDeleted': { case 'roleDeleted': {
const cached = this.rolesCache.get(null); const cached = this.rolesCache.get();
if (cached) { if (cached) {
this.rolesCache.set(null, cached.filter(x => x.id !== body.id)); this.rolesCache.set(cached.filter(x => x.id !== body.id));
} }
break; break;
} }
@ -214,9 +214,9 @@ export class RoleService implements OnApplicationShutdown {
// 期限切れのロールを除外 // 期限切れのロールを除外
assigns = assigns.filter(a => a.expiresAt == null || (a.expiresAt.getTime() > now)); assigns = assigns.filter(a => a.expiresAt == null || (a.expiresAt.getTime() > now));
const assignedRoleIds = assigns.map(x => x.roleId); const assignedRoleIds = assigns.map(x => x.roleId);
const roles = await this.rolesCache.fetch(null, () => this.rolesRepository.findBy({})); const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({}));
const assignedRoles = roles.filter(r => assignedRoleIds.includes(r.id)); const assignedRoles = roles.filter(r => assignedRoleIds.includes(r.id));
const user = roles.some(r => r.target === 'conditional') ? await this.userCacheService.findById(userId) : null; const user = roles.some(r => r.target === 'conditional') ? await this.cacheService.findUserById(userId) : null;
const matchedCondRoles = roles.filter(r => r.target === 'conditional' && this.evalCond(user!, r.condFormula)); const matchedCondRoles = roles.filter(r => r.target === 'conditional' && this.evalCond(user!, r.condFormula));
return [...assignedRoles, ...matchedCondRoles]; return [...assignedRoles, ...matchedCondRoles];
} }
@ -231,11 +231,11 @@ export class RoleService implements OnApplicationShutdown {
// 期限切れのロールを除外 // 期限切れのロールを除外
assigns = assigns.filter(a => a.expiresAt == null || (a.expiresAt.getTime() > now)); assigns = assigns.filter(a => a.expiresAt == null || (a.expiresAt.getTime() > now));
const assignedRoleIds = assigns.map(x => x.roleId); const assignedRoleIds = assigns.map(x => x.roleId);
const roles = await this.rolesCache.fetch(null, () => this.rolesRepository.findBy({})); const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({}));
const assignedBadgeRoles = roles.filter(r => r.asBadge && assignedRoleIds.includes(r.id)); const assignedBadgeRoles = roles.filter(r => r.asBadge && assignedRoleIds.includes(r.id));
const badgeCondRoles = roles.filter(r => r.asBadge && (r.target === 'conditional')); const badgeCondRoles = roles.filter(r => r.asBadge && (r.target === 'conditional'));
if (badgeCondRoles.length > 0) { if (badgeCondRoles.length > 0) {
const user = roles.some(r => r.target === 'conditional') ? await this.userCacheService.findById(userId) : null; const user = roles.some(r => r.target === 'conditional') ? await this.cacheService.findUserById(userId) : null;
const matchedBadgeCondRoles = badgeCondRoles.filter(r => this.evalCond(user!, r.condFormula)); const matchedBadgeCondRoles = badgeCondRoles.filter(r => this.evalCond(user!, r.condFormula));
return [...assignedBadgeRoles, ...matchedBadgeCondRoles]; return [...assignedBadgeRoles, ...matchedBadgeCondRoles];
} else { } else {
@ -301,7 +301,7 @@ export class RoleService implements OnApplicationShutdown {
@bindThis @bindThis
public async getModeratorIds(includeAdmins = true): Promise<User['id'][]> { public async getModeratorIds(includeAdmins = true): Promise<User['id'][]> {
const roles = await this.rolesCache.fetch(null, () => this.rolesRepository.findBy({})); const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({}));
const moderatorRoles = includeAdmins ? roles.filter(r => r.isModerator || r.isAdministrator) : roles.filter(r => r.isModerator); const moderatorRoles = includeAdmins ? roles.filter(r => r.isModerator || r.isAdministrator) : roles.filter(r => r.isModerator);
const assigns = moderatorRoles.length > 0 ? await this.roleAssignmentsRepository.findBy({ const assigns = moderatorRoles.length > 0 ? await this.roleAssignmentsRepository.findBy({
roleId: In(moderatorRoles.map(r => r.id)), roleId: In(moderatorRoles.map(r => r.id)),
@ -321,7 +321,7 @@ export class RoleService implements OnApplicationShutdown {
@bindThis @bindThis
public async getAdministratorIds(): Promise<User['id'][]> { public async getAdministratorIds(): Promise<User['id'][]> {
const roles = await this.rolesCache.fetch(null, () => this.rolesRepository.findBy({})); const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({}));
const administratorRoles = roles.filter(r => r.isAdministrator); const administratorRoles = roles.filter(r => r.isAdministrator);
const assigns = administratorRoles.length > 0 ? await this.roleAssignmentsRepository.findBy({ const assigns = administratorRoles.length > 0 ? await this.roleAssignmentsRepository.findBy({
roleId: In(administratorRoles.map(r => r.id)), roleId: In(administratorRoles.map(r => r.id)),

View File

@ -5,7 +5,7 @@ import type { NotesRepository, UserPublickeysRepository, UsersRepository } from
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import { MemoryKVCache } from '@/misc/cache.js'; import { MemoryKVCache } from '@/misc/cache.js';
import type { UserPublickey } from '@/models/entities/UserPublickey.js'; import type { UserPublickey } from '@/models/entities/UserPublickey.js';
import { UserCacheService } from '@/core/UserCacheService.js'; import { CacheService } from '@/core/CacheService.js';
import type { Note } from '@/models/entities/Note.js'; import type { Note } from '@/models/entities/Note.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { RemoteUser, User } from '@/models/entities/User.js'; import { RemoteUser, User } from '@/models/entities/User.js';
@ -47,7 +47,7 @@ export class ApDbResolverService {
@Inject(DI.userPublickeysRepository) @Inject(DI.userPublickeysRepository)
private userPublickeysRepository: UserPublickeysRepository, private userPublickeysRepository: UserPublickeysRepository,
private userCacheService: UserCacheService, private cacheService: CacheService,
private apPersonService: ApPersonService, private apPersonService: ApPersonService,
) { ) {
this.publicKeyCache = new MemoryKVCache<UserPublickey | null>(Infinity); this.publicKeyCache = new MemoryKVCache<UserPublickey | null>(Infinity);
@ -107,11 +107,11 @@ export class ApDbResolverService {
if (parsed.local) { if (parsed.local) {
if (parsed.type !== 'users') return null; if (parsed.type !== 'users') return null;
return await this.userCacheService.userByIdCache.fetchMaybe(parsed.id, () => this.usersRepository.findOneBy({ return await this.cacheService.userByIdCache.fetchMaybe(parsed.id, () => this.usersRepository.findOneBy({
id: parsed.id, id: parsed.id,
}).then(x => x ?? undefined)) ?? null; }).then(x => x ?? undefined)) ?? null;
} else { } else {
return await this.userCacheService.uriPersonCache.fetch(parsed.uri, () => this.usersRepository.findOneBy({ return await this.cacheService.uriPersonCache.fetch(parsed.uri, () => this.usersRepository.findOneBy({
uri: parsed.uri, uri: parsed.uri,
})); }));
} }
@ -138,7 +138,7 @@ export class ApDbResolverService {
if (key == null) return null; if (key == null) return null;
return { return {
user: await this.userCacheService.findById(key.userId) as RemoteUser, user: await this.cacheService.findUserById(key.userId) as RemoteUser,
key, key,
}; };
} }

View File

@ -8,7 +8,7 @@ import type { Config } from '@/config.js';
import type { RemoteUser } from '@/models/entities/User.js'; import type { RemoteUser } from '@/models/entities/User.js';
import { User } from '@/models/entities/User.js'; import { User } from '@/models/entities/User.js';
import { truncate } from '@/misc/truncate.js'; import { truncate } from '@/misc/truncate.js';
import type { UserCacheService } from '@/core/UserCacheService.js'; import type { CacheService } from '@/core/CacheService.js';
import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js';
import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js'; import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js';
import type Logger from '@/logger.js'; import type Logger from '@/logger.js';
@ -54,7 +54,7 @@ export class ApPersonService implements OnModuleInit {
private metaService: MetaService; private metaService: MetaService;
private federatedInstanceService: FederatedInstanceService; private federatedInstanceService: FederatedInstanceService;
private fetchInstanceMetadataService: FetchInstanceMetadataService; private fetchInstanceMetadataService: FetchInstanceMetadataService;
private userCacheService: UserCacheService; private cacheService: CacheService;
private apResolverService: ApResolverService; private apResolverService: ApResolverService;
private apNoteService: ApNoteService; private apNoteService: ApNoteService;
private apImageService: ApImageService; private apImageService: ApImageService;
@ -97,7 +97,7 @@ export class ApPersonService implements OnModuleInit {
//private metaService: MetaService, //private metaService: MetaService,
//private federatedInstanceService: FederatedInstanceService, //private federatedInstanceService: FederatedInstanceService,
//private fetchInstanceMetadataService: FetchInstanceMetadataService, //private fetchInstanceMetadataService: FetchInstanceMetadataService,
//private userCacheService: UserCacheService, //private cacheService: CacheService,
//private apResolverService: ApResolverService, //private apResolverService: ApResolverService,
//private apNoteService: ApNoteService, //private apNoteService: ApNoteService,
//private apImageService: ApImageService, //private apImageService: ApImageService,
@ -118,7 +118,7 @@ export class ApPersonService implements OnModuleInit {
this.metaService = this.moduleRef.get('MetaService'); this.metaService = this.moduleRef.get('MetaService');
this.federatedInstanceService = this.moduleRef.get('FederatedInstanceService'); this.federatedInstanceService = this.moduleRef.get('FederatedInstanceService');
this.fetchInstanceMetadataService = this.moduleRef.get('FetchInstanceMetadataService'); this.fetchInstanceMetadataService = this.moduleRef.get('FetchInstanceMetadataService');
this.userCacheService = this.moduleRef.get('UserCacheService'); this.cacheService = this.moduleRef.get('CacheService');
this.apResolverService = this.moduleRef.get('ApResolverService'); this.apResolverService = this.moduleRef.get('ApResolverService');
this.apNoteService = this.moduleRef.get('ApNoteService'); this.apNoteService = this.moduleRef.get('ApNoteService');
this.apImageService = this.moduleRef.get('ApImageService'); this.apImageService = this.moduleRef.get('ApImageService');
@ -207,14 +207,14 @@ export class ApPersonService implements OnModuleInit {
public async fetchPerson(uri: string, resolver?: Resolver): Promise<User | null> { public async fetchPerson(uri: string, resolver?: Resolver): Promise<User | null> {
if (typeof uri !== 'string') throw new Error('uri is not string'); if (typeof uri !== 'string') throw new Error('uri is not string');
const cached = this.userCacheService.uriPersonCache.get(uri); const cached = this.cacheService.uriPersonCache.get(uri);
if (cached) return cached; if (cached) return cached;
// URIがこのサーバーを指しているならデータベースからフェッチ // URIがこのサーバーを指しているならデータベースからフェッチ
if (uri.startsWith(this.config.url + '/')) { if (uri.startsWith(this.config.url + '/')) {
const id = uri.split('/').pop(); const id = uri.split('/').pop();
const u = await this.usersRepository.findOneBy({ id }); const u = await this.usersRepository.findOneBy({ id });
if (u) this.userCacheService.uriPersonCache.set(uri, u); if (u) this.cacheService.uriPersonCache.set(uri, u);
return u; return u;
} }
@ -222,7 +222,7 @@ export class ApPersonService implements OnModuleInit {
const exist = await this.usersRepository.findOneBy({ uri }); const exist = await this.usersRepository.findOneBy({ uri });
if (exist) { if (exist) {
this.userCacheService.uriPersonCache.set(uri, exist); this.cacheService.uriPersonCache.set(uri, exist);
return exist; return exist;
} }
//#endregion //#endregion

View File

@ -54,6 +54,7 @@ export class NotificationEntityService implements OnModuleInit {
public async pack( public async pack(
src: Notification, src: Notification,
meId: User['id'], meId: User['id'],
// eslint-disable-next-line @typescript-eslint/ban-types
options: { options: {
}, },

View File

@ -255,7 +255,6 @@ export class UserEntityService implements OnModuleInit {
'+', '+',
'-', '-',
'COUNT', 1); 'COUNT', 1);
console.log('latestNotificationIdsRes', latestNotificationIdsRes);
const latestNotificationId = latestNotificationIdsRes[0]?.[0]; const latestNotificationId = latestNotificationIdsRes[0]?.[0];
return latestNotificationId != null && (latestReadNotificationId == null || latestReadNotificationId < latestNotificationId); return latestNotificationId != null && (latestReadNotificationId == null || latestReadNotificationId < latestNotificationId);

View File

@ -1,9 +1,94 @@
import Redis from 'ioredis';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
// redis通すとDateのインスタンスはstringに変換されるので
type Serialized<T> = {
[K in keyof T]:
T[K] extends Date
? string
: T[K] extends (Date | null)
? (string | null)
: T[K] extends Record<string, any>
? Serialized<T[K]>
: T[K];
};
export class RedisKVCache<T> {
private redisClient: Redis.Redis;
private name: string;
private lifetime: number;
private memoryCache: MemoryKVCache<T>;
constructor(redisClient: RedisKVCache<never>['redisClient'], name: RedisKVCache<never>['name'], lifetime: RedisKVCache<never>['lifetime'], memoryCacheLifetime: number) {
this.redisClient = redisClient;
this.name = name;
this.lifetime = lifetime;
this.memoryCache = new MemoryKVCache(memoryCacheLifetime);
}
@bindThis
public async set(key: string, value: T): Promise<void> {
this.memoryCache.set(key, value);
if (this.lifetime === Infinity) {
await this.redisClient.set(
`kvcache:${this.name}:${key}`,
JSON.stringify(value),
);
} else {
await this.redisClient.set(
`kvcache:${this.name}:${key}`,
JSON.stringify(value),
'ex', Math.round(this.lifetime / 1000),
);
}
}
@bindThis
public async get(key: string): Promise<Serialized<T> | T | undefined> {
const memoryCached = this.memoryCache.get(key);
if (memoryCached !== undefined) return memoryCached;
const cached = await this.redisClient.get(`kvcache:${this.name}:${key}`);
if (cached == null) return undefined;
return JSON.parse(cached);
}
@bindThis
public async delete(key: string): Promise<void> {
this.memoryCache.delete(key);
await this.redisClient.del(`kvcache:${this.name}:${key}`);
}
/**
* fetcherを呼び出して結果をキャッシュ&
* optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします
*/
@bindThis
public async fetch(key: string, fetcher: () => Promise<T>, validator?: (cachedValue: Serialized<T> | T) => boolean): Promise<Serialized<T> | T> {
const cachedValue = await this.get(key);
if (cachedValue !== undefined) {
if (validator) {
if (validator(cachedValue)) {
// Cache HIT
return cachedValue;
}
} else {
// Cache HIT
return cachedValue;
}
}
// Cache MISS
const value = await fetcher();
this.set(key, value);
return value;
}
}
// TODO: メモリ節約のためあまり参照されないキーを定期的に削除できるようにする? // TODO: メモリ節約のためあまり参照されないキーを定期的に削除できるようにする?
export class MemoryKVCache<T> { export class MemoryKVCache<T> {
public cache: Map<string | null, { date: number; value: T; }>; public cache: Map<string, { date: number; value: T; }>;
private lifetime: number; private lifetime: number;
constructor(lifetime: MemoryKVCache<never>['lifetime']) { constructor(lifetime: MemoryKVCache<never>['lifetime']) {
@ -12,7 +97,7 @@ export class MemoryKVCache<T> {
} }
@bindThis @bindThis
public set(key: string | null, value: T): void { public set(key: string, value: T): void {
this.cache.set(key, { this.cache.set(key, {
date: Date.now(), date: Date.now(),
value, value,
@ -20,7 +105,7 @@ export class MemoryKVCache<T> {
} }
@bindThis @bindThis
public get(key: string | null): T | undefined { public get(key: string): T | undefined {
const cached = this.cache.get(key); const cached = this.cache.get(key);
if (cached == null) return undefined; if (cached == null) return undefined;
if ((Date.now() - cached.date) > this.lifetime) { if ((Date.now() - cached.date) > this.lifetime) {
@ -31,7 +116,7 @@ export class MemoryKVCache<T> {
} }
@bindThis @bindThis
public delete(key: string | null) { public delete(key: string) {
this.cache.delete(key); this.cache.delete(key);
} }
@ -40,7 +125,7 @@ export class MemoryKVCache<T> {
* optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします * optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします
*/ */
@bindThis @bindThis
public async fetch(key: string | null, fetcher: () => Promise<T>, validator?: (cachedValue: T) => boolean): Promise<T> { public async fetch(key: string, fetcher: () => Promise<T>, validator?: (cachedValue: T) => boolean): Promise<T> {
const cachedValue = this.get(key); const cachedValue = this.get(key);
if (cachedValue !== undefined) { if (cachedValue !== undefined) {
if (validator) { if (validator) {
@ -65,7 +150,7 @@ export class MemoryKVCache<T> {
* optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします * optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします
*/ */
@bindThis @bindThis
public async fetchMaybe(key: string | null, fetcher: () => Promise<T | undefined>, validator?: (cachedValue: T) => boolean): Promise<T | undefined> { public async fetchMaybe(key: string, fetcher: () => Promise<T | undefined>, validator?: (cachedValue: T) => boolean): Promise<T | undefined> {
const cachedValue = this.get(key); const cachedValue = this.get(key);
if (cachedValue !== undefined) { if (cachedValue !== undefined) {
if (validator) { if (validator) {

View File

@ -7,7 +7,7 @@ import { MetaService } from '@/core/MetaService.js';
import { ApRequestService } from '@/core/activitypub/ApRequestService.js'; import { ApRequestService } from '@/core/activitypub/ApRequestService.js';
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
import { FetchInstanceMetadataService } from '@/core/FetchInstanceMetadataService.js'; import { FetchInstanceMetadataService } from '@/core/FetchInstanceMetadataService.js';
import { MemoryKVCache } from '@/misc/cache.js'; import { MemoryCache } from '@/misc/cache.js';
import type { Instance } from '@/models/entities/Instance.js'; import type { Instance } from '@/models/entities/Instance.js';
import InstanceChart from '@/core/chart/charts/instance.js'; import InstanceChart from '@/core/chart/charts/instance.js';
import ApRequestChart from '@/core/chart/charts/ap-request.js'; import ApRequestChart from '@/core/chart/charts/ap-request.js';
@ -22,7 +22,7 @@ import type { DeliverJobData } from '../types.js';
@Injectable() @Injectable()
export class DeliverProcessorService { export class DeliverProcessorService {
private logger: Logger; private logger: Logger;
private suspendedHostsCache: MemoryKVCache<Instance[]>; private suspendedHostsCache: MemoryCache<Instance[]>;
private latest: string | null; private latest: string | null;
constructor( constructor(
@ -46,7 +46,7 @@ export class DeliverProcessorService {
private queueLoggerService: QueueLoggerService, private queueLoggerService: QueueLoggerService,
) { ) {
this.logger = this.queueLoggerService.logger.createSubLogger('deliver'); this.logger = this.queueLoggerService.logger.createSubLogger('deliver');
this.suspendedHostsCache = new MemoryKVCache<Instance[]>(1000 * 60 * 60); this.suspendedHostsCache = new MemoryCache<Instance[]>(1000 * 60 * 60);
} }
@bindThis @bindThis
@ -60,14 +60,14 @@ export class DeliverProcessorService {
} }
// isSuspendedなら中断 // isSuspendedなら中断
let suspendedHosts = this.suspendedHostsCache.get(null); let suspendedHosts = this.suspendedHostsCache.get();
if (suspendedHosts == null) { if (suspendedHosts == null) {
suspendedHosts = await this.instancesRepository.find({ suspendedHosts = await this.instancesRepository.find({
where: { where: {
isSuspended: true, isSuspended: true,
}, },
}); });
this.suspendedHostsCache.set(null, suspendedHosts); this.suspendedHostsCache.set(suspendedHosts);
} }
if (suspendedHosts.map(x => x.host).includes(this.utilityService.toPuny(host))) { if (suspendedHosts.map(x => x.host).includes(this.utilityService.toPuny(host))) {
return 'skip (suspended)'; return 'skip (suspended)';

View File

@ -4,7 +4,7 @@ import type { NotesRepository, UsersRepository } from '@/models/index.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import { MetaService } from '@/core/MetaService.js'; import { MetaService } from '@/core/MetaService.js';
import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
import { MemoryKVCache } from '@/misc/cache.js'; import { MemoryCache } from '@/misc/cache.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import NotesChart from '@/core/chart/charts/notes.js'; import NotesChart from '@/core/chart/charts/notes.js';
@ -118,17 +118,17 @@ export class NodeinfoServerService {
}; };
}; };
const cache = new MemoryKVCache<Awaited<ReturnType<typeof nodeinfo2>>>(1000 * 60 * 10); const cache = new MemoryCache<Awaited<ReturnType<typeof nodeinfo2>>>(1000 * 60 * 10);
fastify.get(nodeinfo2_1path, async (request, reply) => { fastify.get(nodeinfo2_1path, async (request, reply) => {
const base = await cache.fetch(null, () => nodeinfo2()); const base = await cache.fetch(() => nodeinfo2());
reply.header('Cache-Control', 'public, max-age=600'); reply.header('Cache-Control', 'public, max-age=600');
return { version: '2.1', ...base }; return { version: '2.1', ...base };
}); });
fastify.get(nodeinfo2_0path, async (request, reply) => { fastify.get(nodeinfo2_0path, async (request, reply) => {
const base = await cache.fetch(null, () => nodeinfo2()); const base = await cache.fetch(() => nodeinfo2());
delete (base as any).software.repository; delete (base as any).software.repository;

View File

@ -5,7 +5,7 @@ import type { LocalUser } from '@/models/entities/User.js';
import type { AccessToken } from '@/models/entities/AccessToken.js'; import type { AccessToken } from '@/models/entities/AccessToken.js';
import { MemoryKVCache } from '@/misc/cache.js'; import { MemoryKVCache } from '@/misc/cache.js';
import type { App } from '@/models/entities/App.js'; import type { App } from '@/models/entities/App.js';
import { UserCacheService } from '@/core/UserCacheService.js'; import { CacheService } from '@/core/CacheService.js';
import isNativeToken from '@/misc/is-native-token.js'; import isNativeToken from '@/misc/is-native-token.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
@ -30,7 +30,7 @@ export class AuthenticateService {
@Inject(DI.appsRepository) @Inject(DI.appsRepository)
private appsRepository: AppsRepository, private appsRepository: AppsRepository,
private userCacheService: UserCacheService, private cacheService: CacheService,
) { ) {
this.appCache = new MemoryKVCache<App>(Infinity); this.appCache = new MemoryKVCache<App>(Infinity);
} }
@ -42,7 +42,7 @@ export class AuthenticateService {
} }
if (isNativeToken(token)) { if (isNativeToken(token)) {
const user = await this.userCacheService.localUserByNativeTokenCache.fetch(token, const user = await this.cacheService.localUserByNativeTokenCache.fetch(token,
() => this.usersRepository.findOneBy({ token }) as Promise<LocalUser | null>); () => this.usersRepository.findOneBy({ token }) as Promise<LocalUser | null>);
if (user == null) { if (user == null) {
@ -67,7 +67,7 @@ export class AuthenticateService {
lastUsedAt: new Date(), lastUsedAt: new Date(),
}); });
const user = await this.userCacheService.localUserByIdCache.fetch(accessToken.userId, const user = await this.cacheService.localUserByIdCache.fetch(accessToken.userId,
() => this.usersRepository.findOneBy({ () => this.usersRepository.findOneBy({
id: accessToken.userId, id: accessToken.userId,
}) as Promise<LocalUser>); }) as Promise<LocalUser>);

View File

@ -34,7 +34,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
) { ) {
super(meta, paramDef, async (ps, me) => { super(meta, paramDef, async (ps, me) => {
const freshUser = await this.usersRepository.findOneByOrFail({ id: me.id }); const freshUser = await this.usersRepository.findOneByOrFail({ id: me.id });
const oldToken = freshUser.token; const oldToken = freshUser.token!;
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id }); const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id });

View File

@ -18,6 +18,7 @@ import { AccountUpdateService } from '@/core/AccountUpdateService.js';
import { HashtagService } from '@/core/HashtagService.js'; import { HashtagService } from '@/core/HashtagService.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { RoleService } from '@/core/RoleService.js'; import { RoleService } from '@/core/RoleService.js';
import { CacheService } from '@/core/CacheService.js';
import { ApiError } from '../../error.js'; import { ApiError } from '../../error.js';
export const meta = { export const meta = {
@ -152,6 +153,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
private accountUpdateService: AccountUpdateService, private accountUpdateService: AccountUpdateService,
private hashtagService: HashtagService, private hashtagService: HashtagService,
private roleService: RoleService, private roleService: RoleService,
private cacheService: CacheService,
) { ) {
super(meta, paramDef, async (ps, _user, token) => { super(meta, paramDef, async (ps, _user, token) => {
const user = await this.usersRepository.findOneByOrFail({ id: _user.id }); const user = await this.usersRepository.findOneByOrFail({ id: _user.id });
@ -276,9 +278,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
includeSecrets: isSecure, includeSecrets: isSecure,
}); });
const updatedProfile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
this.cacheService.userProfileCache.set(user.id, updatedProfile);
// Publish meUpdated event // Publish meUpdated event
this.globalEventService.publishMainStream(user.id, 'meUpdated', iObj); this.globalEventService.publishMainStream(user.id, 'meUpdated', iObj);
this.globalEventService.publishUserEvent(user.id, 'updateUserProfile', await this.userProfilesRepository.findOneByOrFail({ userId: user.id })); this.globalEventService.publishUserEvent(user.id, 'updateUserProfile', updatedProfile);
// 鍵垢を解除したとき、溜まっていたフォローリクエストがあるならすべて承認 // 鍵垢を解除したとき、溜まっていたフォローリクエストがあるならすべて承認
if (user.isLocked && ps.isLocked === false) { if (user.isLocked && ps.isLocked === false) {

View File

@ -7,6 +7,7 @@ import type { Muting } from '@/models/entities/Muting.js';
import { GlobalEventService } from '@/core/GlobalEventService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { GetterService } from '@/server/api/GetterService.js'; import { GetterService } from '@/server/api/GetterService.js';
import { CacheService } from '@/core/CacheService.js';
import { ApiError } from '../../error.js'; import { ApiError } from '../../error.js';
export const meta = { export const meta = {
@ -65,6 +66,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
private globalEventService: GlobalEventService, private globalEventService: GlobalEventService,
private getterService: GetterService, private getterService: GetterService,
private idService: IdService, private idService: IdService,
private cacheService: CacheService,
) { ) {
super(meta, paramDef, async (ps, me) => { super(meta, paramDef, async (ps, me) => {
const muter = me; const muter = me;
@ -103,6 +105,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
muteeId: mutee.id, muteeId: mutee.id,
} as Muting); } as Muting);
this.cacheService.userMutingsCache.delete(muter.id);
this.globalEventService.publishUserEvent(me.id, 'mute', mutee); this.globalEventService.publishUserEvent(me.id, 'mute', mutee);
}); });
} }

View File

@ -92,8 +92,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
muterId: muter.id, muterId: muter.id,
muteeId: mutee.id, muteeId: mutee.id,
} as RenoteMuting); } as RenoteMuting);
// publishUserEvent(user.id, 'mute', mutee);
}); });
} }
} }

View File

@ -19,7 +19,7 @@ import type { EventEmitter } from 'events';
//#region Stream type-body definitions //#region Stream type-body definitions
export interface InternalStreamTypes { export interface InternalStreamTypes {
userChangeSuspendedState: { id: User['id']; isSuspended: User['isSuspended']; }; userChangeSuspendedState: { id: User['id']; isSuspended: User['isSuspended']; };
userTokenRegenerated: { id: User['id']; oldToken: User['token']; newToken: User['token']; }; userTokenRegenerated: { id: User['id']; oldToken: string; newToken: string; };
remoteUserUpdated: { id: User['id']; }; remoteUserUpdated: { id: User['id']; };
follow: { followerId: User['id']; followeeId: User['id']; }; follow: { followerId: User['id']; followeeId: User['id']; };
unfollow: { followerId: User['id']; followeeId: User['id']; }; unfollow: { followerId: User['id']; followeeId: User['id']; };

View File

@ -11,7 +11,7 @@ import type { Role, RolesRepository, RoleAssignmentsRepository, UsersRepository,
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { MetaService } from '@/core/MetaService.js'; import { MetaService } from '@/core/MetaService.js';
import { genAid } from '@/misc/id/aid.js'; import { genAid } from '@/misc/id/aid.js';
import { UserCacheService } from '@/core/UserCacheService.js'; import { CacheService } from '@/core/CacheService.js';
import { IdService } from '@/core/IdService.js'; import { IdService } from '@/core/IdService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js';
import { sleep } from '../utils.js'; import { sleep } from '../utils.js';
@ -65,7 +65,7 @@ describe('RoleService', () => {
], ],
providers: [ providers: [
RoleService, RoleService,
UserCacheService, CacheService,
IdService, IdService,
GlobalEventService, GlobalEventService,
], ],