mirror of
https://github.com/misskey-dev/misskey.git
synced 2024-12-27 23:20:21 +01:00
enhance: 新しいコンディショナルロール条件の実装 (#13732)
* enhance: 新しいコンディショナルロールの実装 * fix: CHANGELOG.md
This commit is contained in:
parent
ea9aa6fdb4
commit
cd7f7271ca
12 changed files with 624 additions and 74 deletions
|
@ -8,6 +8,12 @@
|
|||
- Enhance: アンテナでBotによるノートを除外できるように
|
||||
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/545)
|
||||
- Enhance: クリップのノート数を表示するように
|
||||
- Enhance: コンディショナルロールの条件として以下を新たに追加 (#13667)
|
||||
- 猫ユーザーか
|
||||
- botユーザーか
|
||||
- サスペンド済みユーザーか
|
||||
- 鍵アカウントユーザーか
|
||||
- 「アカウントを見つけやすくする」が有効なユーザーか
|
||||
- Fix: Play作成時に設定した公開範囲が機能していない問題を修正
|
||||
|
||||
### Client
|
||||
|
|
20
locales/index.d.ts
vendored
20
locales/index.d.ts
vendored
|
@ -6592,6 +6592,26 @@ export interface Locale extends ILocale {
|
|||
* リモートユーザー
|
||||
*/
|
||||
"isRemote": string;
|
||||
/**
|
||||
* 猫ユーザー
|
||||
*/
|
||||
"isCat": string;
|
||||
/**
|
||||
* botユーザー
|
||||
*/
|
||||
"isBot": string;
|
||||
/**
|
||||
* サスペンド済みユーザー
|
||||
*/
|
||||
"isSuspended": string;
|
||||
/**
|
||||
* 鍵アカウントユーザー
|
||||
*/
|
||||
"isLocked": string;
|
||||
/**
|
||||
* 「アカウントを見つけやすくする」が有効なユーザー
|
||||
*/
|
||||
"isExplorable": string;
|
||||
/**
|
||||
* アカウント作成から~以内
|
||||
*/
|
||||
|
|
|
@ -1703,6 +1703,11 @@ _role:
|
|||
roleAssignedTo: "マニュアルロールにアサイン済み"
|
||||
isLocal: "ローカルユーザー"
|
||||
isRemote: "リモートユーザー"
|
||||
isCat: "猫ユーザー"
|
||||
isBot: "botユーザー"
|
||||
isSuspended: "サスペンド済みユーザー"
|
||||
isLocked: "鍵アカウントユーザー"
|
||||
isExplorable: "「アカウントを見つけやすくする」が有効なユーザー"
|
||||
createdLessThan: "アカウント作成から~以内"
|
||||
createdMoreThan: "アカウント作成から~経過"
|
||||
followersLessThanOrEq: "フォロワー数が~以下"
|
||||
|
|
|
@ -205,45 +205,79 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
|||
private evalCond(user: MiUser, roles: MiRole[], value: RoleCondFormulaValue): boolean {
|
||||
try {
|
||||
switch (value.type) {
|
||||
// ~かつ~
|
||||
case 'and': {
|
||||
return value.values.every(v => this.evalCond(user, roles, v));
|
||||
}
|
||||
// ~または~
|
||||
case 'or': {
|
||||
return value.values.some(v => this.evalCond(user, roles, v));
|
||||
}
|
||||
// ~ではない
|
||||
case 'not': {
|
||||
return !this.evalCond(user, roles, value.value);
|
||||
}
|
||||
// マニュアルロールがアサインされている
|
||||
case 'roleAssignedTo': {
|
||||
return roles.some(r => r.id === value.roleId);
|
||||
}
|
||||
// ローカルユーザのみ
|
||||
case 'isLocal': {
|
||||
return this.userEntityService.isLocalUser(user);
|
||||
}
|
||||
// リモートユーザのみ
|
||||
case 'isRemote': {
|
||||
return this.userEntityService.isRemoteUser(user);
|
||||
}
|
||||
// サスペンド済みユーザである
|
||||
case 'isSuspended': {
|
||||
return user.isSuspended;
|
||||
}
|
||||
// 鍵アカウントユーザである
|
||||
case 'isLocked': {
|
||||
return user.isLocked;
|
||||
}
|
||||
// botユーザである
|
||||
case 'isBot': {
|
||||
return user.isBot;
|
||||
}
|
||||
// 猫である
|
||||
case 'isCat': {
|
||||
return user.isCat;
|
||||
}
|
||||
// 「ユーザを見つけやすくする」が有効なアカウント
|
||||
case 'isExplorable': {
|
||||
return user.isExplorable;
|
||||
}
|
||||
// ユーザが作成されてから指定期間経過した
|
||||
case 'createdLessThan': {
|
||||
return this.idService.parse(user.id).date.getTime() > (Date.now() - (value.sec * 1000));
|
||||
}
|
||||
// ユーザが作成されてから指定期間経っていない
|
||||
case 'createdMoreThan': {
|
||||
return this.idService.parse(user.id).date.getTime() < (Date.now() - (value.sec * 1000));
|
||||
}
|
||||
// フォロワー数が指定値以下
|
||||
case 'followersLessThanOrEq': {
|
||||
return user.followersCount <= value.value;
|
||||
}
|
||||
// フォロワー数が指定値以上
|
||||
case 'followersMoreThanOrEq': {
|
||||
return user.followersCount >= value.value;
|
||||
}
|
||||
// フォロー数が指定値以下
|
||||
case 'followingLessThanOrEq': {
|
||||
return user.followingCount <= value.value;
|
||||
}
|
||||
// フォロー数が指定値以上
|
||||
case 'followingMoreThanOrEq': {
|
||||
return user.followingCount >= value.value;
|
||||
}
|
||||
// ノート数が指定値以下
|
||||
case 'notesLessThanOrEq': {
|
||||
return user.notesCount <= value.value;
|
||||
}
|
||||
// ノート数が指定値以上
|
||||
case 'notesMoreThanOrEq': {
|
||||
return user.notesCount >= value.value;
|
||||
}
|
||||
|
|
|
@ -48,6 +48,7 @@ import {
|
|||
packedRoleCondFormulaValueCreatedSchema,
|
||||
packedRoleCondFormulaFollowersOrFollowingOrNotesSchema,
|
||||
packedRoleCondFormulaValueSchema,
|
||||
packedRoleCondFormulaValueUserSettingBooleanSchema,
|
||||
} from '@/models/json-schema/role.js';
|
||||
import { packedAdSchema } from '@/models/json-schema/ad.js';
|
||||
import { packedReversiGameLiteSchema, packedReversiGameDetailedSchema } from '@/models/json-schema/reversi-game.js';
|
||||
|
@ -97,6 +98,7 @@ export const refs = {
|
|||
RoleCondFormulaLogics: packedRoleCondFormulaLogicsSchema,
|
||||
RoleCondFormulaValueNot: packedRoleCondFormulaValueNot,
|
||||
RoleCondFormulaValueIsLocalOrRemote: packedRoleCondFormulaValueIsLocalOrRemoteSchema,
|
||||
RoleCondFormulaValueUserSettingBooleanSchema: packedRoleCondFormulaValueUserSettingBooleanSchema,
|
||||
RoleCondFormulaValueAssignedRole: packedRoleCondFormulaValueAssignedRoleSchema,
|
||||
RoleCondFormulaValueCreated: packedRoleCondFormulaValueCreatedSchema,
|
||||
RoleCondFormulaFollowersOrFollowingOrNotes: packedRoleCondFormulaFollowersOrFollowingOrNotesSchema,
|
||||
|
|
|
@ -6,69 +6,149 @@
|
|||
import { Entity, Column, PrimaryColumn } from 'typeorm';
|
||||
import { id } from './util/id.js';
|
||||
|
||||
/**
|
||||
* ~かつ~
|
||||
* 複数の条件を同時に満たす場合のみ成立とする
|
||||
*/
|
||||
type CondFormulaValueAnd = {
|
||||
type: 'and';
|
||||
values: RoleCondFormulaValue[];
|
||||
};
|
||||
|
||||
/**
|
||||
* ~または~
|
||||
* 複数の条件のうち、いずれかを満たす場合のみ成立とする
|
||||
*/
|
||||
type CondFormulaValueOr = {
|
||||
type: 'or';
|
||||
values: RoleCondFormulaValue[];
|
||||
};
|
||||
|
||||
/**
|
||||
* ~ではない
|
||||
* 条件を満たさない場合のみ成立とする
|
||||
*/
|
||||
type CondFormulaValueNot = {
|
||||
type: 'not';
|
||||
value: RoleCondFormulaValue;
|
||||
};
|
||||
|
||||
/**
|
||||
* ローカルユーザーのみ成立とする
|
||||
*/
|
||||
type CondFormulaValueIsLocal = {
|
||||
type: 'isLocal';
|
||||
};
|
||||
|
||||
/**
|
||||
* リモートユーザーのみ成立とする
|
||||
*/
|
||||
type CondFormulaValueIsRemote = {
|
||||
type: 'isRemote';
|
||||
};
|
||||
|
||||
/**
|
||||
* 既に指定のマニュアルロールにアサインされている場合のみ成立とする
|
||||
*/
|
||||
type CondFormulaValueRoleAssignedTo = {
|
||||
type: 'roleAssignedTo';
|
||||
roleId: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* サスペンド済みアカウントの場合のみ成立とする
|
||||
*/
|
||||
type CondFormulaValueIsSuspended = {
|
||||
type: 'isSuspended';
|
||||
};
|
||||
|
||||
/**
|
||||
* 鍵アカウントの場合のみ成立とする
|
||||
*/
|
||||
type CondFormulaValueIsLocked = {
|
||||
type: 'isLocked';
|
||||
};
|
||||
|
||||
/**
|
||||
* botアカウントの場合のみ成立とする
|
||||
*/
|
||||
type CondFormulaValueIsBot = {
|
||||
type: 'isBot';
|
||||
};
|
||||
|
||||
/**
|
||||
* 猫アカウントの場合のみ成立とする
|
||||
*/
|
||||
type CondFormulaValueIsCat = {
|
||||
type: 'isCat';
|
||||
};
|
||||
|
||||
/**
|
||||
* 「ユーザを見つけやすくする」が有効なアカウントの場合のみ成立とする
|
||||
*/
|
||||
type CondFormulaValueIsExplorable = {
|
||||
type: 'isExplorable';
|
||||
};
|
||||
|
||||
/**
|
||||
* ユーザが作成されてから指定期間経過した場合のみ成立とする
|
||||
*/
|
||||
type CondFormulaValueCreatedLessThan = {
|
||||
type: 'createdLessThan';
|
||||
sec: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* ユーザが作成されてから指定期間経っていない場合のみ成立とする
|
||||
*/
|
||||
type CondFormulaValueCreatedMoreThan = {
|
||||
type: 'createdMoreThan';
|
||||
sec: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* フォロワー数が指定値以下の場合のみ成立とする
|
||||
*/
|
||||
type CondFormulaValueFollowersLessThanOrEq = {
|
||||
type: 'followersLessThanOrEq';
|
||||
value: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* フォロワー数が指定値以上の場合のみ成立とする
|
||||
*/
|
||||
type CondFormulaValueFollowersMoreThanOrEq = {
|
||||
type: 'followersMoreThanOrEq';
|
||||
value: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* フォロー数が指定値以下の場合のみ成立とする
|
||||
*/
|
||||
type CondFormulaValueFollowingLessThanOrEq = {
|
||||
type: 'followingLessThanOrEq';
|
||||
value: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* フォロー数が指定値以上の場合のみ成立とする
|
||||
*/
|
||||
type CondFormulaValueFollowingMoreThanOrEq = {
|
||||
type: 'followingMoreThanOrEq';
|
||||
value: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* 投稿数が指定値以下の場合のみ成立とする
|
||||
*/
|
||||
type CondFormulaValueNotesLessThanOrEq = {
|
||||
type: 'notesLessThanOrEq';
|
||||
value: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* 投稿数が指定値以上の場合のみ成立とする
|
||||
*/
|
||||
type CondFormulaValueNotesMoreThanOrEq = {
|
||||
type: 'notesMoreThanOrEq';
|
||||
value: number;
|
||||
|
@ -80,6 +160,11 @@ export type RoleCondFormulaValue = { id: string } & (
|
|||
CondFormulaValueNot |
|
||||
CondFormulaValueIsLocal |
|
||||
CondFormulaValueIsRemote |
|
||||
CondFormulaValueIsSuspended |
|
||||
CondFormulaValueIsLocked |
|
||||
CondFormulaValueIsBot |
|
||||
CondFormulaValueIsCat |
|
||||
CondFormulaValueIsExplorable |
|
||||
CondFormulaValueRoleAssignedTo |
|
||||
CondFormulaValueCreatedLessThan |
|
||||
CondFormulaValueCreatedMoreThan |
|
||||
|
|
|
@ -57,6 +57,20 @@ export const packedRoleCondFormulaValueIsLocalOrRemoteSchema = {
|
|||
},
|
||||
} as const;
|
||||
|
||||
export const packedRoleCondFormulaValueUserSettingBooleanSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string', optional: false,
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
nullable: false, optional: false,
|
||||
enum: ['isSuspended', 'isLocked', 'isBot', 'isCat', 'isExplorable'],
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const packedRoleCondFormulaValueAssignedRoleSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
|
@ -135,6 +149,9 @@ export const packedRoleCondFormulaValueSchema = {
|
|||
{
|
||||
ref: 'RoleCondFormulaValueIsLocalOrRemote',
|
||||
},
|
||||
{
|
||||
ref: 'RoleCondFormulaValueUserSettingBooleanSchema',
|
||||
},
|
||||
{
|
||||
ref: 'RoleCondFormulaValueAssignedRole',
|
||||
},
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
|
||||
process.env.NODE_ENV = 'test';
|
||||
|
||||
import { jest } from '@jest/globals';
|
||||
|
@ -20,6 +22,7 @@ import { IdService } from '@/core/IdService.js';
|
|||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { secureRndstr } from '@/misc/secure-rndstr.js';
|
||||
import { NotificationService } from '@/core/NotificationService.js';
|
||||
import { RoleCondFormulaValue } from '@/models/Role.js';
|
||||
import { sleep } from '../utils.js';
|
||||
import type { TestingModule } from '@nestjs/testing';
|
||||
import type { MockFunctionMetadata } from 'jest-mock';
|
||||
|
@ -52,12 +55,26 @@ describe('RoleService', () => {
|
|||
id: genAidx(Date.now()),
|
||||
updatedAt: new Date(),
|
||||
lastUsedAt: new Date(),
|
||||
name: '',
|
||||
description: '',
|
||||
...data,
|
||||
})
|
||||
.then(x => rolesRepository.findOneByOrFail(x.identifiers[0]));
|
||||
}
|
||||
|
||||
function createConditionalRole(condFormula: RoleCondFormulaValue, data: Partial<MiRole> = {}) {
|
||||
return createRole({
|
||||
name: `[conditional] ${condFormula.type}`,
|
||||
target: 'conditional',
|
||||
condFormula: condFormula,
|
||||
...data,
|
||||
});
|
||||
}
|
||||
|
||||
function aidx() {
|
||||
return genAidx(Date.now());
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
clock = lolex.install({
|
||||
now: new Date(),
|
||||
|
@ -73,6 +90,7 @@ describe('RoleService', () => {
|
|||
CacheService,
|
||||
IdService,
|
||||
GlobalEventService,
|
||||
UserEntityService,
|
||||
{
|
||||
provide: NotificationService,
|
||||
useFactory: () => ({
|
||||
|
@ -209,79 +227,6 @@ describe('RoleService', () => {
|
|||
expect(result.driveCapacityMb).toBe(100);
|
||||
});
|
||||
|
||||
test('conditional role', async () => {
|
||||
const user1 = await createUser({
|
||||
id: genAidx(Date.now() - (1000 * 60 * 60 * 24 * 365)),
|
||||
});
|
||||
const user2 = await createUser({
|
||||
id: genAidx(Date.now() - (1000 * 60 * 60 * 24 * 365)),
|
||||
followersCount: 10,
|
||||
});
|
||||
await createRole({
|
||||
name: 'a',
|
||||
policies: {
|
||||
canManageCustomEmojis: {
|
||||
useDefault: false,
|
||||
priority: 0,
|
||||
value: true,
|
||||
},
|
||||
},
|
||||
target: 'conditional',
|
||||
condFormula: {
|
||||
id: '232a4221-9816-49a6-a967-ae0fac52ec5e',
|
||||
type: 'and',
|
||||
values: [{
|
||||
id: '2a37ef43-2d93-4c4d-87f6-f2fdb7d9b530',
|
||||
type: 'followersMoreThanOrEq',
|
||||
value: 10,
|
||||
}, {
|
||||
id: '1bd67839-b126-4f92-bad0-4e285dab453b',
|
||||
type: 'createdMoreThan',
|
||||
sec: 60 * 60 * 24 * 7,
|
||||
}],
|
||||
},
|
||||
});
|
||||
|
||||
metaService.fetch.mockResolvedValue({
|
||||
policies: {
|
||||
canManageCustomEmojis: false,
|
||||
},
|
||||
} as any);
|
||||
|
||||
const user1Policies = await roleService.getUserPolicies(user1.id);
|
||||
const user2Policies = await roleService.getUserPolicies(user2.id);
|
||||
expect(user1Policies.canManageCustomEmojis).toBe(false);
|
||||
expect(user2Policies.canManageCustomEmojis).toBe(true);
|
||||
});
|
||||
|
||||
test('コンディショナルロール: マニュアルロールにアサイン済み', async () => {
|
||||
const [user1, user2, role1] = await Promise.all([
|
||||
createUser(),
|
||||
createUser(),
|
||||
createRole({
|
||||
name: 'manual role',
|
||||
}),
|
||||
]);
|
||||
const role2 = await createRole({
|
||||
name: 'conditional role',
|
||||
target: 'conditional',
|
||||
condFormula: {
|
||||
// idはバックエンドのロジックに必要ない?
|
||||
id: 'bdc612bd-9d54-4675-ae83-0499c82ea670',
|
||||
type: 'roleAssignedTo',
|
||||
roleId: role1.id,
|
||||
},
|
||||
});
|
||||
await roleService.assign(user2.id, role1.id);
|
||||
|
||||
const [u1role, u2role] = await Promise.all([
|
||||
roleService.getUserRoles(user1.id),
|
||||
roleService.getUserRoles(user2.id),
|
||||
]);
|
||||
expect(u1role.some(r => r.id === role2.id)).toBe(false);
|
||||
expect(u2role.some(r => r.id === role2.id)).toBe(true);
|
||||
});
|
||||
|
||||
test('expired role', async () => {
|
||||
const user = await createUser();
|
||||
const role = await createRole({
|
||||
|
@ -320,6 +265,427 @@ describe('RoleService', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('conditional role', () => {
|
||||
test('~かつ~', async () => {
|
||||
const [user1, user2, user3, user4] = await Promise.all([
|
||||
createUser({ isBot: true, isCat: false, isSuspended: false }),
|
||||
createUser({ isBot: false, isCat: true, isSuspended: false }),
|
||||
createUser({ isBot: true, isCat: true, isSuspended: false }),
|
||||
createUser({ isBot: false, isCat: false, isSuspended: true }),
|
||||
]);
|
||||
const role1 = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isBot',
|
||||
});
|
||||
const role2 = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isCat',
|
||||
});
|
||||
const role3 = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isSuspended',
|
||||
});
|
||||
const role4 = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'and',
|
||||
values: [role1.condFormula, role2.condFormula],
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
const actual3 = await roleService.getUserRoles(user3.id);
|
||||
const actual4 = await roleService.getUserRoles(user4.id);
|
||||
expect(actual1.some(r => r.id === role4.id)).toBe(false);
|
||||
expect(actual2.some(r => r.id === role4.id)).toBe(false);
|
||||
expect(actual3.some(r => r.id === role4.id)).toBe(true);
|
||||
expect(actual4.some(r => r.id === role4.id)).toBe(false);
|
||||
});
|
||||
|
||||
test('~または~', async () => {
|
||||
const [user1, user2, user3, user4] = await Promise.all([
|
||||
createUser({ isBot: true, isCat: false, isSuspended: false }),
|
||||
createUser({ isBot: false, isCat: true, isSuspended: false }),
|
||||
createUser({ isBot: true, isCat: true, isSuspended: false }),
|
||||
createUser({ isBot: false, isCat: false, isSuspended: true }),
|
||||
]);
|
||||
const role1 = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isBot',
|
||||
});
|
||||
const role2 = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isCat',
|
||||
});
|
||||
const role3 = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isSuspended',
|
||||
});
|
||||
const role4 = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'or',
|
||||
values: [role1.condFormula, role2.condFormula],
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
const actual3 = await roleService.getUserRoles(user3.id);
|
||||
const actual4 = await roleService.getUserRoles(user4.id);
|
||||
expect(actual1.some(r => r.id === role4.id)).toBe(true);
|
||||
expect(actual2.some(r => r.id === role4.id)).toBe(true);
|
||||
expect(actual3.some(r => r.id === role4.id)).toBe(true);
|
||||
expect(actual4.some(r => r.id === role4.id)).toBe(false);
|
||||
});
|
||||
|
||||
test('~ではない', async () => {
|
||||
const [user1, user2, user3] = await Promise.all([
|
||||
createUser({ isBot: true, isCat: false, isSuspended: false }),
|
||||
createUser({ isBot: false, isCat: true, isSuspended: false }),
|
||||
createUser({ isBot: true, isCat: true, isSuspended: false }),
|
||||
]);
|
||||
const role1 = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isBot',
|
||||
});
|
||||
const role2 = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isCat',
|
||||
});
|
||||
const role4 = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'not',
|
||||
value: role1.condFormula,
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
const actual3 = await roleService.getUserRoles(user3.id);
|
||||
expect(actual1.some(r => r.id === role4.id)).toBe(false);
|
||||
expect(actual2.some(r => r.id === role4.id)).toBe(true);
|
||||
expect(actual3.some(r => r.id === role4.id)).toBe(false);
|
||||
});
|
||||
|
||||
test('マニュアルロールにアサイン済み', async () => {
|
||||
const [user1, user2, role1] = await Promise.all([
|
||||
createUser(),
|
||||
createUser(),
|
||||
createRole({
|
||||
name: 'manual role',
|
||||
}),
|
||||
]);
|
||||
const role2 = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'roleAssignedTo',
|
||||
roleId: role1.id,
|
||||
});
|
||||
await roleService.assign(user2.id, role1.id);
|
||||
|
||||
const [u1role, u2role] = await Promise.all([
|
||||
roleService.getUserRoles(user1.id),
|
||||
roleService.getUserRoles(user2.id),
|
||||
]);
|
||||
expect(u1role.some(r => r.id === role2.id)).toBe(false);
|
||||
expect(u2role.some(r => r.id === role2.id)).toBe(true);
|
||||
});
|
||||
|
||||
test('ローカルユーザのみ', async () => {
|
||||
const [user1, user2] = await Promise.all([
|
||||
createUser({ host: null }),
|
||||
createUser({ host: 'example.com' }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isLocal',
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(true);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(false);
|
||||
});
|
||||
|
||||
test('リモートユーザのみ', async () => {
|
||||
const [user1, user2] = await Promise.all([
|
||||
createUser({ host: null }),
|
||||
createUser({ host: 'example.com' }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isRemote',
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(false);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(true);
|
||||
});
|
||||
|
||||
test('サスペンド済みユーザである', async () => {
|
||||
const [user1, user2] = await Promise.all([
|
||||
createUser({ isSuspended: false }),
|
||||
createUser({ isSuspended: true }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isSuspended',
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(false);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(true);
|
||||
});
|
||||
|
||||
test('鍵アカウントユーザである', async () => {
|
||||
const [user1, user2] = await Promise.all([
|
||||
createUser({ isLocked: false }),
|
||||
createUser({ isLocked: true }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isLocked',
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(false);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(true);
|
||||
});
|
||||
|
||||
test('botユーザである', async () => {
|
||||
const [user1, user2] = await Promise.all([
|
||||
createUser({ isBot: false }),
|
||||
createUser({ isBot: true }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isBot',
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(false);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(true);
|
||||
});
|
||||
|
||||
test('猫である', async () => {
|
||||
const [user1, user2] = await Promise.all([
|
||||
createUser({ isCat: false }),
|
||||
createUser({ isCat: true }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isCat',
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(false);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(true);
|
||||
});
|
||||
|
||||
test('「ユーザを見つけやすくする」が有効なアカウント', async () => {
|
||||
const [user1, user2] = await Promise.all([
|
||||
createUser({ isExplorable: false }),
|
||||
createUser({ isExplorable: true }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'isExplorable',
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(false);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(true);
|
||||
});
|
||||
|
||||
test('ユーザが作成されてから指定期間経過した', async () => {
|
||||
const base = new Date();
|
||||
base.setMinutes(base.getMinutes() - 5);
|
||||
|
||||
const d1 = new Date(base);
|
||||
const d2 = new Date(base);
|
||||
const d3 = new Date(base);
|
||||
d1.setSeconds(d1.getSeconds() - 1);
|
||||
d3.setSeconds(d3.getSeconds() + 1);
|
||||
|
||||
const [user1, user2, user3] = await Promise.all([
|
||||
// 4:59
|
||||
createUser({ id: genAidx(d1.getTime()) }),
|
||||
// 5:00
|
||||
createUser({ id: genAidx(d2.getTime()) }),
|
||||
// 5:01
|
||||
createUser({ id: genAidx(d3.getTime()) }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'createdLessThan',
|
||||
// 5 minutes
|
||||
sec: 300,
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
const actual3 = await roleService.getUserRoles(user3.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(false);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(false);
|
||||
expect(actual3.some(r => r.id === role.id)).toBe(true);
|
||||
});
|
||||
|
||||
test('ユーザが作成されてから指定期間経っていない', async () => {
|
||||
const base = new Date();
|
||||
base.setMinutes(base.getMinutes() - 5);
|
||||
|
||||
const d1 = new Date(base);
|
||||
const d2 = new Date(base);
|
||||
const d3 = new Date(base);
|
||||
d1.setSeconds(d1.getSeconds() - 1);
|
||||
d3.setSeconds(d3.getSeconds() + 1);
|
||||
|
||||
const [user1, user2, user3] = await Promise.all([
|
||||
// 4:59
|
||||
createUser({ id: genAidx(d1.getTime()) }),
|
||||
// 5:00
|
||||
createUser({ id: genAidx(d2.getTime()) }),
|
||||
// 5:01
|
||||
createUser({ id: genAidx(d3.getTime()) }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'createdMoreThan',
|
||||
// 5 minutes
|
||||
sec: 300,
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
const actual3 = await roleService.getUserRoles(user3.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(true);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(false);
|
||||
expect(actual3.some(r => r.id === role.id)).toBe(false);
|
||||
});
|
||||
|
||||
test('フォロワー数が指定値以下', async () => {
|
||||
const [user1, user2, user3] = await Promise.all([
|
||||
createUser({ followersCount: 99 }),
|
||||
createUser({ followersCount: 100 }),
|
||||
createUser({ followersCount: 101 }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'followersLessThanOrEq',
|
||||
value: 100,
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
const actual3 = await roleService.getUserRoles(user3.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(true);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(true);
|
||||
expect(actual3.some(r => r.id === role.id)).toBe(false);
|
||||
});
|
||||
|
||||
test('フォロワー数が指定値以下', async () => {
|
||||
const [user1, user2, user3] = await Promise.all([
|
||||
createUser({ followersCount: 99 }),
|
||||
createUser({ followersCount: 100 }),
|
||||
createUser({ followersCount: 101 }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'followersMoreThanOrEq',
|
||||
value: 100,
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
const actual3 = await roleService.getUserRoles(user3.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(false);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(true);
|
||||
expect(actual3.some(r => r.id === role.id)).toBe(true);
|
||||
});
|
||||
|
||||
test('フォロー数が指定値以下', async () => {
|
||||
const [user1, user2, user3] = await Promise.all([
|
||||
createUser({ followingCount: 99 }),
|
||||
createUser({ followingCount: 100 }),
|
||||
createUser({ followingCount: 101 }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'followingLessThanOrEq',
|
||||
value: 100,
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
const actual3 = await roleService.getUserRoles(user3.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(true);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(true);
|
||||
expect(actual3.some(r => r.id === role.id)).toBe(false);
|
||||
});
|
||||
|
||||
test('フォロー数が指定値以上', async () => {
|
||||
const [user1, user2, user3] = await Promise.all([
|
||||
createUser({ followingCount: 99 }),
|
||||
createUser({ followingCount: 100 }),
|
||||
createUser({ followingCount: 101 }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'followingMoreThanOrEq',
|
||||
value: 100,
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
const actual3 = await roleService.getUserRoles(user3.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(false);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(true);
|
||||
expect(actual3.some(r => r.id === role.id)).toBe(true);
|
||||
});
|
||||
|
||||
test('ノート数が指定値以下', async () => {
|
||||
const [user1, user2, user3] = await Promise.all([
|
||||
createUser({ notesCount: 9 }),
|
||||
createUser({ notesCount: 10 }),
|
||||
createUser({ notesCount: 11 }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'notesLessThanOrEq',
|
||||
value: 10,
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
const actual3 = await roleService.getUserRoles(user3.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(true);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(true);
|
||||
expect(actual3.some(r => r.id === role.id)).toBe(false);
|
||||
});
|
||||
|
||||
test('ノート数が指定値以上', async () => {
|
||||
const [user1, user2, user3] = await Promise.all([
|
||||
createUser({ notesCount: 9 }),
|
||||
createUser({ notesCount: 10 }),
|
||||
createUser({ notesCount: 11 }),
|
||||
]);
|
||||
const role = await createConditionalRole({
|
||||
id: aidx(),
|
||||
type: 'notesMoreThanOrEq',
|
||||
value: 10,
|
||||
});
|
||||
|
||||
const actual1 = await roleService.getUserRoles(user1.id);
|
||||
const actual2 = await roleService.getUserRoles(user2.id);
|
||||
const actual3 = await roleService.getUserRoles(user3.id);
|
||||
expect(actual1.some(r => r.id === role.id)).toBe(false);
|
||||
expect(actual2.some(r => r.id === role.id)).toBe(true);
|
||||
expect(actual3.some(r => r.id === role.id)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('assign', () => {
|
||||
test('公開ロールの場合は通知される', async () => {
|
||||
const user = await createUser();
|
||||
|
|
|
@ -9,6 +9,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkSelect v-model="type" :class="$style.typeSelect">
|
||||
<option value="isLocal">{{ i18n.ts._role._condition.isLocal }}</option>
|
||||
<option value="isRemote">{{ i18n.ts._role._condition.isRemote }}</option>
|
||||
<option value="isSuspended">{{ i18n.ts._role._condition.isSuspended }}</option>
|
||||
<option value="isLocked">{{ i18n.ts._role._condition.isLocked }}</option>
|
||||
<option value="isBot">{{ i18n.ts._role._condition.isBot }}</option>
|
||||
<option value="isCat">{{ i18n.ts._role._condition.isCat }}</option>
|
||||
<option value="isExplorable">{{ i18n.ts._role._condition.isExplorable }}</option>
|
||||
<option value="roleAssignedTo">{{ i18n.ts._role._condition.roleAssignedTo }}</option>
|
||||
<option value="createdLessThan">{{ i18n.ts._role._condition.createdLessThan }}</option>
|
||||
<option value="createdMoreThan">{{ i18n.ts._role._condition.createdMoreThan }}</option>
|
||||
|
|
|
@ -1713,6 +1713,7 @@ declare namespace entities {
|
|||
RoleCondFormulaLogics,
|
||||
RoleCondFormulaValueNot,
|
||||
RoleCondFormulaValueIsLocalOrRemote,
|
||||
RoleCondFormulaValueUserSettingBooleanSchema,
|
||||
RoleCondFormulaValueAssignedRole,
|
||||
RoleCondFormulaValueCreated,
|
||||
RoleCondFormulaFollowersOrFollowingOrNotes,
|
||||
|
@ -2745,6 +2746,9 @@ type RoleCondFormulaValueIsLocalOrRemote = components['schemas']['RoleCondFormul
|
|||
// @public (undocumented)
|
||||
type RoleCondFormulaValueNot = components['schemas']['RoleCondFormulaValueNot'];
|
||||
|
||||
// @public (undocumented)
|
||||
type RoleCondFormulaValueUserSettingBooleanSchema = components['schemas']['RoleCondFormulaValueUserSettingBooleanSchema'];
|
||||
|
||||
// @public (undocumented)
|
||||
type RoleLite = components['schemas']['RoleLite'];
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ export type Signin = components['schemas']['Signin'];
|
|||
export type RoleCondFormulaLogics = components['schemas']['RoleCondFormulaLogics'];
|
||||
export type RoleCondFormulaValueNot = components['schemas']['RoleCondFormulaValueNot'];
|
||||
export type RoleCondFormulaValueIsLocalOrRemote = components['schemas']['RoleCondFormulaValueIsLocalOrRemote'];
|
||||
export type RoleCondFormulaValueUserSettingBooleanSchema = components['schemas']['RoleCondFormulaValueUserSettingBooleanSchema'];
|
||||
export type RoleCondFormulaValueAssignedRole = components['schemas']['RoleCondFormulaValueAssignedRole'];
|
||||
export type RoleCondFormulaValueCreated = components['schemas']['RoleCondFormulaValueCreated'];
|
||||
export type RoleCondFormulaFollowersOrFollowingOrNotes = components['schemas']['RoleCondFormulaFollowersOrFollowingOrNotes'];
|
||||
|
|
|
@ -4586,6 +4586,11 @@ export type components = {
|
|||
/** @enum {string} */
|
||||
type: 'isLocal' | 'isRemote';
|
||||
};
|
||||
RoleCondFormulaValueUserSettingBooleanSchema: {
|
||||
id: string;
|
||||
/** @enum {string} */
|
||||
type: 'isSuspended' | 'isLocked' | 'isBot' | 'isCat' | 'isExplorable';
|
||||
};
|
||||
RoleCondFormulaValueAssignedRole: {
|
||||
id: string;
|
||||
/** @enum {string} */
|
||||
|
@ -4608,7 +4613,7 @@ export type components = {
|
|||
type: 'followersLessThanOrEq' | 'followersMoreThanOrEq' | 'followingLessThanOrEq' | 'followingMoreThanOrEq' | 'notesLessThanOrEq' | 'notesMoreThanOrEq';
|
||||
value: number;
|
||||
};
|
||||
RoleCondFormulaValue: components['schemas']['RoleCondFormulaLogics'] | components['schemas']['RoleCondFormulaValueNot'] | components['schemas']['RoleCondFormulaValueIsLocalOrRemote'] | components['schemas']['RoleCondFormulaValueAssignedRole'] | components['schemas']['RoleCondFormulaValueCreated'] | components['schemas']['RoleCondFormulaFollowersOrFollowingOrNotes'];
|
||||
RoleCondFormulaValue: components['schemas']['RoleCondFormulaLogics'] | components['schemas']['RoleCondFormulaValueNot'] | components['schemas']['RoleCondFormulaValueIsLocalOrRemote'] | components['schemas']['RoleCondFormulaValueUserSettingBooleanSchema'] | components['schemas']['RoleCondFormulaValueAssignedRole'] | components['schemas']['RoleCondFormulaValueCreated'] | components['schemas']['RoleCondFormulaFollowersOrFollowingOrNotes'];
|
||||
RoleLite: {
|
||||
/**
|
||||
* Format: id
|
||||
|
|
Loading…
Reference in a new issue