manage roles and role assignments caches

This commit is contained in:
Hazelnoot 2025-10-01 12:28:08 -04:00
parent a4784eec9a
commit 0e516126f7

View file

@ -17,19 +17,24 @@ import type {
} from '@/models/_.js'; } from '@/models/_.js';
import { MemoryKVCache, MemorySingleCache } from '@/misc/cache.js'; import { MemoryKVCache, MemorySingleCache } from '@/misc/cache.js';
import type { MiUser } from '@/models/User.js'; import type { MiUser } from '@/models/User.js';
import { isLocalUser, isRemoteUser } from '@/models/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 { CacheService } from '@/core/CacheService.js'; import type { CacheService, FollowStats } from '@/core/CacheService.js';
import type { FollowStats } from '@/core/CacheService.js';
import type { RoleCondFormulaValue } from '@/models/Role.js'; import type { RoleCondFormulaValue } from '@/models/Role.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import type { GlobalEvents } from '@/core/GlobalEventService.js'; import type { GlobalEvents } from '@/core/GlobalEventService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js';
import { IdService } from '@/core/IdService.js'; import { IdService } from '@/core/IdService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js'; import { ModerationLogService } from '@/core/ModerationLogService.js';
import type { Packed } from '@/misc/json-schema.js'; import type { Packed } from '@/misc/json-schema.js';
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js'; import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
import { NotificationService } from '@/core/NotificationService.js'; import type { NotificationService } from '@/core/NotificationService.js';
import { TimeService } from '@/core/TimeService.js';
import {
CacheManagementService,
type ManagedMemorySingleCache,
type ManagedMemoryKVCache,
} from '@/core/CacheManagementService.js';
import type { OnApplicationShutdown, OnModuleInit } from '@nestjs/common'; import type { OnApplicationShutdown, OnModuleInit } from '@nestjs/common';
import { getCallerId } from '@/misc/attach-caller-id.js'; import { getCallerId } from '@/misc/attach-caller-id.js';
@ -115,10 +120,14 @@ export const DEFAULT_POLICIES: RolePolicies = {
canViewFederation: true, canViewFederation: true,
}; };
// TODO cache sync fixes (and maybe events too?)
@Injectable() @Injectable()
export class RoleService implements OnApplicationShutdown, OnModuleInit { export class RoleService implements OnApplicationShutdown, OnModuleInit {
private rolesCache: MemorySingleCache<MiRole[]>; private readonly rolesCache: ManagedMemorySingleCache<MiRole[]>;
private roleAssignmentByUserIdCache: MemoryKVCache<MiRoleAssignment[]>; private readonly roleAssignmentByUserIdCache: ManagedMemoryKVCache<MiRoleAssignment[]>;
private cacheService: CacheService;
private notificationService: NotificationService; private notificationService: NotificationService;
public static AlreadyAssignedError = class extends Error {}; public static AlreadyAssignedError = class extends Error {};
@ -145,15 +154,16 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
@Inject(DI.roleAssignmentsRepository) @Inject(DI.roleAssignmentsRepository)
private roleAssignmentsRepository: RoleAssignmentsRepository, private roleAssignmentsRepository: RoleAssignmentsRepository,
private cacheService: CacheService,
private userEntityService: UserEntityService,
private globalEventService: GlobalEventService, private globalEventService: GlobalEventService,
private idService: IdService, private idService: IdService,
private moderationLogService: ModerationLogService, private moderationLogService: ModerationLogService,
private fanoutTimelineService: FanoutTimelineService, private fanoutTimelineService: FanoutTimelineService,
private readonly timeService: TimeService,
cacheManagementService: CacheManagementService,
) { ) {
this.rolesCache = new MemorySingleCache<MiRole[]>(1000 * 60 * 60); // 1h this.rolesCache = cacheManagementService.createMemorySingleCache<MiRole[]>(1000 * 60 * 60); // 1h
this.roleAssignmentByUserIdCache = new MemoryKVCache<MiRoleAssignment[]>(1000 * 60 * 5); // 5m this.roleAssignmentByUserIdCache = cacheManagementService.createMemoryKVCache<MiRoleAssignment[]>(1000 * 60 * 5); // 5m
// TODO additional cache for final calculation? // TODO additional cache for final calculation?
this.redisForSub.on('message', this.onMessage); this.redisForSub.on('message', this.onMessage);
@ -161,7 +171,8 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
@bindThis @bindThis
async onModuleInit() { async onModuleInit() {
this.notificationService = this.moduleRef.get(NotificationService.name); this.notificationService = this.moduleRef.get('NotificationService');
this.cacheService = this.moduleRef.get('CacheService');
} }
@bindThis @bindThis
@ -250,11 +261,11 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
} }
// ローカルユーザのみ // ローカルユーザのみ
case 'isLocal': { case 'isLocal': {
return this.userEntityService.isLocalUser(user); return isLocalUser(user);
} }
// リモートユーザのみ // リモートユーザのみ
case 'isRemote': { case 'isRemote': {
return this.userEntityService.isRemoteUser(user); return isRemoteUser(user);
} }
// User is from a specific instance // User is from a specific instance
case 'isFromInstance': { case 'isFromInstance': {
@ -295,11 +306,11 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
} }
// ユーザが作成されてから指定期間経過した // ユーザが作成されてから指定期間経過した
case 'createdLessThan': { case 'createdLessThan': {
return this.idService.parse(user.id).date.getTime() > (Date.now() - (value.sec * 1000)); return this.idService.parse(user.id).date.getTime() > (this.timeService.now - (value.sec * 1000));
} }
// ユーザが作成されてから指定期間経っていない // ユーザが作成されてから指定期間経っていない
case 'createdMoreThan': { case 'createdMoreThan': {
return this.idService.parse(user.id).date.getTime() < (Date.now() - (value.sec * 1000)); return this.idService.parse(user.id).date.getTime() < (this.timeService.now - (value.sec * 1000));
} }
// フォロワー数が指定値以下 // フォロワー数が指定値以下
case 'followersLessThanOrEq': { case 'followersLessThanOrEq': {
@ -399,7 +410,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
@bindThis @bindThis
public async getUserAssigns(userOrId: MiUser | MiUser['id']) { public async getUserAssigns(userOrId: MiUser | MiUser['id']) {
const now = Date.now(); const now = this.timeService.now;
const userId = typeof(userOrId) === 'object' ? userOrId.id : userOrId; const userId = typeof(userOrId) === 'object' ? userOrId.id : userOrId;
let assigns = await this.roleAssignmentByUserIdCache.fetch(userId, () => this.roleAssignmentsRepository.findBy({ userId })); let assigns = await this.roleAssignmentByUserIdCache.fetch(userId, () => this.roleAssignmentsRepository.findBy({ userId }));
// 期限切れのロールを除外 // 期限切れのロールを除外
@ -438,7 +449,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
*/ */
@bindThis @bindThis
public async getUserBadgeRoles(userOrId: MiUser | MiUser['id']) { public async getUserBadgeRoles(userOrId: MiUser | MiUser['id']) {
const now = Date.now(); const now = this.timeService.now;
const userId = typeof(userOrId) === 'object' ? userOrId.id : userOrId; const userId = typeof(userOrId) === 'object' ? userOrId.id : userOrId;
let assigns = await this.roleAssignmentByUserIdCache.fetch(userId, () => this.roleAssignmentsRepository.findBy({ userId })); let assigns = await this.roleAssignmentByUserIdCache.fetch(userId, () => this.roleAssignmentsRepository.findBy({ userId }));
// 期限切れのロールを除外 // 期限切れのロールを除外
@ -584,7 +595,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
: []; : [];
// Setを経由して重複を除去ユーザIDは重複する可能性があるので // Setを経由して重複を除去ユーザIDは重複する可能性があるので
const now = Date.now(); const now = this.timeService.now;
const resultSet = new Set( const resultSet = new Set(
assigns assigns
.filter(it => .filter(it =>
@ -638,7 +649,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
@bindThis @bindThis
public async assign(userId: MiUser['id'], roleId: MiRole['id'], expiresAt: Date | null = null, moderator?: MiUser): Promise<void> { public async assign(userId: MiUser['id'], roleId: MiRole['id'], expiresAt: Date | null = null, moderator?: MiUser): Promise<void> {
const now = Date.now(); const now = this.timeService.now;
const role = await this.rolesRepository.findOneByOrFail({ id: roleId }); const role = await this.rolesRepository.findOneByOrFail({ id: roleId });
@ -666,7 +677,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
}); });
this.rolesRepository.update(roleId, { this.rolesRepository.update(roleId, {
lastUsedAt: new Date(), lastUsedAt: this.timeService.date,
}); });
this.globalEventService.publishInternalEvent('userRoleAssigned', created); this.globalEventService.publishInternalEvent('userRoleAssigned', created);
@ -693,7 +704,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
@bindThis @bindThis
public async unassign(userId: MiUser['id'], roleId: MiRole['id'], moderator?: MiUser): Promise<void> { public async unassign(userId: MiUser['id'], roleId: MiRole['id'], moderator?: MiUser): Promise<void> {
const now = new Date(); const now = this.timeService.date;
const existing = await this.roleAssignmentsRepository.findOneBy({ roleId, userId }); const existing = await this.roleAssignmentsRepository.findOneBy({ roleId, userId });
if (existing == null) { if (existing == null) {
@ -745,7 +756,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
@bindThis @bindThis
public async create(values: Partial<MiRole>, moderator?: MiUser): Promise<MiRole> { public async create(values: Partial<MiRole>, moderator?: MiUser): Promise<MiRole> {
const date = new Date(); const date = this.timeService.date;
const created = await this.rolesRepository.insertOne({ const created = await this.rolesRepository.insertOne({
id: this.idService.gen(date.getTime()), id: this.idService.gen(date.getTime()),
updatedAt: date, updatedAt: date,
@ -781,7 +792,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
@bindThis @bindThis
public async update(role: MiRole, params: Partial<MiRole>, moderator?: MiUser): Promise<void> { public async update(role: MiRole, params: Partial<MiRole>, moderator?: MiUser): Promise<void> {
const date = new Date(); const date = this.timeService.date;
await this.rolesRepository.update(role.id, { await this.rolesRepository.update(role.id, {
updatedAt: date, updatedAt: date,
...params, ...params,
@ -826,7 +837,6 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
@bindThis @bindThis
public dispose(): void { public dispose(): void {
this.redisForSub.off('message', this.onMessage); this.redisForSub.off('message', this.onMessage);
this.roleAssignmentByUserIdCache.dispose();
} }
@bindThis @bindThis