diff --git a/locales/index.d.ts b/locales/index.d.ts index 7f20c6803f..d6344405b5 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -7802,6 +7802,10 @@ export interface Locale extends ILocale { * This condition may be incorrect for remote users. */ "remoteDataWarning": string; + /** + * Select a user to test the condition. + */ + "selectTestUser": string; }; "_sensitiveMediaDetection": { /** diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index afaa645383..6429e304e5 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -356,6 +356,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