* 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
This commit is contained in:
syuilo
2023-01-12 21:02:26 +09:00
committed by GitHub
parent 60e545b2fd
commit 2470afaa2e
89 changed files with 2001 additions and 612 deletions

View File

@@ -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,

View File

@@ -53,7 +53,7 @@ export class CreateSystemUserService {
usernameLower: username.toLowerCase(),
host: null,
token: secret,
isAdmin: false,
isRoot: false,
isLocked: true,
isExplorable: false,
isBot: true,

View File

@@ -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 => {});

View File

@@ -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})`);

View File

@@ -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[]) {

View File

@@ -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);
}
}

View File

@@ -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,
}));

View File

@@ -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);

View File

@@ -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)));
}
}

View File

@@ -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