From 683638b570ec41dcd58b4f907e00705cac8a5232 Mon Sep 17 00:00:00 2001 From: bunnybeam Date: Mon, 4 Aug 2025 19:40:00 +0100 Subject: [PATCH] implement conditional role tester --- locales/index.d.ts | 4 +- packages/backend/src/core/RoleService.ts | 33 +++++++ .../backend/src/server/api/endpoint-list.ts | 1 + .../admin/roles/annotate-condition.ts | 71 ++++++++++++++ .../src/pages/admin/RolesEditorFormula.vue | 21 ++++- .../frontend/src/pages/admin/RolesTester.vue | 34 ------- .../frontend/src/pages/admin/roles.editor.vue | 94 ++++++++++++++++++- .../frontend/src/pages/admin/roles.role.vue | 87 +---------------- sharkey-locales/en-US.yml | 2 +- 9 files changed, 222 insertions(+), 125 deletions(-) create mode 100644 packages/backend/src/server/api/endpoints/admin/roles/annotate-condition.ts delete mode 100644 packages/frontend/src/pages/admin/RolesTester.vue diff --git a/locales/index.d.ts b/locales/index.d.ts index 80eb0c054e..65f9e283d0 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -7783,9 +7783,9 @@ export interface Locale extends ILocale { */ "remoteDataWarning": string; /** - * Role tester + * Select a user to test the condition. */ - "roleTester": string; + "selectTestUser": string; }; "_sensitiveMediaDetection": { /** diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index 10e1315dd2..af33b0ad3f 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -354,6 +354,39 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit { } } + @bindThis + public annotateCond(user: MiUser, roles: MiRole[], value: RoleCondFormulaValue, followStats: FollowStats, results: { [k: string]: boolean }): boolean { + let result: boolean; + try { + switch (value.type) { + case 'and': { + result = true; + // Don't use every(), since that short-circuits. + // We need to run annotateCond() on every condition. + value.values.forEach(v => result = this.annotateCond(user, roles, v, followStats, results) && result); + break; + } + case 'or': { + result = false; + value.values.forEach(v => result = this.annotateCond(user, roles, v, followStats, results) || result); + break; + } + case 'not': { + result = !this.annotateCond(user, roles, value.value, followStats, results); + break; + } + default: { + result = this.evalCond(user, roles, value, followStats); + } + } + } catch (err) { + // TODO: log error + result = false; + } + results[value.id] = result; + return result; + } + @bindThis public async getRoles() { const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({})); diff --git a/packages/backend/src/server/api/endpoint-list.ts b/packages/backend/src/server/api/endpoint-list.ts index 962055052d..521710bc13 100644 --- a/packages/backend/src/server/api/endpoint-list.ts +++ b/packages/backend/src/server/api/endpoint-list.ts @@ -86,6 +86,7 @@ export * as 'admin/relays/list' from './endpoints/admin/relays/list.js'; export * as 'admin/relays/remove' from './endpoints/admin/relays/remove.js'; export * as 'admin/reset-password' from './endpoints/admin/reset-password.js'; export * as 'admin/resolve-abuse-user-report' from './endpoints/admin/resolve-abuse-user-report.js'; +export * as 'admin/roles/annotate-condition' from './endpoints/admin/roles/annotate-condition.js'; export * as 'admin/roles/assign' from './endpoints/admin/roles/assign.js'; export * as 'admin/roles/create' from './endpoints/admin/roles/create.js'; export * as 'admin/roles/clone' from './endpoints/admin/roles/clone.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/roles/annotate-condition.ts b/packages/backend/src/server/api/endpoints/admin/roles/annotate-condition.ts new file mode 100644 index 0000000000..aa8cdd5139 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/roles/annotate-condition.ts @@ -0,0 +1,71 @@ +/* + * SPDX-FileCopyrightText: bunnybeam and other Sharkey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import type { UsersRepository } from '@/models/_.js'; +import { DI } from '@/di-symbols.js'; +import { ApiError } from '@/server/api/error.js'; +import { RoleService } from '@/core/RoleService.js'; +import { CacheService } from '@/core/CacheService.js'; + +export const meta = { + tags: ['admin', 'role'], + + requireCredential: true, + requireModerator: true, + kind: 'read:admin:roles', + + errors: { + noSuchUser: { + message: 'No such user.', + code: 'NO_SUCH_USER', + id: '34550050-3115-4443-b389-ce3e62eb9857', + }, + }, + + res: { + type: 'object', + optional: false, nullable: false, + additionalProperties: { type: 'boolean' }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + userId: { type: 'string', format: 'misskey:id' }, + condFormula: { type: 'object' }, + }, + required: [ + 'userId', + 'condFormula', + ], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + private roleService: RoleService, + private cacheService: CacheService, + ) { + super(meta, paramDef, async (ps) => { + const user = await this.usersRepository.findOneBy({ id: ps.userId }); + if (user === null) { + throw new ApiError(meta.errors.noSuchUser); + } + const followStats = await this.cacheService.getFollowStats(ps.userId); + const roles = await this.roleService.getUserRoles(ps.userId); + + const results: { [k: string]: boolean } = {}; + roleService.annotateCond(user, roles, ps.condFormula, followStats, results); + return results; + }); + } +} + diff --git a/packages/frontend/src/pages/admin/RolesEditorFormula.vue b/packages/frontend/src/pages/admin/RolesEditorFormula.vue index b7375b0faf..680f7902a3 100644 --- a/packages/frontend/src/pages/admin/RolesEditorFormula.vue +++ b/packages/frontend/src/pages/admin/RolesEditorFormula.vue @@ -6,6 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/sharkey-locales/en-US.yml b/sharkey-locales/en-US.yml index fac793b4da..52e0da8bc8 100644 --- a/sharkey-locales/en-US.yml +++ b/sharkey-locales/en-US.yml @@ -265,7 +265,7 @@ _role: remoteFollowingLessThanOrEq: "Follows X or fewer remote accounts" remoteFollowingMoreThanOrEq: "Follows X or more remote accounts" remoteDataWarning: "This condition may be incorrect for remote users." - roleTester: "Role tester" + selectTestUser: "Select a user to test the condition." _emailUnavailable: banned: "This email address is banned" _signup: