summaryrefslogtreecommitdiff
path: root/packages/backend/src/core
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2023-01-12 21:02:26 +0900
committerGitHub <noreply@github.com>2023-01-12 21:02:26 +0900
commit2470afaa2e200fb2fc748e0f8eef5e2c215369b6 (patch)
treec270452679996127a9d15c4ba5f97b39bb9ba560 /packages/backend/src/core
parentUpdate CHANGELOG.md (diff)
downloadmisskey-2470afaa2e200fb2fc748e0f8eef5e2c215369b6.tar.gz
misskey-2470afaa2e200fb2fc748e0f8eef5e2c215369b6.tar.bz2
misskey-2470afaa2e200fb2fc748e0f8eef5e2c215369b6.zip
Role (#9437)
* wip * Update CHANGELOG.md * wip * wip * wip * Update create.ts * wip * wip * Update CHANGELOG.md * wip * wip * wip * wip * wip * wip * wip * Update CHANGELOG.md * wip * wip * Update delete.ts * Update delete.ts * wip * wip * wip * Update account-info.vue * wip * wip * Update settings.vue * Update user-info.vue * wip * Update show-file.ts * Update show-user.ts * wip * wip * Update delete.ts * wip * wip * Update overview.moderators.vue * Create 1673500412259-Role.js * wip * wip * Update roles.vue * 色 * Update roles.vue * integrate silence * wip * wip
Diffstat (limited to 'packages/backend/src/core')
-rw-r--r--packages/backend/src/core/CoreModule.ts12
-rw-r--r--packages/backend/src/core/CreateSystemUserService.ts2
-rw-r--r--packages/backend/src/core/DeleteAccountService.ts3
-rw-r--r--packages/backend/src/core/DriveService.ts19
-rw-r--r--packages/backend/src/core/NoteCreateService.ts11
-rw-r--r--packages/backend/src/core/RoleService.ts201
-rw-r--r--packages/backend/src/core/SignupService.ts6
-rw-r--r--packages/backend/src/core/UserCacheService.ts4
-rw-r--r--packages/backend/src/core/entities/RoleEntityService.ts80
-rw-r--r--packages/backend/src/core/entities/UserEntityService.ts19
10 files changed, 330 insertions, 27 deletions
diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts
index 2f17fa389a..0ae1ee32b2 100644
--- a/packages/backend/src/core/CoreModule.ts
+++ b/packages/backend/src/core/CoreModule.ts
@@ -35,6 +35,7 @@ import { PushNotificationService } from './PushNotificationService.js';
import { QueryService } from './QueryService.js';
import { ReactionService } from './ReactionService.js';
import { RelayService } from './RelayService.js';
+import { RoleService } from './RoleService.js';
import { S3Service } from './S3Service.js';
import { SignupService } from './SignupService.js';
import { TwoFactorAuthenticationService } from './TwoFactorAuthenticationService.js';
@@ -97,6 +98,7 @@ import { UserGroupInvitationEntityService } from './entities/UserGroupInvitation
import { UserListEntityService } from './entities/UserListEntityService.js';
import { FlashEntityService } from './entities/FlashEntityService.js';
import { FlashLikeEntityService } from './entities/FlashLikeEntityService.js';
+import { RoleEntityService } from './entities/RoleEntityService.js';
import { ApAudienceService } from './activitypub/ApAudienceService.js';
import { ApDbResolverService } from './activitypub/ApDbResolverService.js';
import { ApDeliverManagerService } from './activitypub/ApDeliverManagerService.js';
@@ -158,6 +160,7 @@ const $PushNotificationService: Provider = { provide: 'PushNotificationService',
const $QueryService: Provider = { provide: 'QueryService', useExisting: QueryService };
const $ReactionService: Provider = { provide: 'ReactionService', useExisting: ReactionService };
const $RelayService: Provider = { provide: 'RelayService', useExisting: RelayService };
+const $RoleService: Provider = { provide: 'RoleService', useExisting: RoleService };
const $S3Service: Provider = { provide: 'S3Service', useExisting: S3Service };
const $SignupService: Provider = { provide: 'SignupService', useExisting: SignupService };
const $TwoFactorAuthenticationService: Provider = { provide: 'TwoFactorAuthenticationService', useExisting: TwoFactorAuthenticationService };
@@ -220,6 +223,7 @@ const $UserGroupInvitationEntityService: Provider = { provide: 'UserGroupInvitat
const $UserListEntityService: Provider = { provide: 'UserListEntityService', useExisting: UserListEntityService };
const $FlashEntityService: Provider = { provide: 'FlashEntityService', useExisting: FlashEntityService };
const $FlashLikeEntityService: Provider = { provide: 'FlashLikeEntityService', useExisting: FlashLikeEntityService };
+const $RoleEntityService: Provider = { provide: 'RoleEntityService', useExisting: RoleEntityService };
const $ApAudienceService: Provider = { provide: 'ApAudienceService', useExisting: ApAudienceService };
const $ApDbResolverService: Provider = { provide: 'ApDbResolverService', useExisting: ApDbResolverService };
@@ -283,6 +287,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
QueryService,
ReactionService,
RelayService,
+ RoleService,
S3Service,
SignupService,
TwoFactorAuthenticationService,
@@ -344,6 +349,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
UserListEntityService,
FlashEntityService,
FlashLikeEntityService,
+ RoleEntityService,
ApAudienceService,
ApDbResolverService,
ApDeliverManagerService,
@@ -402,6 +408,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$QueryService,
$ReactionService,
$RelayService,
+ $RoleService,
$S3Service,
$SignupService,
$TwoFactorAuthenticationService,
@@ -463,6 +470,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$UserListEntityService,
$FlashEntityService,
$FlashLikeEntityService,
+ $RoleEntityService,
$ApAudienceService,
$ApDbResolverService,
$ApDeliverManagerService,
@@ -522,6 +530,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
QueryService,
ReactionService,
RelayService,
+ RoleService,
S3Service,
SignupService,
TwoFactorAuthenticationService,
@@ -582,6 +591,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
UserListEntityService,
FlashEntityService,
FlashLikeEntityService,
+ RoleEntityService,
ApAudienceService,
ApDbResolverService,
ApDeliverManagerService,
@@ -640,6 +650,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$QueryService,
$ReactionService,
$RelayService,
+ $RoleService,
$S3Service,
$SignupService,
$TwoFactorAuthenticationService,
@@ -700,6 +711,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$UserListEntityService,
$FlashEntityService,
$FlashLikeEntityService,
+ $RoleEntityService,
$ApAudienceService,
$ApDbResolverService,
$ApDeliverManagerService,
diff --git a/packages/backend/src/core/CreateSystemUserService.ts b/packages/backend/src/core/CreateSystemUserService.ts
index 1e753f65cc..8f887d90f9 100644
--- a/packages/backend/src/core/CreateSystemUserService.ts
+++ b/packages/backend/src/core/CreateSystemUserService.ts
@@ -53,7 +53,7 @@ export class CreateSystemUserService {
usernameLower: username.toLowerCase(),
host: null,
token: secret,
- isAdmin: false,
+ isRoot: false,
isLocked: true,
isExplorable: false,
isBot: true,
diff --git a/packages/backend/src/core/DeleteAccountService.ts b/packages/backend/src/core/DeleteAccountService.ts
index e42c738707..0ac12857c9 100644
--- a/packages/backend/src/core/DeleteAccountService.ts
+++ b/packages/backend/src/core/DeleteAccountService.ts
@@ -23,6 +23,9 @@ export class DeleteAccountService {
id: string;
host: string | null;
}): Promise<void> {
+ const _user = await this.usersRepository.findOneByOrFail({ id: user.id });
+ if (_user.isRoot) throw new Error('cannot delete a root account');
+
// 物理削除する前にDelete activityを送信する
await this.userSuspendService.doPostSuspend(user).catch(e => {});
diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts
index bbdb5fae83..5954abba91 100644
--- a/packages/backend/src/core/DriveService.ts
+++ b/packages/backend/src/core/DriveService.ts
@@ -32,11 +32,12 @@ import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.j
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { FileInfoService } from '@/core/FileInfoService.js';
import { bindThis } from '@/decorators.js';
+import { RoleService } from '@/core/RoleService.js';
import type S3 from 'aws-sdk/clients/s3.js';
type AddFileArgs = {
/** User who wish to add file */
- user: { id: User['id']; host: User['host']; driveCapacityOverrideMb: User['driveCapacityOverrideMb'] } | null;
+ user: { id: User['id']; host: User['host'] } | null;
/** File path */
path: string;
/** Name */
@@ -62,7 +63,7 @@ type AddFileArgs = {
type UploadFromUrlArgs = {
url: string;
- user: { id: User['id']; host: User['host']; driveCapacityOverrideMb: User['driveCapacityOverrideMb'] } | null;
+ user: { id: User['id']; host: User['host'] } | null;
folderId?: DriveFolder['id'] | null;
uri?: string | null;
sensitive?: boolean;
@@ -106,6 +107,7 @@ export class DriveService {
private videoProcessingService: VideoProcessingService,
private globalEventService: GlobalEventService,
private queueService: QueueService,
+ private roleService: RoleService,
private driveChart: DriveChart,
private perUserDriveChart: PerUserDriveChart,
private instanceChart: InstanceChart,
@@ -463,15 +465,16 @@ export class DriveService {
//#region Check drive usage
if (user && !isLink) {
const usage = await this.driveFileEntityService.calcDriveUsageOf(user);
- const u = await this.usersRepository.findOneBy({ id: user.id });
- const instance = await this.metaService.fetch();
- let driveCapacity = 1024 * 1024 * (this.userEntityService.isLocalUser(user) ? instance.localDriveCapacityMb : instance.remoteDriveCapacityMb);
-
- if (this.userEntityService.isLocalUser(user) && u?.driveCapacityOverrideMb != null) {
- driveCapacity = 1024 * 1024 * u.driveCapacityOverrideMb;
+ let driveCapacity: number;
+ if (this.userEntityService.isLocalUser(user)) {
+ const role = await this.roleService.getUserRoleOptions(user.id);
+ driveCapacity = 1024 * 1024 * role.driveCapacityMb;
this.registerLogger.debug('drive capacity override applied');
this.registerLogger.debug(`overrideCap: ${driveCapacity}bytes, usage: ${usage}bytes, u+s: ${usage + info.size}bytes`);
+ } else {
+ const instance = await this.metaService.fetch();
+ driveCapacity = 1024 * 1024 * instance.remoteDriveCapacityMb;
}
this.registerLogger.debug(`drive usage is ${usage} (max: ${driveCapacity})`);
diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts
index 6038840406..1c2add5d64 100644
--- a/packages/backend/src/core/NoteCreateService.ts
+++ b/packages/backend/src/core/NoteCreateService.ts
@@ -42,6 +42,7 @@ import { NoteReadService } from '@/core/NoteReadService.js';
import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
import { bindThis } from '@/decorators.js';
import { DB_MAX_NOTE_TEXT_LENGTH } from '@/const.js';
+import { RoleService } from '@/core/RoleService.js';
const mutedWordsCache = new Cache<{ userId: UserProfile['userId']; mutedWords: UserProfile['mutedWords']; }[]>(1000 * 60 * 5);
@@ -186,6 +187,7 @@ export class NoteCreateService {
private remoteUserResolveService: RemoteUserResolveService,
private apDeliverManagerService: ApDeliverManagerService,
private apRendererService: ApRendererService,
+ private roleService: RoleService,
private notesChart: NotesChart,
private perUserNotesChart: PerUserNotesChart,
private activeUsersChart: ActiveUsersChart,
@@ -197,7 +199,6 @@ export class NoteCreateService {
id: User['id'];
username: User['username'];
host: User['host'];
- isSilenced: User['isSilenced'];
createdAt: User['createdAt'];
isBot: User['isBot'];
}, data: Option, silent = false): Promise<Note> {
@@ -224,9 +225,10 @@ export class NoteCreateService {
if (data.channel != null) data.visibleUsers = [];
if (data.channel != null) data.localOnly = true;
- // サイレンス
- if (user.isSilenced && data.visibility === 'public' && data.channel == null) {
- data.visibility = 'home';
+ if (data.visibility === 'public' && data.channel == null) {
+ if ((await this.roleService.getUserRoleOptions(user.id)).canPublicNote) {
+ data.visibility = 'home';
+ }
}
// Renote対象が「ホームまたは全体」以外の公開範囲ならreject
@@ -418,7 +420,6 @@ export class NoteCreateService {
id: User['id'];
username: User['username'];
host: User['host'];
- isSilenced: User['isSilenced'];
createdAt: User['createdAt'];
isBot: User['isBot'];
}, data: Option, silent: boolean, tags: string[], mentionedUsers: MinimumUser[]) {
diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts
new file mode 100644
index 0000000000..6ce7f431ca
--- /dev/null
+++ b/packages/backend/src/core/RoleService.ts
@@ -0,0 +1,201 @@
+import { Inject, Injectable } from '@nestjs/common';
+import Redis from 'ioredis';
+import { In } from 'typeorm';
+import type { Role, RoleAssignment, RoleAssignmentsRepository, RolesRepository, UsersRepository } from '@/models/index.js';
+import { Cache } from '@/misc/cache.js';
+import type { CacheableLocalUser, CacheableUser, ILocalUser, User } from '@/models/entities/User.js';
+import { DI } from '@/di-symbols.js';
+import { bindThis } from '@/decorators.js';
+import { MetaService } from '@/core/MetaService.js';
+import type { OnApplicationShutdown } from '@nestjs/common';
+
+export type RoleOptions = {
+ gtlAvailable: boolean;
+ ltlAvailable: boolean;
+ canPublicNote: boolean;
+ driveCapacityMb: number;
+ antennaLimit: number;
+};
+
+export const DEFAULT_ROLE: RoleOptions = {
+ gtlAvailable: true,
+ ltlAvailable: true,
+ canPublicNote: true,
+ driveCapacityMb: 100,
+ antennaLimit: 5,
+};
+
+@Injectable()
+export class RoleService implements OnApplicationShutdown {
+ private rolesCache: Cache<Role[]>;
+ private roleAssignmentByUserIdCache: Cache<RoleAssignment[]>;
+
+ constructor(
+ @Inject(DI.redisSubscriber)
+ private redisSubscriber: Redis.Redis,
+
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
+
+ @Inject(DI.rolesRepository)
+ private rolesRepository: RolesRepository,
+
+ @Inject(DI.roleAssignmentsRepository)
+ private roleAssignmentsRepository: RoleAssignmentsRepository,
+
+ private metaService: MetaService,
+ ) {
+ //this.onMessage = this.onMessage.bind(this);
+
+ this.rolesCache = new Cache<Role[]>(Infinity);
+ this.roleAssignmentByUserIdCache = new Cache<RoleAssignment[]>(Infinity);
+
+ this.redisSubscriber.on('message', this.onMessage);
+ }
+
+ @bindThis
+ private async onMessage(_: string, data: string): Promise<void> {
+ const obj = JSON.parse(data);
+
+ if (obj.channel === 'internal') {
+ const { type, body } = obj.message;
+ switch (type) {
+ case 'roleCreated': {
+ const cached = this.rolesCache.get(null);
+ if (cached) {
+ body.createdAt = new Date(body.createdAt);
+ body.updatedAt = new Date(body.updatedAt);
+ body.lastUsedAt = new Date(body.lastUsedAt);
+ cached.push(body);
+ }
+ break;
+ }
+ case 'roleUpdated': {
+ const cached = this.rolesCache.get(null);
+ if (cached) {
+ const i = cached.findIndex(x => x.id === body.id);
+ if (i > -1) {
+ body.createdAt = new Date(body.createdAt);
+ body.updatedAt = new Date(body.updatedAt);
+ body.lastUsedAt = new Date(body.lastUsedAt);
+ cached[i] = body;
+ }
+ }
+ break;
+ }
+ case 'roleDeleted': {
+ const cached = this.rolesCache.get(null);
+ if (cached) {
+ this.rolesCache.set(null, cached.filter(x => x.id !== body.id));
+ }
+ break;
+ }
+ case 'userRoleAssigned': {
+ const cached = this.roleAssignmentByUserIdCache.get(body.userId);
+ if (cached) {
+ body.createdAt = new Date(body.createdAt);
+ cached.push(body);
+ }
+ break;
+ }
+ case 'userRoleUnassigned': {
+ const cached = this.roleAssignmentByUserIdCache.get(body.userId);
+ if (cached) {
+ this.roleAssignmentByUserIdCache.set(body.userId, cached.filter(x => x.id !== body.id));
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ }
+
+ @bindThis
+ public async getUserRoles(userId: User['id']) {
+ const assigns = await this.roleAssignmentByUserIdCache.fetch(userId, () => this.roleAssignmentsRepository.findBy({ userId }));
+ const assignedRoleIds = assigns.map(x => x.roleId);
+ const roles = await this.rolesCache.fetch(null, () => this.rolesRepository.findBy({}));
+ return roles.filter(r => assignedRoleIds.includes(r.id));
+ }
+
+ @bindThis
+ public async getUserRoleOptions(userId: User['id'] | null): Promise<RoleOptions> {
+ const meta = await this.metaService.fetch();
+ const baseRoleOptions = { ...DEFAULT_ROLE, ...meta.defaultRoleOverride };
+
+ if (userId == null) return baseRoleOptions;
+
+ const roles = await this.getUserRoles(userId);
+
+ function getOptionValues(option: keyof RoleOptions) {
+ if (roles.length === 0) return [baseRoleOptions[option]];
+ return roles.map(role => (role.options[option] && (role.options[option].useDefault !== true)) ? role.options[option].value : baseRoleOptions[option]);
+ }
+
+ return {
+ gtlAvailable: getOptionValues('gtlAvailable').some(x => x === true),
+ ltlAvailable: getOptionValues('ltlAvailable').some(x => x === true),
+ canPublicNote: getOptionValues('canPublicNote').some(x => x === true),
+ driveCapacityMb: Math.max(...getOptionValues('driveCapacityMb')),
+ antennaLimit: Math.max(...getOptionValues('antennaLimit')),
+ };
+ }
+
+ @bindThis
+ public async isModerator(user: { id: User['id']; isRoot: User['isRoot'] } | null): Promise<boolean> {
+ if (user == null) return false;
+ return user.isRoot || (await this.getUserRoles(user.id)).some(r => r.isModerator || r.isAdministrator);
+ }
+
+ @bindThis
+ public async isAdministrator(user: { id: User['id']; isRoot: User['isRoot'] } | null): Promise<boolean> {
+ if (user == null) return false;
+ return user.isRoot || (await this.getUserRoles(user.id)).some(r => r.isAdministrator);
+ }
+
+ @bindThis
+ public async getModeratorIds(includeAdmins = true): Promise<User['id'][]> {
+ const roles = await this.rolesCache.fetch(null, () => this.rolesRepository.findBy({}));
+ const moderatorRoles = includeAdmins ? roles.filter(r => r.isModerator || r.isAdministrator) : roles.filter(r => r.isModerator);
+ const assigns = moderatorRoles.length > 0 ? await this.roleAssignmentsRepository.findBy({
+ roleId: In(moderatorRoles.map(r => r.id)),
+ }) : [];
+ // TODO: isRootなアカウントも含める
+ return assigns.map(a => a.userId);
+ }
+
+ @bindThis
+ public async getModerators(includeAdmins = true): Promise<User[]> {
+ const ids = await this.getModeratorIds(includeAdmins);
+ const users = ids.length > 0 ? await this.usersRepository.findBy({
+ id: In(ids),
+ }) : [];
+ return users;
+ }
+
+ @bindThis
+ public async getAdministratorIds(): Promise<User['id'][]> {
+ const roles = await this.rolesCache.fetch(null, () => this.rolesRepository.findBy({}));
+ const administratorRoles = roles.filter(r => r.isAdministrator);
+ const assigns = administratorRoles.length > 0 ? await this.roleAssignmentsRepository.findBy({
+ roleId: In(administratorRoles.map(r => r.id)),
+ }) : [];
+ // TODO: isRootなアカウントも含める
+ return assigns.map(a => a.userId);
+ }
+
+ @bindThis
+ public async getAdministrators(): Promise<User[]> {
+ const ids = await this.getAdministratorIds();
+ const users = ids.length > 0 ? await this.usersRepository.findBy({
+ id: In(ids),
+ }) : [];
+ return users;
+ }
+
+ @bindThis
+ public onApplicationShutdown(signal?: string | undefined) {
+ this.redisSubscriber.off('message', this.onMessage);
+ }
+}
diff --git a/packages/backend/src/core/SignupService.ts b/packages/backend/src/core/SignupService.ts
index 9cf203566d..90a7186909 100644
--- a/packages/backend/src/core/SignupService.ts
+++ b/packages/backend/src/core/SignupService.ts
@@ -11,10 +11,10 @@ import { IdService } from '@/core/IdService.js';
import { UserKeypair } from '@/models/entities/UserKeypair.js';
import { UsedUsername } from '@/models/entities/UsedUsername.js';
import generateUserToken from '@/misc/generate-native-user-token.js';
-import UsersChart from './chart/charts/users.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
-import { UtilityService } from './UtilityService.js';
import { bindThis } from '@/decorators.js';
+import UsersChart from './chart/charts/users.js';
+import { UtilityService } from './UtilityService.js';
@Injectable()
export class SignupService {
@@ -112,7 +112,7 @@ export class SignupService {
usernameLower: username.toLowerCase(),
host: this.utilityService.toPunyNullable(host),
token: secret,
- isAdmin: (await this.usersRepository.countBy({
+ isRoot: (await this.usersRepository.countBy({
host: IsNull(),
})) === 0,
}));
diff --git a/packages/backend/src/core/UserCacheService.ts b/packages/backend/src/core/UserCacheService.ts
index 423c8993e3..3f735c0c53 100644
--- a/packages/backend/src/core/UserCacheService.ts
+++ b/packages/backend/src/core/UserCacheService.ts
@@ -5,8 +5,8 @@ import { Cache } from '@/misc/cache.js';
import type { CacheableLocalUser, CacheableUser, ILocalUser } from '@/models/entities/User.js';
import { DI } from '@/di-symbols.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
-import type { OnApplicationShutdown } from '@nestjs/common';
import { bindThis } from '@/decorators.js';
+import type { OnApplicationShutdown } from '@nestjs/common';
@Injectable()
export class UserCacheService implements OnApplicationShutdown {
@@ -42,8 +42,6 @@ export class UserCacheService implements OnApplicationShutdown {
const { type, body } = obj.message;
switch (type) {
case 'userChangeSuspendedState':
- case 'userChangeSilencedState':
- case 'userChangeModeratorState':
case 'remoteUserUpdated': {
const user = await this.usersRepository.findOneByOrFail({ id: body.id });
this.userByIdCache.set(user.id, user);
diff --git a/packages/backend/src/core/entities/RoleEntityService.ts b/packages/backend/src/core/entities/RoleEntityService.ts
new file mode 100644
index 0000000000..22c4cdff81
--- /dev/null
+++ b/packages/backend/src/core/entities/RoleEntityService.ts
@@ -0,0 +1,80 @@
+import { Inject, Injectable } from '@nestjs/common';
+import { DI } from '@/di-symbols.js';
+import type { RoleAssignmentsRepository, RolesRepository } from '@/models/index.js';
+import { awaitAll } from '@/misc/prelude/await-all.js';
+import type { Packed } from '@/misc/schema.js';
+import type { User } from '@/models/entities/User.js';
+import type { Role } from '@/models/entities/Role.js';
+import { bindThis } from '@/decorators.js';
+import { DEFAULT_ROLE } from '@/core/RoleService.js';
+import { UserEntityService } from './UserEntityService.js';
+
+@Injectable()
+export class RoleEntityService {
+ constructor(
+ @Inject(DI.rolesRepository)
+ private rolesRepository: RolesRepository,
+
+ @Inject(DI.roleAssignmentsRepository)
+ private roleAssignmentsRepository: RoleAssignmentsRepository,
+
+ private userEntityService: UserEntityService,
+ ) {
+ }
+
+ @bindThis
+ public async pack(
+ src: Role['id'] | Role,
+ me?: { id: User['id'] } | null | undefined,
+ options?: {
+ detail?: boolean;
+ },
+ ) {
+ const opts = Object.assign({
+ detail: true,
+ }, options);
+
+ const role = typeof src === 'object' ? src : await this.rolesRepository.findOneByOrFail({ id: src });
+
+ const assigns = await this.roleAssignmentsRepository.findBy({
+ roleId: role.id,
+ });
+
+ const roleOptions = { ...role.options };
+ for (const [k, v] of Object.entries(DEFAULT_ROLE)) {
+ if (roleOptions[k] == null) roleOptions[k] = {
+ useDefault: true,
+ value: v,
+ };
+ }
+
+ return await awaitAll({
+ id: role.id,
+ createdAt: role.createdAt.toISOString(),
+ updatedAt: role.updatedAt.toISOString(),
+ name: role.name,
+ description: role.description,
+ color: role.color,
+ isPublic: role.isPublic,
+ isAdministrator: role.isAdministrator,
+ isModerator: role.isModerator,
+ canEditMembersByModerator: role.canEditMembersByModerator,
+ options: roleOptions,
+ ...(opts.detail ? {
+ users: this.userEntityService.packMany(assigns.map(x => x.userId), me),
+ } : {}),
+ });
+ }
+
+ @bindThis
+ public packMany(
+ roles: any[],
+ me: { id: User['id'] },
+ options?: {
+ detail?: boolean;
+ },
+ ) {
+ return Promise.all(roles.map(x => this.pack(x, me, options)));
+ }
+}
+
diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts
index a123746220..9a90aec456 100644
--- a/packages/backend/src/core/entities/UserEntityService.ts
+++ b/packages/backend/src/core/entities/UserEntityService.ts
@@ -13,6 +13,8 @@ import type { Instance } from '@/models/entities/Instance.js';
import type { ILocalUser, IRemoteUser, User } from '@/models/entities/User.js';
import { birthdaySchema, descriptionSchema, localUsernameSchema, locationSchema, nameSchema, passwordSchema } from '@/models/entities/User.js';
import type { UsersRepository, UserSecurityKeysRepository, FollowingsRepository, FollowRequestsRepository, BlockingsRepository, MutingsRepository, DriveFilesRepository, NoteUnreadsRepository, ChannelFollowingsRepository, NotificationsRepository, UserNotePiningsRepository, UserProfilesRepository, InstancesRepository, AnnouncementReadsRepository, MessagingMessagesRepository, UserGroupJoiningsRepository, AnnouncementsRepository, AntennaNotesRepository, PagesRepository } from '@/models/index.js';
+import { bindThis } from '@/decorators.js';
+import { RoleService } from '@/core/RoleService.js';
import type { OnModuleInit } from '@nestjs/common';
import type { AntennaService } from '../AntennaService.js';
import type { CustomEmojiService } from '../CustomEmojiService.js';
@@ -41,7 +43,6 @@ function isRemoteUser<T extends { host: User['host'] }>(user: T): user is T & {
function isRemoteUser(user: User | { host: User['host'] }): boolean {
return !isLocalUser(user);
}
-import { bindThis } from '@/decorators.js';
@Injectable()
export class UserEntityService implements OnModuleInit {
@@ -50,6 +51,7 @@ export class UserEntityService implements OnModuleInit {
private pageEntityService: PageEntityService;
private customEmojiService: CustomEmojiService;
private antennaService: AntennaService;
+ private roleService: RoleService;
private userInstanceCache: Cache<Instance | null>;
constructor(
@@ -120,6 +122,7 @@ export class UserEntityService implements OnModuleInit {
//private pageEntityService: PageEntityService,
//private customEmojiService: CustomEmojiService,
//private antennaService: AntennaService,
+ //private roleService: RoleService,
) {
this.userInstanceCache = new Cache<Instance | null>(1000 * 60 * 60 * 3);
}
@@ -130,6 +133,7 @@ export class UserEntityService implements OnModuleInit {
this.pageEntityService = this.moduleRef.get('PageEntityService');
this.customEmojiService = this.moduleRef.get('CustomEmojiService');
this.antennaService = this.moduleRef.get('AntennaService');
+ this.roleService = this.moduleRef.get('RoleService');
}
//#region Validators
@@ -383,6 +387,9 @@ export class UserEntityService implements OnModuleInit {
(profile.ffVisibility === 'followers') && (relation && relation.isFollowing) ? user.followersCount :
null;
+ const isModerator = isMe && opts.detail ? this.roleService.isModerator(user) : null;
+ const isAdmin = isMe && opts.detail ? this.roleService.isAdministrator(user) : null;
+
const falsy = opts.detail ? false : undefined;
const packed = {
@@ -392,8 +399,6 @@ export class UserEntityService implements OnModuleInit {
host: user.host,
avatarUrl: this.getAvatarUrlSync(user),
avatarBlurhash: user.avatar?.blurhash ?? null,
- isAdmin: user.isAdmin ?? falsy,
- isModerator: user.isModerator ?? falsy,
isBot: user.isBot ?? falsy,
isCat: user.isCat ?? falsy,
instance: user.host ? this.userInstanceCache.fetch(user.host,
@@ -418,7 +423,7 @@ export class UserEntityService implements OnModuleInit {
bannerUrl: user.banner ? this.driveFileEntityService.getPublicUrl(user.banner, false) : null,
bannerBlurhash: user.banner?.blurhash ?? null,
isLocked: user.isLocked,
- isSilenced: user.isSilenced ?? falsy,
+ isSilenced: this.roleService.getUserRoleOptions(user.id).then(r => !r.canPublicNote),
isSuspended: user.isSuspended ?? falsy,
description: profile!.description,
location: profile!.location,
@@ -443,14 +448,13 @@ export class UserEntityService implements OnModuleInit {
userId: user.id,
}).then(result => result >= 1)
: false,
- ...(isMe || opts.includeSecrets ? {
- driveCapacityOverrideMb: user.driveCapacityOverrideMb,
- } : {}),
} : {}),
...(opts.detail && isMe ? {
avatarId: user.avatarId,
bannerId: user.bannerId,
+ isModerator: isModerator,
+ isAdmin: isAdmin,
injectFeaturedNote: profile!.injectFeaturedNote,
receiveAnnouncementEmail: profile!.receiveAnnouncementEmail,
alwaysMarkNsfw: profile!.alwaysMarkNsfw,
@@ -484,6 +488,7 @@ export class UserEntityService implements OnModuleInit {
} : {}),
...(opts.includeSecrets ? {
+ role: this.roleService.getUserRoleOptions(user.id),
email: profile!.email,
emailVerified: profile!.emailVerified,
securityKeysList: profile!.twoFactorEnabled