fix RoleService.

This commit is contained in:
おさむのひと 2024-10-10 21:11:17 +09:00
parent 8868ada741
commit 7cbcd06ef7
4 changed files with 212 additions and 49 deletions

View File

@ -61,7 +61,10 @@ export class AbuseReportNotificationService implements OnApplicationShutdown {
return; return;
} }
const moderatorIds = await this.roleService.getModeratorIds(true, true); const moderatorIds = await this.roleService.getModeratorIds({
includeAdmins: true,
excludeExpire: true,
});
for (const moderatorId of moderatorIds) { for (const moderatorId of moderatorIds) {
for (const abuseReport of abuseReports) { for (const abuseReport of abuseReports) {
@ -370,7 +373,10 @@ export class AbuseReportNotificationService implements OnApplicationShutdown {
} }
// モデレータ権限の有無で通知先設定を振り分ける // モデレータ権限の有無で通知先設定を振り分ける
const authorizedUserIds = await this.roleService.getModeratorIds(true, true); const authorizedUserIds = await this.roleService.getModeratorIds({
includeAdmins: true,
excludeExpire: true,
});
const authorizedUserRecipients = Array.of<MiAbuseReportNotificationRecipient>(); const authorizedUserRecipients = Array.of<MiAbuseReportNotificationRecipient>();
const unauthorizedUserRecipients = Array.of<MiAbuseReportNotificationRecipient>(); const unauthorizedUserRecipients = Array.of<MiAbuseReportNotificationRecipient>();
for (const recipient of userRecipients) { for (const recipient of userRecipients) {

View File

@ -425,8 +425,23 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
return check.isExplorable; return check.isExplorable;
} }
/**
* ID一覧を取得する.
*
* @param opts.includeAdmins (デフォルト: true)
* @param opts.includeRoot rootユーザも含めるか(デフォルト: false)
* @param opts.excludeExpire (デフォルト: false)
*/
@bindThis @bindThis
public async getModeratorIds(includeAdmins = true, excludeExpire = false): Promise<MiUser['id'][]> { public async getModeratorIds(opts?: {
includeAdmins?: boolean,
includeRoot?: boolean,
excludeExpire?: boolean,
}): Promise<MiUser['id'][]> {
const includeAdmins = opts?.includeAdmins ?? true;
const includeRoot = opts?.includeRoot ?? false;
const excludeExpire = opts?.excludeExpire ?? false;
const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({})); const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({}));
const moderatorRoles = includeAdmins const moderatorRoles = includeAdmins
? roles.filter(r => r.isModerator || r.isAdministrator) ? roles.filter(r => r.isModerator || r.isAdministrator)
@ -436,6 +451,19 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
? await this.roleAssignmentsRepository.findBy({ roleId: In(moderatorRoles.map(r => r.id)) }) ? await this.roleAssignmentsRepository.findBy({ roleId: In(moderatorRoles.map(r => r.id)) })
: []; : [];
const now = Date.now();
// Setを経由して重複を除去ユーザIDは重複する可能性があるので
const result = new Set(
assigns
.filter(it =>
(excludeExpire)
? (it.expiresAt == null || it.expiresAt.getTime() > now)
: true,
)
.map(a => a.userId),
);
if (includeRoot) {
const rootUserId = await this.rootUserIdCache.fetch(async () => { const rootUserId = await this.rootUserIdCache.fetch(async () => {
const it = await this.usersRepository.createQueryBuilder('users') const it = await this.usersRepository.createQueryBuilder('users')
.select('id') .select('id')
@ -443,30 +471,19 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
.getOneOrFail(); .getOneOrFail();
return it.id; return it.id;
}); });
result.add(rootUserId);
}
const now = Date.now(); return [...result].sort((x, y) => x.localeCompare(y));
const result = [
// Setを経由して重複を除去ユーザIDは重複する可能性があるので
...new Set(
[
...assigns
.filter(it =>
(excludeExpire)
? (it.expiresAt == null || it.expiresAt.getTime() > now)
: true,
)
.map(a => a.userId),
rootUserId,
],
),
];
return result.sort((x, y) => x.localeCompare(y));
} }
@bindThis @bindThis
public async getModerators(includeAdmins = true, excludeExpire = false): Promise<MiUser[]> { public async getModerators(opts?: {
const ids = await this.getModeratorIds(includeAdmins, excludeExpire); includeAdmins?: boolean,
includeRoot?: boolean,
excludeExpire?: boolean,
}): Promise<MiUser[]> {
const ids = await this.getModeratorIds(opts);
return ids.length > 0 return ids.length > 0
? await this.usersRepository.findBy({ ? await this.usersRepository.findBy({
id: In(ids), id: In(ids),

View File

@ -71,13 +71,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
break; break;
} }
case 'moderator': { case 'moderator': {
const moderatorIds = await this.roleService.getModeratorIds(false); const moderatorIds = await this.roleService.getModeratorIds({ includeAdmins: false });
if (moderatorIds.length === 0) return []; if (moderatorIds.length === 0) return [];
query.where('user.id IN (:...moderatorIds)', { moderatorIds: moderatorIds }); query.where('user.id IN (:...moderatorIds)', { moderatorIds: moderatorIds });
break; break;
} }
case 'adminOrModerator': { case 'adminOrModerator': {
const adminOrModeratorIds = await this.roleService.getModeratorIds(); const adminOrModeratorIds = await this.roleService.getModeratorIds({ includeAdmins: true });
if (adminOrModeratorIds.length === 0) return []; if (adminOrModeratorIds.length === 0) return [];
query.where('user.id IN (:...adminOrModeratorIds)', { adminOrModeratorIds: adminOrModeratorIds }); query.where('user.id IN (:...adminOrModeratorIds)', { adminOrModeratorIds: adminOrModeratorIds });
break; break;

View File

@ -10,6 +10,8 @@ import { jest } from '@jest/globals';
import { ModuleMocker } from 'jest-mock'; import { ModuleMocker } from 'jest-mock';
import { Test } from '@nestjs/testing'; import { Test } from '@nestjs/testing';
import * as lolex from '@sinonjs/fake-timers'; import * as lolex from '@sinonjs/fake-timers';
import type { TestingModule } from '@nestjs/testing';
import type { MockFunctionMetadata } from 'jest-mock';
import { GlobalModule } from '@/GlobalModule.js'; import { GlobalModule } from '@/GlobalModule.js';
import { RoleService } from '@/core/RoleService.js'; import { RoleService } from '@/core/RoleService.js';
import { import {
@ -31,8 +33,6 @@ import { secureRndstr } from '@/misc/secure-rndstr.js';
import { NotificationService } from '@/core/NotificationService.js'; import { NotificationService } from '@/core/NotificationService.js';
import { RoleCondFormulaValue } from '@/models/Role.js'; import { RoleCondFormulaValue } from '@/models/Role.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js';
import type { TestingModule } from '@nestjs/testing';
import type { MockFunctionMetadata } from 'jest-mock';
const moduleMocker = new ModuleMocker(global); const moduleMocker = new ModuleMocker(global);
@ -277,9 +277,9 @@ describe('RoleService', () => {
}); });
describe('getModeratorIds', () => { describe('getModeratorIds', () => {
test('includeAdmins = false, excludeExpire = false', async () => { test('includeAdmins = false, includeRoot = false, excludeExpire = false', async () => {
const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2] = await Promise.all([ const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([
createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser({ isRoot: true }),
]); ]);
const role1 = await createRole({ name: 'admin', isAdministrator: true }); const role1 = await createRole({ name: 'admin', isAdministrator: true });
@ -295,13 +295,17 @@ describe('RoleService', () => {
assignRole({ userId: normalUser2.id, roleId: role3.id, expiresAt: new Date(Date.now() - 1000) }), assignRole({ userId: normalUser2.id, roleId: role3.id, expiresAt: new Date(Date.now() - 1000) }),
]); ]);
const result = await roleService.getModeratorIds(false, false); const result = await roleService.getModeratorIds({
includeAdmins: false,
includeRoot: false,
excludeExpire: false,
});
expect(result).toEqual([modeUser1.id, modeUser2.id]); expect(result).toEqual([modeUser1.id, modeUser2.id]);
}); });
test('includeAdmins = false, excludeExpire = true', async () => { test('includeAdmins = false, includeRoot = false, excludeExpire = true', async () => {
const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2] = await Promise.all([ const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([
createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser({ isRoot: true }),
]); ]);
const role1 = await createRole({ name: 'admin', isAdministrator: true }); const role1 = await createRole({ name: 'admin', isAdministrator: true });
@ -317,13 +321,17 @@ describe('RoleService', () => {
assignRole({ userId: normalUser2.id, roleId: role3.id, expiresAt: new Date(Date.now() - 1000) }), assignRole({ userId: normalUser2.id, roleId: role3.id, expiresAt: new Date(Date.now() - 1000) }),
]); ]);
const result = await roleService.getModeratorIds(false, true); const result = await roleService.getModeratorIds({
includeAdmins: false,
includeRoot: false,
excludeExpire: true,
});
expect(result).toEqual([modeUser1.id]); expect(result).toEqual([modeUser1.id]);
}); });
test('includeAdmins = true, excludeExpire = false', async () => { test('includeAdmins = true, includeRoot = false, excludeExpire = false', async () => {
const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2] = await Promise.all([ const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([
createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser({ isRoot: true }),
]); ]);
const role1 = await createRole({ name: 'admin', isAdministrator: true }); const role1 = await createRole({ name: 'admin', isAdministrator: true });
@ -339,13 +347,17 @@ describe('RoleService', () => {
assignRole({ userId: normalUser2.id, roleId: role3.id, expiresAt: new Date(Date.now() - 1000) }), assignRole({ userId: normalUser2.id, roleId: role3.id, expiresAt: new Date(Date.now() - 1000) }),
]); ]);
const result = await roleService.getModeratorIds(true, false); const result = await roleService.getModeratorIds({
includeAdmins: true,
includeRoot: false,
excludeExpire: false,
});
expect(result).toEqual([adminUser1.id, adminUser2.id, modeUser1.id, modeUser2.id]); expect(result).toEqual([adminUser1.id, adminUser2.id, modeUser1.id, modeUser2.id]);
}); });
test('includeAdmins = true, excludeExpire = true', async () => { test('includeAdmins = true, includeRoot = false, excludeExpire = true', async () => {
const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2] = await Promise.all([ const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([
createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser({ isRoot: true }),
]); ]);
const role1 = await createRole({ name: 'admin', isAdministrator: true }); const role1 = await createRole({ name: 'admin', isAdministrator: true });
@ -361,9 +373,137 @@ describe('RoleService', () => {
assignRole({ userId: normalUser2.id, roleId: role3.id, expiresAt: new Date(Date.now() - 1000) }), assignRole({ userId: normalUser2.id, roleId: role3.id, expiresAt: new Date(Date.now() - 1000) }),
]); ]);
const result = await roleService.getModeratorIds(true, true); const result = await roleService.getModeratorIds({
includeAdmins: true,
includeRoot: false,
excludeExpire: true,
});
expect(result).toEqual([adminUser1.id, modeUser1.id]); expect(result).toEqual([adminUser1.id, modeUser1.id]);
}); });
test('includeAdmins = false, includeRoot = true, excludeExpire = false', async () => {
const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([
createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser({ isRoot: true }),
]);
const role1 = await createRole({ name: 'admin', isAdministrator: true });
const role2 = await createRole({ name: 'moderator', isModerator: true });
const role3 = await createRole({ name: 'normal' });
await Promise.all([
assignRole({ userId: adminUser1.id, roleId: role1.id }),
assignRole({ userId: adminUser2.id, roleId: role1.id, expiresAt: new Date(Date.now() - 1000) }),
assignRole({ userId: modeUser1.id, roleId: role2.id }),
assignRole({ userId: modeUser2.id, roleId: role2.id, expiresAt: new Date(Date.now() - 1000) }),
assignRole({ userId: normalUser1.id, roleId: role3.id }),
assignRole({ userId: normalUser2.id, roleId: role3.id, expiresAt: new Date(Date.now() - 1000) }),
]);
const result = await roleService.getModeratorIds({
includeAdmins: false,
includeRoot: true,
excludeExpire: false,
});
expect(result).toEqual([rootUser.id]);
});
test('includeAdmins = false, includeRoot = true, excludeExpire = false', async () => {
const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([
createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser({ isRoot: true }),
]);
const role1 = await createRole({ name: 'admin', isAdministrator: true });
const role2 = await createRole({ name: 'moderator', isModerator: true });
const role3 = await createRole({ name: 'normal' });
await Promise.all([
assignRole({ userId: adminUser1.id, roleId: role1.id }),
assignRole({ userId: adminUser2.id, roleId: role1.id, expiresAt: new Date(Date.now() - 1000) }),
assignRole({ userId: modeUser1.id, roleId: role2.id }),
assignRole({ userId: modeUser2.id, roleId: role2.id, expiresAt: new Date(Date.now() - 1000) }),
assignRole({ userId: normalUser1.id, roleId: role3.id }),
assignRole({ userId: normalUser2.id, roleId: role3.id, expiresAt: new Date(Date.now() - 1000) }),
]);
const result = await roleService.getModeratorIds({
includeAdmins: false,
includeRoot: true,
excludeExpire: false,
});
expect(result).toEqual([rootUser.id]);
});
test('root has moderator role', async () => {
const [adminUser1, modeUser1, normalUser1, rootUser] = await Promise.all([
createUser(), createUser(), createUser(), createUser({ isRoot: true }),
]);
const role1 = await createRole({ name: 'admin', isAdministrator: true });
const role2 = await createRole({ name: 'moderator', isModerator: true });
const role3 = await createRole({ name: 'normal' });
await Promise.all([
assignRole({ userId: adminUser1.id, roleId: role1.id }),
assignRole({ userId: modeUser1.id, roleId: role2.id }),
assignRole({ userId: rootUser.id, roleId: role2.id }),
assignRole({ userId: normalUser1.id, roleId: role3.id }),
]);
const result = await roleService.getModeratorIds({
includeAdmins: false,
includeRoot: true,
excludeExpire: false,
});
expect(result).toEqual([modeUser1.id, rootUser.id]);
});
test('root has administrator role', async () => {
const [adminUser1, modeUser1, normalUser1, rootUser] = await Promise.all([
createUser(), createUser(), createUser(), createUser({ isRoot: true }),
]);
const role1 = await createRole({ name: 'admin', isAdministrator: true });
const role2 = await createRole({ name: 'moderator', isModerator: true });
const role3 = await createRole({ name: 'normal' });
await Promise.all([
assignRole({ userId: adminUser1.id, roleId: role1.id }),
assignRole({ userId: rootUser.id, roleId: role1.id }),
assignRole({ userId: modeUser1.id, roleId: role2.id }),
assignRole({ userId: normalUser1.id, roleId: role3.id }),
]);
const result = await roleService.getModeratorIds({
includeAdmins: true,
includeRoot: true,
excludeExpire: false,
});
expect(result).toEqual([adminUser1.id, modeUser1.id, rootUser.id]);
});
test('root has moderator role(expire)', async () => {
const [adminUser1, modeUser1, normalUser1, rootUser] = await Promise.all([
createUser(), createUser(), createUser(), createUser({ isRoot: true }),
]);
const role1 = await createRole({ name: 'admin', isAdministrator: true });
const role2 = await createRole({ name: 'moderator', isModerator: true });
const role3 = await createRole({ name: 'normal' });
await Promise.all([
assignRole({ userId: adminUser1.id, roleId: role1.id }),
assignRole({ userId: modeUser1.id, roleId: role2.id, expiresAt: new Date(Date.now() - 1000) }),
assignRole({ userId: rootUser.id, roleId: role2.id, expiresAt: new Date(Date.now() - 1000) }),
assignRole({ userId: normalUser1.id, roleId: role3.id }),
]);
const result = await roleService.getModeratorIds({
includeAdmins: false,
includeRoot: true,
excludeExpire: true,
});
expect(result).toEqual([rootUser.id]);
});
}); });
describe('conditional role', () => { describe('conditional role', () => {