enhance: コンテンツ削除を制限されていてもアカウントの閉鎖ができるように (MisskeyIO#532)
This commit is contained in:
parent
2564fc7346
commit
075ec2d7df
|
@ -1691,6 +1691,7 @@ _role:
|
||||||
canCreateContent: "Can create contents"
|
canCreateContent: "Can create contents"
|
||||||
canUpdateContent: "Can edit contents"
|
canUpdateContent: "Can edit contents"
|
||||||
canDeleteContent: "Can delete contents"
|
canDeleteContent: "Can delete contents"
|
||||||
|
canPurgeAccount: "Can delete account completely"
|
||||||
canUpdateAvatar: "Can change avatar"
|
canUpdateAvatar: "Can change avatar"
|
||||||
canUpdateBanner: "Can change banner"
|
canUpdateBanner: "Can change banner"
|
||||||
mentionMax: "Maximum number of mentions in a note"
|
mentionMax: "Maximum number of mentions in a note"
|
||||||
|
|
|
@ -6594,6 +6594,10 @@ export interface Locale extends ILocale {
|
||||||
* コンテンツの削除
|
* コンテンツの削除
|
||||||
*/
|
*/
|
||||||
"canDeleteContent": string;
|
"canDeleteContent": string;
|
||||||
|
/**
|
||||||
|
* 完全なアカウントの削除
|
||||||
|
*/
|
||||||
|
"canPurgeAccount": string;
|
||||||
/**
|
/**
|
||||||
* アイコンの変更
|
* アイコンの変更
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1706,6 +1706,7 @@ _role:
|
||||||
canCreateContent: "コンテンツの作成"
|
canCreateContent: "コンテンツの作成"
|
||||||
canUpdateContent: "コンテンツの編集"
|
canUpdateContent: "コンテンツの編集"
|
||||||
canDeleteContent: "コンテンツの削除"
|
canDeleteContent: "コンテンツの削除"
|
||||||
|
canPurgeAccount: "完全なアカウントの削除"
|
||||||
canUpdateAvatar: "アイコンの変更"
|
canUpdateAvatar: "アイコンの変更"
|
||||||
canUpdateBanner: "バナーの変更"
|
canUpdateBanner: "バナーの変更"
|
||||||
mentionMax: "ノート内の最大メンション数"
|
mentionMax: "ノート内の最大メンション数"
|
||||||
|
|
|
@ -1674,6 +1674,7 @@ _role:
|
||||||
canCreateContent: "컨텐츠 생성 허용"
|
canCreateContent: "컨텐츠 생성 허용"
|
||||||
canUpdateContent: "컨텐츠 수정 허용"
|
canUpdateContent: "컨텐츠 수정 허용"
|
||||||
canDeleteContent: "컨텐츠 삭제 허용"
|
canDeleteContent: "컨텐츠 삭제 허용"
|
||||||
|
canPurgeAccount: "완전한 계정 삭제 허용"
|
||||||
canUpdateAvatar: "아바타 변경 허용"
|
canUpdateAvatar: "아바타 변경 허용"
|
||||||
canUpdateBanner: "배너 변경 허용"
|
canUpdateBanner: "배너 변경 허용"
|
||||||
canInvite: "서버 초대 코드 발행"
|
canInvite: "서버 초대 코드 발행"
|
||||||
|
|
|
@ -4,38 +4,47 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import type Logger from '@/logger.js';
|
||||||
import type { UsersRepository } from '@/models/_.js';
|
import type { UsersRepository } from '@/models/_.js';
|
||||||
|
import type { MiUser } from '@/models/User.js';
|
||||||
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
import { QueueService } from '@/core/QueueService.js';
|
import { QueueService } from '@/core/QueueService.js';
|
||||||
import { UserSuspendService } from '@/core/UserSuspendService.js';
|
import { UserSuspendService } from '@/core/UserSuspendService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
|
||||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||||
|
import { LoggerService } from '@/core/LoggerService.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DeleteAccountService {
|
export class DeleteAccountService {
|
||||||
|
public logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.usersRepository)
|
@Inject(DI.usersRepository)
|
||||||
private usersRepository: UsersRepository,
|
private usersRepository: UsersRepository,
|
||||||
|
|
||||||
private userSuspendService: UserSuspendService,
|
private roleService: RoleService,
|
||||||
private queueService: QueueService,
|
private queueService: QueueService,
|
||||||
|
private userSuspendService: UserSuspendService,
|
||||||
private globalEventService: GlobalEventService,
|
private globalEventService: GlobalEventService,
|
||||||
|
private loggerService: LoggerService,
|
||||||
) {
|
) {
|
||||||
|
this.logger = this.loggerService.getLogger('delete-account');
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async deleteAccount(user: {
|
public async deleteAccount(user: MiUser, soft: boolean, me: MiUser | null): Promise<void> {
|
||||||
id: string;
|
this.logger.warn(`Delete account requested by ${me ? me.id : 'remote'} for ${user.id} (soft: ${soft})`);
|
||||||
host: string | null;
|
|
||||||
}): Promise<void> {
|
|
||||||
const _user = await this.usersRepository.findOneByOrFail({ id: user.id });
|
const _user = await this.usersRepository.findOneByOrFail({ id: user.id });
|
||||||
if (_user.isRoot) throw new Error('cannot delete a root account');
|
if (_user.isRoot) throw new Error('cannot delete a root account');
|
||||||
|
|
||||||
// 物理削除する前にDelete activityを送信する
|
// 物理削除する前にDelete activityを送信する
|
||||||
await this.userSuspendService.doPostSuspend(user).catch(e => {});
|
await this.userSuspendService.doPostSuspend(user).catch(err => this.logger.error(err));
|
||||||
|
|
||||||
this.queueService.createDeleteAccountJob(user, {
|
this.queueService.createDeleteAccountJob(user, {
|
||||||
soft: false,
|
force: me ? await this.roleService.isModerator(me) : false,
|
||||||
|
soft: soft,
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.usersRepository.update(user.id, {
|
await this.usersRepository.update(user.id, {
|
||||||
|
@ -44,4 +53,16 @@ export class DeleteAccountService {
|
||||||
|
|
||||||
this.globalEventService.publishInternalEvent('userChangeDeletedState', { id: user.id, isDeleted: true });
|
this.globalEventService.publishInternalEvent('userChangeDeletedState', { id: user.id, isDeleted: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async deleteAllDriveFiles(user: MiUser, me: MiUser | null): Promise<void> {
|
||||||
|
this.logger.warn(`Delete all drive files requested by ${me ? me.id : 'remote'} for ${user.id}`);
|
||||||
|
|
||||||
|
await this.usersRepository.findOneByOrFail({ id: user.id });
|
||||||
|
|
||||||
|
this.queueService.createDeleteAccountJob(user, {
|
||||||
|
force: me ? await this.roleService.isModerator(me) : false,
|
||||||
|
onlyFiles: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -352,10 +352,12 @@ export class QueueService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public createDeleteAccountJob(user: ThinUser, opts: { soft?: boolean; } = {}) {
|
public createDeleteAccountJob(user: ThinUser, opts: { soft?: boolean, force?: boolean, onlyFiles?: boolean } = {}) {
|
||||||
return this.dbQueue.add('deleteAccount', {
|
return this.dbQueue.add('deleteAccount', {
|
||||||
user: { id: user.id },
|
user: { id: user.id },
|
||||||
soft: opts.soft,
|
soft: opts.soft,
|
||||||
|
force: opts.force,
|
||||||
|
onlyFiles: opts.onlyFiles,
|
||||||
}, {
|
}, {
|
||||||
removeOnComplete: true,
|
removeOnComplete: true,
|
||||||
removeOnFail: true,
|
removeOnFail: true,
|
||||||
|
|
|
@ -40,6 +40,7 @@ export type RolePolicies = {
|
||||||
canCreateContent: boolean;
|
canCreateContent: boolean;
|
||||||
canUpdateContent: boolean;
|
canUpdateContent: boolean;
|
||||||
canDeleteContent: boolean;
|
canDeleteContent: boolean;
|
||||||
|
canPurgeAccount: boolean;
|
||||||
canUpdateAvatar: boolean;
|
canUpdateAvatar: boolean;
|
||||||
canUpdateBanner: boolean;
|
canUpdateBanner: boolean;
|
||||||
mentionLimit: number;
|
mentionLimit: number;
|
||||||
|
@ -77,6 +78,7 @@ export const DEFAULT_POLICIES: RolePolicies = {
|
||||||
canCreateContent: true,
|
canCreateContent: true,
|
||||||
canUpdateContent: true,
|
canUpdateContent: true,
|
||||||
canDeleteContent: true,
|
canDeleteContent: true,
|
||||||
|
canPurgeAccount: true,
|
||||||
canUpdateAvatar: true,
|
canUpdateAvatar: true,
|
||||||
canUpdateBanner: true,
|
canUpdateBanner: true,
|
||||||
mentionLimit: 20,
|
mentionLimit: 20,
|
||||||
|
@ -353,6 +355,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
||||||
canCreateContent: calc('canCreateContent', vs => vs.some(v => v === true)),
|
canCreateContent: calc('canCreateContent', vs => vs.some(v => v === true)),
|
||||||
canUpdateContent: calc('canUpdateContent', vs => vs.some(v => v === true)),
|
canUpdateContent: calc('canUpdateContent', vs => vs.some(v => v === true)),
|
||||||
canDeleteContent: calc('canDeleteContent', vs => vs.some(v => v === true)),
|
canDeleteContent: calc('canDeleteContent', vs => vs.some(v => v === true)),
|
||||||
|
canPurgeAccount: calc('canPurgeAccount', vs => vs.some(v => v === true)),
|
||||||
canUpdateAvatar: calc('canUpdateAvatar', vs => vs.some(v => v === true)),
|
canUpdateAvatar: calc('canUpdateAvatar', vs => vs.some(v => v === true)),
|
||||||
canUpdateBanner: calc('canUpdateBanner', vs => vs.some(v => v === true)),
|
canUpdateBanner: calc('canUpdateBanner', vs => vs.some(v => v === true)),
|
||||||
mentionLimit: calc('mentionLimit', vs => Math.max(...vs)),
|
mentionLimit: calc('mentionLimit', vs => Math.max(...vs)),
|
||||||
|
|
|
@ -487,7 +487,8 @@ export class ApInboxService {
|
||||||
return 'skip: already deleted';
|
return 'skip: already deleted';
|
||||||
}
|
}
|
||||||
|
|
||||||
const job = await this.queueService.createDeleteAccountJob(actor);
|
// リモートから消されたということなので、物理削除する
|
||||||
|
const job = await this.queueService.createDeleteAccountJob(actor, { force: true, soft: false });
|
||||||
|
|
||||||
await this.usersRepository.update(actor.id, {
|
await this.usersRepository.update(actor.id, {
|
||||||
isDeleted: true,
|
isDeleted: true,
|
||||||
|
|
|
@ -179,6 +179,10 @@ export const packedRolePoliciesSchema = {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
},
|
},
|
||||||
|
canPurgeAccount: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
canUpdateAvatar: {
|
canUpdateAvatar: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
|
|
|
@ -4,14 +4,15 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { DI } from '@/di-symbols.js';
|
|
||||||
import type { DriveFilesRepository, NotesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
|
|
||||||
import type Logger from '@/logger.js';
|
|
||||||
import { DriveService } from '@/core/DriveService.js';
|
|
||||||
import type { MiUser } from '@/models/User.js';
|
|
||||||
import { EmailService } from '@/core/EmailService.js';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import type Logger from '@/logger.js';
|
||||||
|
import type { DriveFilesRepository, NotesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
|
||||||
|
import type { MiUser } from '@/models/User.js';
|
||||||
|
import { DriveService } from '@/core/DriveService.js';
|
||||||
|
import { EmailService } from '@/core/EmailService.js';
|
||||||
import { SearchService } from '@/core/SearchService.js';
|
import { SearchService } from '@/core/SearchService.js';
|
||||||
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||||
import type * as Bull from 'bullmq';
|
import type * as Bull from 'bullmq';
|
||||||
import type { DbUserDeleteJobData } from '../types.js';
|
import type { DbUserDeleteJobData } from '../types.js';
|
||||||
|
@ -35,8 +36,9 @@ export class DeleteAccountProcessorService {
|
||||||
|
|
||||||
private driveService: DriveService,
|
private driveService: DriveService,
|
||||||
private emailService: EmailService,
|
private emailService: EmailService,
|
||||||
private queueLoggerService: QueueLoggerService,
|
|
||||||
private searchService: SearchService,
|
private searchService: SearchService,
|
||||||
|
private roleService: RoleService,
|
||||||
|
private queueLoggerService: QueueLoggerService,
|
||||||
) {
|
) {
|
||||||
this.logger = this.queueLoggerService.logger.createSubLogger('delete-account');
|
this.logger = this.queueLoggerService.logger.createSubLogger('delete-account');
|
||||||
}
|
}
|
||||||
|
@ -87,19 +89,32 @@ export class DeleteAccountProcessorService {
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async process(job: Bull.Job<DbUserDeleteJobData>): Promise<string | void> {
|
public async process(job: Bull.Job<DbUserDeleteJobData>): Promise<string | void> {
|
||||||
this.logger.info(`Deleting account of ${job.data.user.id} ...`);
|
this.logger.info(`Deleting account of ${job.data.user.id} ...`, { userDeleteJobData: job.data });
|
||||||
|
|
||||||
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all([
|
const { canDeleteContent, canPurgeAccount } = !job.data.force
|
||||||
this.deleteNotes(user),
|
? await this.roleService.getUserPolicies(user.id)
|
||||||
this.deleteFiles(user),
|
: { canDeleteContent: true, canPurgeAccount: true };
|
||||||
]);
|
|
||||||
|
|
||||||
{ // Send email notification
|
if (job.data.onlyFiles) {
|
||||||
|
if (!canDeleteContent) return 'Permission denied';
|
||||||
|
|
||||||
|
await this.deleteFiles(user);
|
||||||
|
return 'Files deleted';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canDeleteContent) {
|
||||||
|
await Promise.all([
|
||||||
|
this.deleteNotes(user),
|
||||||
|
this.deleteFiles(user),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user.token) { // Send email notification
|
||||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
|
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
|
||||||
if (profile.email && profile.emailVerified) {
|
if (profile.email && profile.emailVerified) {
|
||||||
await this.emailService.sendEmail(profile.email, 'Account deleted',
|
await this.emailService.sendEmail(profile.email, 'Account deleted',
|
||||||
|
@ -108,9 +123,13 @@ export class DeleteAccountProcessorService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// soft指定されている場合は物理削除しない
|
// 制限されている もしくは soft指定されている場合は物理削除しない、代わりに凍結+削除フラグを立てる
|
||||||
if (job.data.soft) {
|
if (!(canDeleteContent && canPurgeAccount) || job.data.soft) {
|
||||||
// nop
|
await this.usersRepository.update(user.id, {
|
||||||
|
token: null,
|
||||||
|
isSuspended: true,
|
||||||
|
isDeleted: true,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
await this.usersRepository.delete(job.data.user.id);
|
await this.usersRepository.delete(job.data.user.id);
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,6 +79,8 @@ export type DBExportAntennasData = {
|
||||||
export type DbUserDeleteJobData = {
|
export type DbUserDeleteJobData = {
|
||||||
user: ThinUser;
|
user: ThinUser;
|
||||||
soft?: boolean;
|
soft?: boolean;
|
||||||
|
force?: boolean;
|
||||||
|
onlyFiles?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DbUserImportJobData = {
|
export type DbUserImportJobData = {
|
||||||
|
|
|
@ -27,11 +27,11 @@ import * as ep___admin_avatarDecorations_create from './endpoints/admin/avatar-d
|
||||||
import * as ep___admin_avatarDecorations_delete from './endpoints/admin/avatar-decorations/delete.js';
|
import * as ep___admin_avatarDecorations_delete from './endpoints/admin/avatar-decorations/delete.js';
|
||||||
import * as ep___admin_avatarDecorations_list from './endpoints/admin/avatar-decorations/list.js';
|
import * as ep___admin_avatarDecorations_list from './endpoints/admin/avatar-decorations/list.js';
|
||||||
import * as ep___admin_avatarDecorations_update from './endpoints/admin/avatar-decorations/update.js';
|
import * as ep___admin_avatarDecorations_update from './endpoints/admin/avatar-decorations/update.js';
|
||||||
import * as ep___admin_deleteAllFilesOfAUser from './endpoints/admin/delete-all-files-of-a-user.js';
|
|
||||||
import * as ep___admin_unsetUserAvatar from './endpoints/admin/unset-user-avatar.js';
|
import * as ep___admin_unsetUserAvatar from './endpoints/admin/unset-user-avatar.js';
|
||||||
import * as ep___admin_unsetUserBanner from './endpoints/admin/unset-user-banner.js';
|
import * as ep___admin_unsetUserBanner from './endpoints/admin/unset-user-banner.js';
|
||||||
import * as ep___admin_drive_cleanRemoteFiles from './endpoints/admin/drive/clean-remote-files.js';
|
import * as ep___admin_drive_cleanRemoteFiles from './endpoints/admin/drive/clean-remote-files.js';
|
||||||
import * as ep___admin_drive_cleanup from './endpoints/admin/drive/cleanup.js';
|
import * as ep___admin_drive_cleanup from './endpoints/admin/drive/cleanup.js';
|
||||||
|
import * as ep___admin_drive_deleteAllFilesOfAUser from './endpoints/admin/drive/delete-all-files-of-a-user.js';
|
||||||
import * as ep___admin_drive_files from './endpoints/admin/drive/files.js';
|
import * as ep___admin_drive_files from './endpoints/admin/drive/files.js';
|
||||||
import * as ep___admin_drive_showFile from './endpoints/admin/drive/show-file.js';
|
import * as ep___admin_drive_showFile from './endpoints/admin/drive/show-file.js';
|
||||||
import * as ep___admin_emoji_addAliasesBulk from './endpoints/admin/emoji/add-aliases-bulk.js';
|
import * as ep___admin_emoji_addAliasesBulk from './endpoints/admin/emoji/add-aliases-bulk.js';
|
||||||
|
@ -79,7 +79,6 @@ import * as ep___admin_showUsers from './endpoints/admin/show-users.js';
|
||||||
import * as ep___admin_suspendUser from './endpoints/admin/suspend-user.js';
|
import * as ep___admin_suspendUser from './endpoints/admin/suspend-user.js';
|
||||||
import * as ep___admin_unsuspendUser from './endpoints/admin/unsuspend-user.js';
|
import * as ep___admin_unsuspendUser from './endpoints/admin/unsuspend-user.js';
|
||||||
import * as ep___admin_updateMeta from './endpoints/admin/update-meta.js';
|
import * as ep___admin_updateMeta from './endpoints/admin/update-meta.js';
|
||||||
import * as ep___admin_deleteAccount from './endpoints/admin/delete-account.js';
|
|
||||||
import * as ep___admin_updateUserNote from './endpoints/admin/update-user-note.js';
|
import * as ep___admin_updateUserNote from './endpoints/admin/update-user-note.js';
|
||||||
import * as ep___admin_roles_create from './endpoints/admin/roles/create.js';
|
import * as ep___admin_roles_create from './endpoints/admin/roles/create.js';
|
||||||
import * as ep___admin_roles_delete from './endpoints/admin/roles/delete.js';
|
import * as ep___admin_roles_delete from './endpoints/admin/roles/delete.js';
|
||||||
|
@ -413,11 +412,11 @@ const $admin_avatarDecorations_create: Provider = { provide: 'ep:admin/avatar-de
|
||||||
const $admin_avatarDecorations_delete: Provider = { provide: 'ep:admin/avatar-decorations/delete', useClass: ep___admin_avatarDecorations_delete.default };
|
const $admin_avatarDecorations_delete: Provider = { provide: 'ep:admin/avatar-decorations/delete', useClass: ep___admin_avatarDecorations_delete.default };
|
||||||
const $admin_avatarDecorations_list: Provider = { provide: 'ep:admin/avatar-decorations/list', useClass: ep___admin_avatarDecorations_list.default };
|
const $admin_avatarDecorations_list: Provider = { provide: 'ep:admin/avatar-decorations/list', useClass: ep___admin_avatarDecorations_list.default };
|
||||||
const $admin_avatarDecorations_update: Provider = { provide: 'ep:admin/avatar-decorations/update', useClass: ep___admin_avatarDecorations_update.default };
|
const $admin_avatarDecorations_update: Provider = { provide: 'ep:admin/avatar-decorations/update', useClass: ep___admin_avatarDecorations_update.default };
|
||||||
const $admin_deleteAllFilesOfAUser: Provider = { provide: 'ep:admin/delete-all-files-of-a-user', useClass: ep___admin_deleteAllFilesOfAUser.default };
|
|
||||||
const $admin_unsetUserAvatar: Provider = { provide: 'ep:admin/unset-user-avatar', useClass: ep___admin_unsetUserAvatar.default };
|
const $admin_unsetUserAvatar: Provider = { provide: 'ep:admin/unset-user-avatar', useClass: ep___admin_unsetUserAvatar.default };
|
||||||
const $admin_unsetUserBanner: Provider = { provide: 'ep:admin/unset-user-banner', useClass: ep___admin_unsetUserBanner.default };
|
const $admin_unsetUserBanner: Provider = { provide: 'ep:admin/unset-user-banner', useClass: ep___admin_unsetUserBanner.default };
|
||||||
const $admin_drive_cleanRemoteFiles: Provider = { provide: 'ep:admin/drive/clean-remote-files', useClass: ep___admin_drive_cleanRemoteFiles.default };
|
const $admin_drive_cleanRemoteFiles: Provider = { provide: 'ep:admin/drive/clean-remote-files', useClass: ep___admin_drive_cleanRemoteFiles.default };
|
||||||
const $admin_drive_cleanup: Provider = { provide: 'ep:admin/drive/cleanup', useClass: ep___admin_drive_cleanup.default };
|
const $admin_drive_cleanup: Provider = { provide: 'ep:admin/drive/cleanup', useClass: ep___admin_drive_cleanup.default };
|
||||||
|
const $admin_drive_deleteAllFilesOfAUser: Provider = { provide: 'ep:admin/drive/delete-all-files-of-a-user', useClass: ep___admin_drive_deleteAllFilesOfAUser.default };
|
||||||
const $admin_drive_files: Provider = { provide: 'ep:admin/drive/files', useClass: ep___admin_drive_files.default };
|
const $admin_drive_files: Provider = { provide: 'ep:admin/drive/files', useClass: ep___admin_drive_files.default };
|
||||||
const $admin_drive_showFile: Provider = { provide: 'ep:admin/drive/show-file', useClass: ep___admin_drive_showFile.default };
|
const $admin_drive_showFile: Provider = { provide: 'ep:admin/drive/show-file', useClass: ep___admin_drive_showFile.default };
|
||||||
const $admin_emoji_addAliasesBulk: Provider = { provide: 'ep:admin/emoji/add-aliases-bulk', useClass: ep___admin_emoji_addAliasesBulk.default };
|
const $admin_emoji_addAliasesBulk: Provider = { provide: 'ep:admin/emoji/add-aliases-bulk', useClass: ep___admin_emoji_addAliasesBulk.default };
|
||||||
|
@ -465,7 +464,6 @@ const $admin_showUsers: Provider = { provide: 'ep:admin/show-users', useClass: e
|
||||||
const $admin_suspendUser: Provider = { provide: 'ep:admin/suspend-user', useClass: ep___admin_suspendUser.default };
|
const $admin_suspendUser: Provider = { provide: 'ep:admin/suspend-user', useClass: ep___admin_suspendUser.default };
|
||||||
const $admin_unsuspendUser: Provider = { provide: 'ep:admin/unsuspend-user', useClass: ep___admin_unsuspendUser.default };
|
const $admin_unsuspendUser: Provider = { provide: 'ep:admin/unsuspend-user', useClass: ep___admin_unsuspendUser.default };
|
||||||
const $admin_updateMeta: Provider = { provide: 'ep:admin/update-meta', useClass: ep___admin_updateMeta.default };
|
const $admin_updateMeta: Provider = { provide: 'ep:admin/update-meta', useClass: ep___admin_updateMeta.default };
|
||||||
const $admin_deleteAccount: Provider = { provide: 'ep:admin/delete-account', useClass: ep___admin_deleteAccount.default };
|
|
||||||
const $admin_updateUserNote: Provider = { provide: 'ep:admin/update-user-note', useClass: ep___admin_updateUserNote.default };
|
const $admin_updateUserNote: Provider = { provide: 'ep:admin/update-user-note', useClass: ep___admin_updateUserNote.default };
|
||||||
const $admin_roles_create: Provider = { provide: 'ep:admin/roles/create', useClass: ep___admin_roles_create.default };
|
const $admin_roles_create: Provider = { provide: 'ep:admin/roles/create', useClass: ep___admin_roles_create.default };
|
||||||
const $admin_roles_delete: Provider = { provide: 'ep:admin/roles/delete', useClass: ep___admin_roles_delete.default };
|
const $admin_roles_delete: Provider = { provide: 'ep:admin/roles/delete', useClass: ep___admin_roles_delete.default };
|
||||||
|
@ -803,11 +801,11 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
||||||
$admin_avatarDecorations_delete,
|
$admin_avatarDecorations_delete,
|
||||||
$admin_avatarDecorations_list,
|
$admin_avatarDecorations_list,
|
||||||
$admin_avatarDecorations_update,
|
$admin_avatarDecorations_update,
|
||||||
$admin_deleteAllFilesOfAUser,
|
|
||||||
$admin_unsetUserAvatar,
|
$admin_unsetUserAvatar,
|
||||||
$admin_unsetUserBanner,
|
$admin_unsetUserBanner,
|
||||||
$admin_drive_cleanRemoteFiles,
|
$admin_drive_cleanRemoteFiles,
|
||||||
$admin_drive_cleanup,
|
$admin_drive_cleanup,
|
||||||
|
$admin_drive_deleteAllFilesOfAUser,
|
||||||
$admin_drive_files,
|
$admin_drive_files,
|
||||||
$admin_drive_showFile,
|
$admin_drive_showFile,
|
||||||
$admin_emoji_addAliasesBulk,
|
$admin_emoji_addAliasesBulk,
|
||||||
|
@ -855,7 +853,6 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
||||||
$admin_suspendUser,
|
$admin_suspendUser,
|
||||||
$admin_unsuspendUser,
|
$admin_unsuspendUser,
|
||||||
$admin_updateMeta,
|
$admin_updateMeta,
|
||||||
$admin_deleteAccount,
|
|
||||||
$admin_updateUserNote,
|
$admin_updateUserNote,
|
||||||
$admin_roles_create,
|
$admin_roles_create,
|
||||||
$admin_roles_delete,
|
$admin_roles_delete,
|
||||||
|
@ -1187,11 +1184,11 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
||||||
$admin_avatarDecorations_delete,
|
$admin_avatarDecorations_delete,
|
||||||
$admin_avatarDecorations_list,
|
$admin_avatarDecorations_list,
|
||||||
$admin_avatarDecorations_update,
|
$admin_avatarDecorations_update,
|
||||||
$admin_deleteAllFilesOfAUser,
|
|
||||||
$admin_unsetUserAvatar,
|
$admin_unsetUserAvatar,
|
||||||
$admin_unsetUserBanner,
|
$admin_unsetUserBanner,
|
||||||
$admin_drive_cleanRemoteFiles,
|
$admin_drive_cleanRemoteFiles,
|
||||||
$admin_drive_cleanup,
|
$admin_drive_cleanup,
|
||||||
|
$admin_drive_deleteAllFilesOfAUser,
|
||||||
$admin_drive_files,
|
$admin_drive_files,
|
||||||
$admin_drive_showFile,
|
$admin_drive_showFile,
|
||||||
$admin_emoji_addAliasesBulk,
|
$admin_emoji_addAliasesBulk,
|
||||||
|
@ -1239,7 +1236,6 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
||||||
$admin_suspendUser,
|
$admin_suspendUser,
|
||||||
$admin_unsuspendUser,
|
$admin_unsuspendUser,
|
||||||
$admin_updateMeta,
|
$admin_updateMeta,
|
||||||
$admin_deleteAccount,
|
|
||||||
$admin_updateUserNote,
|
$admin_updateUserNote,
|
||||||
$admin_roles_create,
|
$admin_roles_create,
|
||||||
$admin_roles_delete,
|
$admin_roles_delete,
|
||||||
|
|
|
@ -124,6 +124,13 @@ export class SigninApiService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (user.isDeleted && user.isSuspended) {
|
||||||
|
logger.error('No such user. (logical deletion)');
|
||||||
|
return error(404, {
|
||||||
|
id: '6cc579cc-885d-43d8-95c2-b8c7fc963280',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (user.isSuspended) {
|
if (user.isSuspended) {
|
||||||
logger.error('User is suspended.');
|
logger.error('User is suspended.');
|
||||||
return error(403, {
|
return error(403, {
|
||||||
|
|
|
@ -27,11 +27,11 @@ import * as ep___admin_avatarDecorations_create from './endpoints/admin/avatar-d
|
||||||
import * as ep___admin_avatarDecorations_delete from './endpoints/admin/avatar-decorations/delete.js';
|
import * as ep___admin_avatarDecorations_delete from './endpoints/admin/avatar-decorations/delete.js';
|
||||||
import * as ep___admin_avatarDecorations_list from './endpoints/admin/avatar-decorations/list.js';
|
import * as ep___admin_avatarDecorations_list from './endpoints/admin/avatar-decorations/list.js';
|
||||||
import * as ep___admin_avatarDecorations_update from './endpoints/admin/avatar-decorations/update.js';
|
import * as ep___admin_avatarDecorations_update from './endpoints/admin/avatar-decorations/update.js';
|
||||||
import * as ep___admin_deleteAllFilesOfAUser from './endpoints/admin/delete-all-files-of-a-user.js';
|
|
||||||
import * as ep___admin_unsetUserAvatar from './endpoints/admin/unset-user-avatar.js';
|
import * as ep___admin_unsetUserAvatar from './endpoints/admin/unset-user-avatar.js';
|
||||||
import * as ep___admin_unsetUserBanner from './endpoints/admin/unset-user-banner.js';
|
import * as ep___admin_unsetUserBanner from './endpoints/admin/unset-user-banner.js';
|
||||||
import * as ep___admin_drive_cleanRemoteFiles from './endpoints/admin/drive/clean-remote-files.js';
|
import * as ep___admin_drive_cleanRemoteFiles from './endpoints/admin/drive/clean-remote-files.js';
|
||||||
import * as ep___admin_drive_cleanup from './endpoints/admin/drive/cleanup.js';
|
import * as ep___admin_drive_cleanup from './endpoints/admin/drive/cleanup.js';
|
||||||
|
import * as ep___admin_drive_deleteAllFilesOfAUser from './endpoints/admin/drive/delete-all-files-of-a-user.js';
|
||||||
import * as ep___admin_drive_files from './endpoints/admin/drive/files.js';
|
import * as ep___admin_drive_files from './endpoints/admin/drive/files.js';
|
||||||
import * as ep___admin_drive_showFile from './endpoints/admin/drive/show-file.js';
|
import * as ep___admin_drive_showFile from './endpoints/admin/drive/show-file.js';
|
||||||
import * as ep___admin_emoji_addAliasesBulk from './endpoints/admin/emoji/add-aliases-bulk.js';
|
import * as ep___admin_emoji_addAliasesBulk from './endpoints/admin/emoji/add-aliases-bulk.js';
|
||||||
|
@ -79,7 +79,6 @@ import * as ep___admin_showUsers from './endpoints/admin/show-users.js';
|
||||||
import * as ep___admin_suspendUser from './endpoints/admin/suspend-user.js';
|
import * as ep___admin_suspendUser from './endpoints/admin/suspend-user.js';
|
||||||
import * as ep___admin_unsuspendUser from './endpoints/admin/unsuspend-user.js';
|
import * as ep___admin_unsuspendUser from './endpoints/admin/unsuspend-user.js';
|
||||||
import * as ep___admin_updateMeta from './endpoints/admin/update-meta.js';
|
import * as ep___admin_updateMeta from './endpoints/admin/update-meta.js';
|
||||||
import * as ep___admin_deleteAccount from './endpoints/admin/delete-account.js';
|
|
||||||
import * as ep___admin_updateUserNote from './endpoints/admin/update-user-note.js';
|
import * as ep___admin_updateUserNote from './endpoints/admin/update-user-note.js';
|
||||||
import * as ep___admin_roles_create from './endpoints/admin/roles/create.js';
|
import * as ep___admin_roles_create from './endpoints/admin/roles/create.js';
|
||||||
import * as ep___admin_roles_delete from './endpoints/admin/roles/delete.js';
|
import * as ep___admin_roles_delete from './endpoints/admin/roles/delete.js';
|
||||||
|
@ -411,11 +410,11 @@ const eps = [
|
||||||
['admin/avatar-decorations/delete', ep___admin_avatarDecorations_delete],
|
['admin/avatar-decorations/delete', ep___admin_avatarDecorations_delete],
|
||||||
['admin/avatar-decorations/list', ep___admin_avatarDecorations_list],
|
['admin/avatar-decorations/list', ep___admin_avatarDecorations_list],
|
||||||
['admin/avatar-decorations/update', ep___admin_avatarDecorations_update],
|
['admin/avatar-decorations/update', ep___admin_avatarDecorations_update],
|
||||||
['admin/delete-all-files-of-a-user', ep___admin_deleteAllFilesOfAUser],
|
|
||||||
['admin/unset-user-avatar', ep___admin_unsetUserAvatar],
|
['admin/unset-user-avatar', ep___admin_unsetUserAvatar],
|
||||||
['admin/unset-user-banner', ep___admin_unsetUserBanner],
|
['admin/unset-user-banner', ep___admin_unsetUserBanner],
|
||||||
['admin/drive/clean-remote-files', ep___admin_drive_cleanRemoteFiles],
|
['admin/drive/clean-remote-files', ep___admin_drive_cleanRemoteFiles],
|
||||||
['admin/drive/cleanup', ep___admin_drive_cleanup],
|
['admin/drive/cleanup', ep___admin_drive_cleanup],
|
||||||
|
['admin/drive/delete-all-files-of-a-user', ep___admin_drive_deleteAllFilesOfAUser],
|
||||||
['admin/drive/files', ep___admin_drive_files],
|
['admin/drive/files', ep___admin_drive_files],
|
||||||
['admin/drive/show-file', ep___admin_drive_showFile],
|
['admin/drive/show-file', ep___admin_drive_showFile],
|
||||||
['admin/emoji/add-aliases-bulk', ep___admin_emoji_addAliasesBulk],
|
['admin/emoji/add-aliases-bulk', ep___admin_emoji_addAliasesBulk],
|
||||||
|
@ -463,7 +462,6 @@ const eps = [
|
||||||
['admin/suspend-user', ep___admin_suspendUser],
|
['admin/suspend-user', ep___admin_suspendUser],
|
||||||
['admin/unsuspend-user', ep___admin_unsuspendUser],
|
['admin/unsuspend-user', ep___admin_unsuspendUser],
|
||||||
['admin/update-meta', ep___admin_updateMeta],
|
['admin/update-meta', ep___admin_updateMeta],
|
||||||
['admin/delete-account', ep___admin_deleteAccount],
|
|
||||||
['admin/update-user-note', ep___admin_updateUserNote],
|
['admin/update-user-note', ep___admin_updateUserNote],
|
||||||
['admin/roles/create', ep___admin_roles_create],
|
['admin/roles/create', ep___admin_roles_create],
|
||||||
['admin/roles/delete', ep___admin_roles_delete],
|
['admin/roles/delete', ep___admin_roles_delete],
|
||||||
|
|
|
@ -5,11 +5,11 @@
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import type { UsersRepository } from '@/models/_.js';
|
|
||||||
import { QueueService } from '@/core/QueueService.js';
|
|
||||||
import { UserSuspendService } from '@/core/UserSuspendService.js';
|
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import type { UsersRepository } from '@/models/_.js';
|
||||||
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
|
import { DeleteAccountService } from '@/core/DeleteAccountService.js';
|
||||||
|
import { ApiError } from '@/server/api/error.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['admin'],
|
tags: ['admin'],
|
||||||
|
@ -17,6 +17,20 @@ export const meta = {
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
requireAdmin: true,
|
requireAdmin: true,
|
||||||
kind: 'write:admin:account',
|
kind: 'write:admin:account',
|
||||||
|
|
||||||
|
errors: {
|
||||||
|
userNotFound: {
|
||||||
|
message: 'User not found.',
|
||||||
|
code: 'USER_NOT_FOUND',
|
||||||
|
id: '6c45276a-525e-46b0-892f-17a5036258bf',
|
||||||
|
},
|
||||||
|
|
||||||
|
cannotDeleteModerator: {
|
||||||
|
message: 'Cannot delete a moderator.',
|
||||||
|
code: 'CANNOT_DELETE_MODERATOR',
|
||||||
|
id: 'd195c621-f21a-4c2f-a634-484c2a616311',
|
||||||
|
},
|
||||||
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const paramDef = {
|
export const paramDef = {
|
||||||
|
@ -33,37 +47,17 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
@Inject(DI.usersRepository)
|
@Inject(DI.usersRepository)
|
||||||
private usersRepository: UsersRepository,
|
private usersRepository: UsersRepository,
|
||||||
|
|
||||||
private userEntityService: UserEntityService,
|
private roleService: RoleService,
|
||||||
private queueService: QueueService,
|
private deleteAccountService: DeleteAccountService,
|
||||||
private userSuspendService: UserSuspendService,
|
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const user = await this.usersRepository.findOneBy({ id: ps.userId });
|
const user = await this.usersRepository.findOneBy({ id: ps.userId });
|
||||||
|
|
||||||
if (user == null) {
|
if (user == null) throw new ApiError(meta.errors.userNotFound);
|
||||||
throw new Error('user not found');
|
if (await this.roleService.isModerator(user)) throw new ApiError(meta.errors.cannotDeleteModerator);
|
||||||
}
|
|
||||||
|
|
||||||
if (user.isRoot) {
|
// 管理者からの削除ということはモデレーション行為なので、soft delete にする
|
||||||
throw new Error('cannot delete a root account');
|
await this.deleteAccountService.deleteAccount(user, true, me);
|
||||||
}
|
|
||||||
|
|
||||||
if (this.userEntityService.isLocalUser(user)) {
|
|
||||||
// 物理削除する前にDelete activityを送信する
|
|
||||||
await this.userSuspendService.doPostSuspend(user).catch(err => {});
|
|
||||||
|
|
||||||
this.queueService.createDeleteAccountJob(user, {
|
|
||||||
soft: false,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.queueService.createDeleteAccountJob(user, {
|
|
||||||
soft: true, // リモートユーザーの削除は、完全にDBから物理削除してしまうと再度連合してきてアカウントが復活する可能性があるため、soft指定する
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.usersRepository.update(user.id, {
|
|
||||||
isDeleted: true,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
|
||||||
import type { DriveFilesRepository } from '@/models/_.js';
|
|
||||||
import { DriveService } from '@/core/DriveService.js';
|
|
||||||
import { DI } from '@/di-symbols.js';
|
|
||||||
|
|
||||||
export const meta = {
|
|
||||||
tags: ['admin'],
|
|
||||||
|
|
||||||
requireCredential: true,
|
|
||||||
requireAdmin: true,
|
|
||||||
kind: 'write:admin:delete-all-files-of-a-user',
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export const paramDef = {
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
userId: { type: 'string', format: 'misskey:id' },
|
|
||||||
},
|
|
||||||
required: ['userId'],
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
|
||||||
constructor(
|
|
||||||
@Inject(DI.driveFilesRepository)
|
|
||||||
private driveFilesRepository: DriveFilesRepository,
|
|
||||||
|
|
||||||
private driveService: DriveService,
|
|
||||||
) {
|
|
||||||
super(meta, paramDef, async (ps, me) => {
|
|
||||||
const files = await this.driveFilesRepository.findBy({
|
|
||||||
userId: ps.userId,
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const file of files) {
|
|
||||||
this.driveService.deleteFile(file);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -4,17 +4,17 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import type { UsersRepository } from '@/models/_.js';
|
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
import { DeleteAccountService } from '@/core/DeleteAccountService.js';
|
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import type { UsersRepository } from '@/models/_.js';
|
||||||
|
import { DeleteAccountService } from '@/core/DeleteAccountService.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['admin'],
|
tags: ['admin'],
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
requireAdmin: true,
|
requireModerator: true,
|
||||||
kind: 'write:admin:delete-account',
|
kind: 'write:admin:drive',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const paramDef = {
|
export const paramDef = {
|
||||||
|
@ -33,13 +33,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
|
|
||||||
private deleteAccountService: DeleteAccountService,
|
private deleteAccountService: DeleteAccountService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const user = await this.usersRepository.findOneByOrFail({ id: ps.userId });
|
const user = await this.usersRepository.findOneByOrFail({ id: ps.userId });
|
||||||
if (user.isDeleted) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.deleteAccountService.deleteAccount(user);
|
await this.deleteAccountService.deleteAllDriveFiles(user, me);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -115,6 +115,14 @@ export const meta = {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
},
|
},
|
||||||
|
isLimited: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
isDeleted: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
isSuspended: {
|
isSuspended: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
|
@ -242,6 +250,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
isModerator: isModerator,
|
isModerator: isModerator,
|
||||||
isSilenced: isSilenced,
|
isSilenced: isSilenced,
|
||||||
isLimited: isLimited,
|
isLimited: isLimited,
|
||||||
|
isDeleted: user.isDeleted,
|
||||||
isSuspended: user.isSuspended,
|
isSuspended: user.isSuspended,
|
||||||
isHibernated: user.isHibernated,
|
isHibernated: user.isHibernated,
|
||||||
lastActiveDate: user.lastActiveDate ? user.lastActiveDate.toISOString() : null,
|
lastActiveDate: user.lastActiveDate ? user.lastActiveDate.toISOString() : null,
|
||||||
|
|
|
@ -14,7 +14,6 @@ import { ApiError } from '@/server/api/error.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
requireRolePolicy: 'canDeleteContent',
|
|
||||||
|
|
||||||
secure: true,
|
secure: true,
|
||||||
|
|
||||||
|
@ -76,7 +75,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
await this.userAuthService.twoFactorAuthenticate(profile, token);
|
await this.userAuthService.twoFactorAuthenticate(profile, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.deleteAccountService.deleteAccount(me);
|
await this.deleteAccountService.deleteAccount(me, false, me);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,7 +104,7 @@ describe('アンテナ', () => {
|
||||||
await api('i/delete-account', { password: 'userDeletedBySelf' }, userDeletedBySelf);
|
await api('i/delete-account', { password: 'userDeletedBySelf' }, userDeletedBySelf);
|
||||||
userDeletedByAdmin = await signup({ username: 'userDeletedByAdmin' });
|
userDeletedByAdmin = await signup({ username: 'userDeletedByAdmin' });
|
||||||
await post(userDeletedByAdmin, { text: 'test' });
|
await post(userDeletedByAdmin, { text: 'test' });
|
||||||
await api('admin/delete-account', { userId: userDeletedByAdmin.id }, root);
|
await api('admin/accounts/delete', { userId: userDeletedByAdmin.id }, root);
|
||||||
userFollowedByAlice = await signup({ username: 'userFollowedByAlice' });
|
userFollowedByAlice = await signup({ username: 'userFollowedByAlice' });
|
||||||
await post(userFollowedByAlice, { text: 'test' });
|
await post(userFollowedByAlice, { text: 'test' });
|
||||||
await api('following/create', { userId: userFollowedByAlice.id }, alice);
|
await api('following/create', { userId: userFollowedByAlice.id }, alice);
|
||||||
|
|
|
@ -246,7 +246,7 @@ describe('ユーザー', () => {
|
||||||
await api('i/delete-account', { password: 'userDeletedBySelf' }, userDeletedBySelf);
|
await api('i/delete-account', { password: 'userDeletedBySelf' }, userDeletedBySelf);
|
||||||
userDeletedByAdmin = await signup({ username: 'userDeletedByAdmin' });
|
userDeletedByAdmin = await signup({ username: 'userDeletedByAdmin' });
|
||||||
await post(userDeletedByAdmin, { text: 'test' });
|
await post(userDeletedByAdmin, { text: 'test' });
|
||||||
await api('admin/delete-account', { userId: userDeletedByAdmin.id }, root);
|
await api('admin/accounts/delete', { userId: userDeletedByAdmin.id }, root);
|
||||||
userFollowingAlice = await signup({ username: 'userFollowingAlice' });
|
userFollowingAlice = await signup({ username: 'userFollowingAlice' });
|
||||||
await post(userFollowingAlice, { text: 'test' });
|
await post(userFollowingAlice, { text: 'test' });
|
||||||
await api('following/create', { userId: alice.id }, userFollowingAlice);
|
await api('following/create', { userId: alice.id }, userFollowingAlice);
|
||||||
|
|
|
@ -108,6 +108,7 @@ export function userDetailed(id = 'someuserid', username = 'miskist', host = 'mi
|
||||||
isMuted: false,
|
isMuted: false,
|
||||||
isSilenced: false,
|
isSilenced: false,
|
||||||
isLimited: false,
|
isLimited: false,
|
||||||
|
isDeleted: false,
|
||||||
isSuspended: false,
|
isSuspended: false,
|
||||||
lang: 'en',
|
lang: 'en',
|
||||||
location: 'Fediverse',
|
location: 'Fediverse',
|
||||||
|
|
|
@ -79,6 +79,7 @@ export const ROLE_POLICIES = [
|
||||||
'canCreateContent',
|
'canCreateContent',
|
||||||
'canUpdateContent',
|
'canUpdateContent',
|
||||||
'canDeleteContent',
|
'canDeleteContent',
|
||||||
|
'canPurgeAccount',
|
||||||
'canUpdateAvatar',
|
'canUpdateAvatar',
|
||||||
'canUpdateBanner',
|
'canUpdateBanner',
|
||||||
'mentionLimit',
|
'mentionLimit',
|
||||||
|
|
|
@ -15,10 +15,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<span class="name"><MkUserName class="name" :user="user"/></span>
|
<span class="name"><MkUserName class="name" :user="user"/></span>
|
||||||
<span class="sub"><span class="acct _monospace">@{{ acct(user) }}</span></span>
|
<span class="sub"><span class="acct _monospace">@{{ acct(user) }}</span></span>
|
||||||
<span class="state">
|
<span class="state">
|
||||||
<span v-if="suspended" class="suspended">Suspended</span>
|
<span v-if="admin" class="admin">Admin</span>
|
||||||
<span v-if="limited" class="limited">Limited</span>
|
|
||||||
<span v-if="silenced" class="silenced">Silenced</span>
|
|
||||||
<span v-if="moderator" class="moderator">Moderator</span>
|
<span v-if="moderator" class="moderator">Moderator</span>
|
||||||
|
<span v-if="silenced" class="silenced">Silenced</span>
|
||||||
|
<span v-if="limited" class="limited">Limited</span>
|
||||||
|
<span v-if="suspended" class="suspended">Suspended</span>
|
||||||
|
<span v-if="deleted" class="deleted">Deleted</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -32,12 +34,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #key>ID</template>
|
<template #key>ID</template>
|
||||||
<template #value><span class="_monospace">{{ user.id }}</span></template>
|
<template #value><span class="_monospace">{{ user.id }}</span></template>
|
||||||
</MkKeyValue>
|
</MkKeyValue>
|
||||||
<!-- 要る?
|
|
||||||
<MkKeyValue v-if="ips.length > 0" :copy="user.id" oneline>
|
|
||||||
<template #key>IP (recent)</template>
|
|
||||||
<template #value><span class="_monospace">{{ ips[0].ip }}</span></template>
|
|
||||||
</MkKeyValue>
|
|
||||||
-->
|
|
||||||
<MkKeyValue oneline>
|
<MkKeyValue oneline>
|
||||||
<template #key>{{ i18n.ts.createdAt }}</template>
|
<template #key>{{ i18n.ts.createdAt }}</template>
|
||||||
<template #value><span class="_monospace"><MkTime :time="user.createdAt" :mode="'detail'"/></span></template>
|
<template #value><span class="_monospace"><MkTime :time="user.createdAt" :mode="'detail'"/></span></template>
|
||||||
|
@ -50,48 +46,16 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #key>{{ i18n.ts.email }}</template>
|
<template #key>{{ i18n.ts.email }}</template>
|
||||||
<template #value><span class="_monospace">{{ info.email }}</span></template>
|
<template #value><span class="_monospace">{{ info.email }}</span></template>
|
||||||
</MkKeyValue>
|
</MkKeyValue>
|
||||||
|
<MkKeyValue v-if="ips.length > 0" :copy="user.id" oneline>
|
||||||
|
<template #key>IP (recent)</template>
|
||||||
|
<template #value><span class="_monospace">{{ ips[0].ip }}</span></template>
|
||||||
|
</MkKeyValue>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MkTextarea v-model="moderationNote" manualSave>
|
<MkTextarea v-model="moderationNote" manualSave>
|
||||||
<template #label>{{ i18n.ts.moderationNote }}</template>
|
<template #label>{{ i18n.ts.moderationNote }}</template>
|
||||||
</MkTextarea>
|
</MkTextarea>
|
||||||
|
|
||||||
<!--
|
|
||||||
<FormSection>
|
|
||||||
<template #label>ActivityPub</template>
|
|
||||||
|
|
||||||
<div class="_gaps_m">
|
|
||||||
<div style="display: flex; flex-direction: column; gap: 1em;">
|
|
||||||
<MkKeyValue v-if="user.host" oneline>
|
|
||||||
<template #key>{{ i18n.ts.instanceInfo }}</template>
|
|
||||||
<template #value><MkA :to="`/instance-info/${user.host}`" class="_link">{{ user.host }} <i class="ti ti-chevron-right"></i></MkA></template>
|
|
||||||
</MkKeyValue>
|
|
||||||
<MkKeyValue v-else oneline>
|
|
||||||
<template #key>{{ i18n.ts.instanceInfo }}</template>
|
|
||||||
<template #value>(Local user)</template>
|
|
||||||
</MkKeyValue>
|
|
||||||
<MkKeyValue oneline>
|
|
||||||
<template #key>{{ i18n.ts.updatedAt }}</template>
|
|
||||||
<template #value><MkTime v-if="user.lastFetchedAt" mode="detail" :time="user.lastFetchedAt"/><span v-else>N/A</span></template>
|
|
||||||
</MkKeyValue>
|
|
||||||
<MkKeyValue v-if="ap" oneline>
|
|
||||||
<template #key>Type</template>
|
|
||||||
<template #value><span class="_monospace">{{ ap.type }}</span></template>
|
|
||||||
</MkKeyValue>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<MkButton v-if="user.host != null" @click="updateRemoteUser"><i class="ti ti-refresh"></i> {{ i18n.ts.updateRemoteUser }}</MkButton>
|
|
||||||
|
|
||||||
<MkFolder>
|
|
||||||
<template #label>Raw</template>
|
|
||||||
|
|
||||||
<MkObjectView v-if="ap" tall :value="ap">
|
|
||||||
</MkObjectView>
|
|
||||||
</MkFolder>
|
|
||||||
</div>
|
|
||||||
</FormSection>
|
|
||||||
-->
|
|
||||||
|
|
||||||
<FormSection>
|
<FormSection>
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<MkSwitch v-model="suspended" @update:modelValue="toggleSuspend">{{ i18n.ts.suspend }}</MkSwitch>
|
<MkSwitch v-model="suspended" @update:modelValue="toggleSuspend">{{ i18n.ts.suspend }}</MkSwitch>
|
||||||
|
@ -123,8 +87,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
|
|
||||||
<div>
|
<div class="_buttons">
|
||||||
<MkButton v-if="iAmModerator" inline danger style="margin-right: 8px;" @click="unsetUserAvatar"><i class="ti ti-user-circle"></i> {{ i18n.ts.unsetUserAvatar }}</MkButton>
|
<MkButton v-if="iAmModerator" inline danger @click="unsetUserAvatar"><i class="ti ti-user-circle"></i> {{ i18n.ts.unsetUserAvatar }}</MkButton>
|
||||||
<MkButton v-if="iAmModerator" inline danger @click="unsetUserBanner"><i class="ti ti-photo"></i> {{ i18n.ts.unsetUserBanner }}</MkButton>
|
<MkButton v-if="iAmModerator" inline danger @click="unsetUserBanner"><i class="ti ti-photo"></i> {{ i18n.ts.unsetUserBanner }}</MkButton>
|
||||||
</div>
|
</div>
|
||||||
<MkButton v-if="$i.isAdmin" inline danger @click="deleteAccount">{{ i18n.ts.deleteAccount }}</MkButton>
|
<MkButton v-if="$i.isAdmin" inline danger @click="deleteAccount">{{ i18n.ts.deleteAccount }}</MkButton>
|
||||||
|
@ -172,6 +136,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="tab === 'drive'" class="_gaps">
|
<div v-else-if="tab === 'drive'" class="_gaps">
|
||||||
|
<MkButton v-if="iAmModerator" inline danger @click="deleteAllFiles"><i class="ti ti-trash"></i> {{ i18n.ts.deleteAllFiles }}</MkButton>
|
||||||
<MkFileListForAdmin :pagination="filesPagination" viewMode="grid"/>
|
<MkFileListForAdmin :pagination="filesPagination" viewMode="grid"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -191,6 +156,36 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="tab === 'activitypub'" class="_gaps_m">
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 1em;">
|
||||||
|
<MkKeyValue v-if="user.host" oneline>
|
||||||
|
<template #key>{{ i18n.ts.instanceInfo }}</template>
|
||||||
|
<template #value><MkA :to="`/instance-info/${user.host}`" class="_link">{{ user.host }} <i class="ti ti-chevron-right"></i></MkA></template>
|
||||||
|
</MkKeyValue>
|
||||||
|
<MkKeyValue v-else oneline>
|
||||||
|
<template #key>{{ i18n.ts.instanceInfo }}</template>
|
||||||
|
<template #value>(Local user)</template>
|
||||||
|
</MkKeyValue>
|
||||||
|
<MkKeyValue oneline>
|
||||||
|
<template #key>{{ i18n.ts.updatedAt }}</template>
|
||||||
|
<template #value><MkTime v-if="user.lastFetchedAt" mode="detail" :time="user.lastFetchedAt"/><span v-else>N/A</span></template>
|
||||||
|
</MkKeyValue>
|
||||||
|
<MkKeyValue v-if="ap" oneline>
|
||||||
|
<template #key>Type</template>
|
||||||
|
<template #value><span class="_monospace">{{ ap.type }}</span></template>
|
||||||
|
</MkKeyValue>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<MkButton v-if="user.host != null" @click="updateRemoteUser"><i class="ti ti-refresh"></i> {{ i18n.ts.updateRemoteUser }}</MkButton>
|
||||||
|
|
||||||
|
<MkFolder>
|
||||||
|
<template #label>Raw</template>
|
||||||
|
|
||||||
|
<MkObjectView v-if="ap" tall :value="ap">
|
||||||
|
</MkObjectView>
|
||||||
|
</MkFolder>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-else-if="tab === 'raw'" class="_gaps_m">
|
<div v-else-if="tab === 'raw'" class="_gaps_m">
|
||||||
<MkObjectView v-if="info && $i.isAdmin" tall :value="info">
|
<MkObjectView v-if="info && $i.isAdmin" tall :value="info">
|
||||||
</MkObjectView>
|
</MkObjectView>
|
||||||
|
@ -243,10 +238,12 @@ const init = ref<ReturnType<typeof createFetcher>>();
|
||||||
const info = ref<any>();
|
const info = ref<any>();
|
||||||
const ips = ref<Misskey.entities.AdminGetUserIpsResponse | null>(null);
|
const ips = ref<Misskey.entities.AdminGetUserIpsResponse | null>(null);
|
||||||
const ap = ref<any>(null);
|
const ap = ref<any>(null);
|
||||||
|
const admin = ref(false);
|
||||||
const moderator = ref(false);
|
const moderator = ref(false);
|
||||||
const silenced = ref(false);
|
const silenced = ref(false);
|
||||||
const limited = ref(false);
|
const limited = ref(false);
|
||||||
const suspended = ref(false);
|
const suspended = ref(false);
|
||||||
|
const deleted = ref(false);
|
||||||
const moderationNote = ref('');
|
const moderationNote = ref('');
|
||||||
const filesPagination = {
|
const filesPagination = {
|
||||||
endpoint: 'admin/drive/files' as const,
|
endpoint: 'admin/drive/files' as const,
|
||||||
|
@ -277,15 +274,18 @@ function createFetcher() {
|
||||||
user.value = _user;
|
user.value = _user;
|
||||||
info.value = _info;
|
info.value = _info;
|
||||||
ips.value = _ips;
|
ips.value = _ips;
|
||||||
|
admin.value = info.value.isAdmin;
|
||||||
moderator.value = info.value.isModerator;
|
moderator.value = info.value.isModerator;
|
||||||
silenced.value = info.value.isSilenced;
|
silenced.value = info.value.isSilenced;
|
||||||
limited.value = info.value.isLimited;
|
limited.value = info.value.isLimited;
|
||||||
suspended.value = info.value.isSuspended;
|
suspended.value = info.value.isSuspended;
|
||||||
|
deleted.value = info.value.isDeleted;
|
||||||
moderationNote.value = info.value.moderationNote;
|
moderationNote.value = info.value.moderationNote;
|
||||||
|
|
||||||
watch(moderationNote, async () => {
|
watch(moderationNote, async () => {
|
||||||
await misskeyApi('admin/update-user-note', { userId: user.value.id, text: moderationNote.value });
|
await misskeyApi('admin/update-user-note', {
|
||||||
await refreshUser();
|
userId: user.value.id, text: moderationNote.value
|
||||||
|
}).then(refreshUser);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -295,8 +295,9 @@ function refreshUser() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateRemoteUser() {
|
async function updateRemoteUser() {
|
||||||
await os.apiWithDialog('federation/update-remote-user', { userId: user.value.id });
|
await os.apiWithDialog('federation/update-remote-user', {
|
||||||
refreshUser();
|
userId: user.value.id
|
||||||
|
}).then(refreshUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resetPassword() {
|
async function resetPassword() {
|
||||||
|
@ -325,8 +326,9 @@ async function toggleSuspend(v) {
|
||||||
if (confirm.canceled) {
|
if (confirm.canceled) {
|
||||||
suspended.value = !v;
|
suspended.value = !v;
|
||||||
} else {
|
} else {
|
||||||
await misskeyApi(v ? 'admin/suspend-user' : 'admin/unsuspend-user', { userId: user.value.id });
|
await misskeyApi(v ? 'admin/suspend-user' : 'admin/unsuspend-user', {
|
||||||
await refreshUser();
|
userId: user.value.id
|
||||||
|
}).then(refreshUser);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -336,17 +338,10 @@ async function unsetUserAvatar() {
|
||||||
text: i18n.ts.unsetUserAvatarConfirm,
|
text: i18n.ts.unsetUserAvatarConfirm,
|
||||||
});
|
});
|
||||||
if (confirm.canceled) return;
|
if (confirm.canceled) return;
|
||||||
const process = async () => {
|
|
||||||
await misskeyApi('admin/unset-user-avatar', { userId: user.value.id });
|
await os.apiWithDialog('admin/unset-user-avatar', {
|
||||||
os.success();
|
userId: user.value.id
|
||||||
};
|
}).then(refreshUser);
|
||||||
await process().catch(err => {
|
|
||||||
os.alert({
|
|
||||||
type: 'error',
|
|
||||||
text: err.toString(),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
refreshUser();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function unsetUserBanner() {
|
async function unsetUserBanner() {
|
||||||
|
@ -355,17 +350,10 @@ async function unsetUserBanner() {
|
||||||
text: i18n.ts.unsetUserBannerConfirm,
|
text: i18n.ts.unsetUserBannerConfirm,
|
||||||
});
|
});
|
||||||
if (confirm.canceled) return;
|
if (confirm.canceled) return;
|
||||||
const process = async () => {
|
|
||||||
await misskeyApi('admin/unset-user-banner', { userId: user.value.id });
|
await os.apiWithDialog('admin/unset-user-banner', {
|
||||||
os.success();
|
userId: user.value.id
|
||||||
};
|
}).then(refreshUser);
|
||||||
await process().catch(err => {
|
|
||||||
os.alert({
|
|
||||||
type: 'error',
|
|
||||||
text: err.toString(),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
refreshUser();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteAllFiles() {
|
async function deleteAllFiles() {
|
||||||
|
@ -374,17 +362,22 @@ async function deleteAllFiles() {
|
||||||
text: i18n.ts.deleteAllFilesConfirm,
|
text: i18n.ts.deleteAllFilesConfirm,
|
||||||
});
|
});
|
||||||
if (confirm.canceled) return;
|
if (confirm.canceled) return;
|
||||||
const process = async () => {
|
|
||||||
await misskeyApi('admin/delete-all-files-of-a-user', { userId: user.value.id });
|
const typed = await os.inputText({
|
||||||
os.success();
|
text: i18n.tsx.typeToConfirm({ x: user.value?.username }),
|
||||||
};
|
});
|
||||||
await process().catch(err => {
|
if (typed.canceled) return;
|
||||||
|
|
||||||
|
if (typed.result === user.value?.username) {
|
||||||
|
await os.apiWithDialog('admin/drive/delete-all-files-of-a-user', {
|
||||||
|
userId: user.value.id
|
||||||
|
}).then(refreshUser);
|
||||||
|
} else {
|
||||||
os.alert({
|
os.alert({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
text: err.toString(),
|
text: 'input not match',
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
await refreshUser();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteAccount() {
|
async function deleteAccount() {
|
||||||
|
@ -400,9 +393,9 @@ async function deleteAccount() {
|
||||||
if (typed.canceled) return;
|
if (typed.canceled) return;
|
||||||
|
|
||||||
if (typed.result === user.value?.username) {
|
if (typed.result === user.value?.username) {
|
||||||
await os.apiWithDialog('admin/delete-account', {
|
await os.apiWithDialog('admin/accounts/delete', {
|
||||||
userId: user.value.id,
|
userId: user.value.id,
|
||||||
});
|
}).then(refreshUser);
|
||||||
} else {
|
} else {
|
||||||
os.alert({
|
os.alert({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
|
@ -444,8 +437,9 @@ async function assignRole() {
|
||||||
: period === 'oneMonth' ? Date.now() + (1000 * 60 * 60 * 24 * 30)
|
: period === 'oneMonth' ? Date.now() + (1000 * 60 * 60 * 24 * 30)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
await os.apiWithDialog('admin/roles/assign', { roleId, userId: user.value.id, expiresAt });
|
await os.apiWithDialog('admin/roles/assign', {
|
||||||
refreshUser();
|
roleId, userId: user.value.id, expiresAt
|
||||||
|
}).then(refreshUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function unassignRole(role, ev) {
|
async function unassignRole(role, ev) {
|
||||||
|
@ -454,8 +448,9 @@ async function unassignRole(role, ev) {
|
||||||
icon: 'ti ti-x',
|
icon: 'ti ti-x',
|
||||||
danger: true,
|
danger: true,
|
||||||
action: async () => {
|
action: async () => {
|
||||||
await os.apiWithDialog('admin/roles/unassign', { roleId: role.id, userId: user.value.id });
|
await os.apiWithDialog('admin/roles/unassign', {
|
||||||
refreshUser();
|
roleId: role.id, userId: user.value.id
|
||||||
|
}).then(refreshUser);
|
||||||
},
|
},
|
||||||
}], ev.currentTarget ?? ev.target);
|
}], ev.currentTarget ?? ev.target);
|
||||||
}
|
}
|
||||||
|
@ -525,6 +520,10 @@ const headerTabs = computed(() => [{
|
||||||
key: 'chart',
|
key: 'chart',
|
||||||
title: i18n.ts.charts,
|
title: i18n.ts.charts,
|
||||||
icon: 'ti ti-chart-line',
|
icon: 'ti ti-chart-line',
|
||||||
|
}, {
|
||||||
|
key: 'activitypub',
|
||||||
|
title: 'ActivityPub',
|
||||||
|
icon: 'ti ti-share',
|
||||||
}, {
|
}, {
|
||||||
key: 'raw',
|
key: 'raw',
|
||||||
title: 'Raw',
|
title: 'Raw',
|
||||||
|
@ -581,7 +580,12 @@ definePageMetadata(() => ({
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .suspended, > .limited, > .silenced, > .moderator {
|
> .admin,
|
||||||
|
> .moderator,
|
||||||
|
> .silenced,
|
||||||
|
> .limited,
|
||||||
|
> .suspended,
|
||||||
|
> .deleted {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
border: solid 1px;
|
border: solid 1px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
|
@ -589,14 +593,14 @@ definePageMetadata(() => ({
|
||||||
font-size: 85%;
|
font-size: 85%;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .suspended {
|
> .admin {
|
||||||
color: var(--error);
|
color: var(--success);
|
||||||
border-color: var(--error);
|
border-color: var(--success);
|
||||||
}
|
}
|
||||||
|
|
||||||
> .limited {
|
> .moderator {
|
||||||
color: var(--error);
|
color: var(--success);
|
||||||
border-color: var(--error);
|
border-color: var(--success);
|
||||||
}
|
}
|
||||||
|
|
||||||
> .silenced {
|
> .silenced {
|
||||||
|
@ -604,9 +608,19 @@ definePageMetadata(() => ({
|
||||||
border-color: var(--warn);
|
border-color: var(--warn);
|
||||||
}
|
}
|
||||||
|
|
||||||
> .moderator {
|
> .limited {
|
||||||
color: var(--success);
|
color: var(--error);
|
||||||
border-color: var(--success);
|
border-color: var(--error);
|
||||||
|
}
|
||||||
|
|
||||||
|
> .suspended {
|
||||||
|
color: var(--error);
|
||||||
|
border-color: var(--error);
|
||||||
|
}
|
||||||
|
|
||||||
|
> .deleted {
|
||||||
|
color: var(--error);
|
||||||
|
border-color: var(--error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -240,6 +240,26 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
|
|
||||||
|
<MkFolder v-if="matchQuery([i18n.ts._role._options.canPurgeAccount, 'canPurgeAccount'])">
|
||||||
|
<template #label>{{ i18n.ts._role._options.canPurgeAccount }}</template>
|
||||||
|
<template #suffix>
|
||||||
|
<span v-if="role.policies.canPurgeAccount.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
|
||||||
|
<span v-else>{{ role.policies.canPurgeAccount.value ? i18n.ts.yes : i18n.ts.no }}</span>
|
||||||
|
<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.canPurgeAccount)"></i></span>
|
||||||
|
</template>
|
||||||
|
<div class="_gaps">
|
||||||
|
<MkSwitch v-model="role.policies.canPurgeAccount.useDefault" :readonly="readonly">
|
||||||
|
<template #label>{{ i18n.ts._role.useBaseValue }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
<MkSwitch v-model="role.policies.canPurgeAccount.value" :disabled="role.policies.canPurgeAccount.useDefault" :readonly="readonly">
|
||||||
|
<template #label>{{ i18n.ts.enable }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
<MkRange v-model="role.policies.canPurgeAccount.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
|
||||||
|
<template #label>{{ i18n.ts._role.priority }}</template>
|
||||||
|
</MkRange>
|
||||||
|
</div>
|
||||||
|
</MkFolder>
|
||||||
|
|
||||||
<MkFolder v-if="matchQuery([i18n.ts._role._options.canUpdateAvatar, 'canUpdateAvatar'])">
|
<MkFolder v-if="matchQuery([i18n.ts._role._options.canUpdateAvatar, 'canUpdateAvatar'])">
|
||||||
<template #label>{{ i18n.ts._role._options.canUpdateAvatar }}</template>
|
<template #label>{{ i18n.ts._role._options.canUpdateAvatar }}</template>
|
||||||
<template #suffix>
|
<template #suffix>
|
||||||
|
|
|
@ -80,6 +80,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</MkSwitch>
|
</MkSwitch>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
|
|
||||||
|
<MkFolder v-if="matchQuery([i18n.ts._role._options.canPurgeAccount, 'canPurgeAccount'])">
|
||||||
|
<template #label>{{ i18n.ts._role._options.canPurgeAccount }}</template>
|
||||||
|
<template #suffix>{{ policies.canPurgeAccount ? i18n.ts.yes : i18n.ts.no }}</template>
|
||||||
|
<MkSwitch v-model="policies.canPurgeAccount">
|
||||||
|
<template #label>{{ i18n.ts.enable }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
</MkFolder>
|
||||||
|
|
||||||
<MkFolder v-if="matchQuery([i18n.ts._role._options.canUpdateAvatar, 'canUpdateAvatar'])">
|
<MkFolder v-if="matchQuery([i18n.ts._role._options.canUpdateAvatar, 'canUpdateAvatar'])">
|
||||||
<template #label>{{ i18n.ts._role._options.canUpdateAvatar }}</template>
|
<template #label>{{ i18n.ts._role._options.canUpdateAvatar }}</template>
|
||||||
<template #suffix>{{ policies.canUpdateAvatar ? i18n.ts.yes : i18n.ts.no }}</template>
|
<template #suffix>{{ policies.canUpdateAvatar ? i18n.ts.yes : i18n.ts.no }}</template>
|
||||||
|
|
|
@ -12,10 +12,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<span :class="$style.userMInfoMetaName"><MkUserName :class="$style.userMInfoMetaName" :user="user"/></span>
|
<span :class="$style.userMInfoMetaName"><MkUserName :class="$style.userMInfoMetaName" :user="user"/></span>
|
||||||
<span :class="$style.userMInfoMetaSub"><span class="acct _monospace">@{{ acct(user) }}</span></span>
|
<span :class="$style.userMInfoMetaSub"><span class="acct _monospace">@{{ acct(user) }}</span></span>
|
||||||
<span :class="$style.userMInfoMetaState">
|
<span :class="$style.userMInfoMetaState">
|
||||||
<span v-if="suspended" :class="$style.suspended">Suspended</span>
|
|
||||||
<span v-if="limited" :class="$style.limited">Limited</span>
|
|
||||||
<span v-if="silenced" :class="$style.silenced">Silenced</span>
|
|
||||||
<span v-if="moderator" :class="$style.moderator">Moderator</span>
|
<span v-if="moderator" :class="$style.moderator">Moderator</span>
|
||||||
|
<span v-if="silenced" :class="$style.silenced">Silenced</span>
|
||||||
|
<span v-if="limited" :class="$style.limited">Limited</span>
|
||||||
|
<span v-if="suspended" :class="$style.suspended">Suspended</span>
|
||||||
|
<span v-if="deleted" :class="$style.deleted">Deleted</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -56,6 +57,7 @@ const moderator = computed(() => props.user.isModerator ?? false);
|
||||||
const silenced = computed(() => props.user.isSilenced ?? false);
|
const silenced = computed(() => props.user.isSilenced ?? false);
|
||||||
const limited = computed(() => props.user.isLimited ?? false);
|
const limited = computed(() => props.user.isLimited ?? false);
|
||||||
const suspended = computed(() => props.user.isSuspended ?? false);
|
const suspended = computed(() => props.user.isSuspended ?? false);
|
||||||
|
const deleted = computed(() => props.user.isDeleted ?? false);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
@ -104,10 +106,11 @@ const suspended = computed(() => props.user.isSuspended ?? false);
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .suspended,
|
> .moderator,
|
||||||
> .limited,
|
|
||||||
> .silenced,
|
> .silenced,
|
||||||
> .moderator {
|
> .limited,
|
||||||
|
> .suspended,
|
||||||
|
> .deleted {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
border: solid 1px;
|
border: solid 1px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
|
@ -115,14 +118,9 @@ const suspended = computed(() => props.user.isSuspended ?? false);
|
||||||
font-size: 85%;
|
font-size: 85%;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .suspended {
|
> .moderator {
|
||||||
color: var(--error);
|
color: var(--success);
|
||||||
border-color: var(--error);
|
border-color: var(--success);
|
||||||
}
|
|
||||||
|
|
||||||
> .limited {
|
|
||||||
color: var(--error);
|
|
||||||
border-color: var(--error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
> .silenced {
|
> .silenced {
|
||||||
|
@ -130,9 +128,19 @@ const suspended = computed(() => props.user.isSuspended ?? false);
|
||||||
border-color: var(--warn);
|
border-color: var(--warn);
|
||||||
}
|
}
|
||||||
|
|
||||||
> .moderator {
|
> .limited {
|
||||||
color: var(--success);
|
color: var(--error);
|
||||||
border-color: var(--success);
|
border-color: var(--error);
|
||||||
|
}
|
||||||
|
|
||||||
|
> .suspended {
|
||||||
|
color: var(--error);
|
||||||
|
border-color: var(--error);
|
||||||
|
}
|
||||||
|
|
||||||
|
> .deleted {
|
||||||
|
color: var(--error);
|
||||||
|
border-color: var(--error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -122,10 +122,7 @@ type AdminAvatarDecorationsListResponse = operations['admin/avatar-decorations/l
|
||||||
type AdminAvatarDecorationsUpdateRequest = operations['admin/avatar-decorations/update']['requestBody']['content']['application/json'];
|
type AdminAvatarDecorationsUpdateRequest = operations['admin/avatar-decorations/update']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type AdminDeleteAccountRequest = operations['admin/delete-account']['requestBody']['content']['application/json'];
|
type AdminDriveDeleteAllFilesOfAUserRequest = operations['admin/drive/delete-all-files-of-a-user']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
// @public (undocumented)
|
|
||||||
type AdminDeleteAllFilesOfAUserRequest = operations['admin/delete-all-files-of-a-user']['requestBody']['content']['application/json'];
|
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type AdminDriveFilesRequest = operations['admin/drive/files']['requestBody']['content']['application/json'];
|
type AdminDriveFilesRequest = operations['admin/drive/files']['requestBody']['content']['application/json'];
|
||||||
|
@ -1215,9 +1212,9 @@ declare namespace entities {
|
||||||
AdminAvatarDecorationsListRequest,
|
AdminAvatarDecorationsListRequest,
|
||||||
AdminAvatarDecorationsListResponse,
|
AdminAvatarDecorationsListResponse,
|
||||||
AdminAvatarDecorationsUpdateRequest,
|
AdminAvatarDecorationsUpdateRequest,
|
||||||
AdminDeleteAllFilesOfAUserRequest,
|
|
||||||
AdminUnsetUserAvatarRequest,
|
AdminUnsetUserAvatarRequest,
|
||||||
AdminUnsetUserBannerRequest,
|
AdminUnsetUserBannerRequest,
|
||||||
|
AdminDriveDeleteAllFilesOfAUserRequest,
|
||||||
AdminDriveFilesRequest,
|
AdminDriveFilesRequest,
|
||||||
AdminDriveFilesResponse,
|
AdminDriveFilesResponse,
|
||||||
AdminDriveShowFileRequest,
|
AdminDriveShowFileRequest,
|
||||||
|
@ -1280,7 +1277,6 @@ declare namespace entities {
|
||||||
AdminSuspendUserRequest,
|
AdminSuspendUserRequest,
|
||||||
AdminUnsuspendUserRequest,
|
AdminUnsuspendUserRequest,
|
||||||
AdminUpdateMetaRequest,
|
AdminUpdateMetaRequest,
|
||||||
AdminDeleteAccountRequest,
|
|
||||||
AdminUpdateUserNoteRequest,
|
AdminUpdateUserNoteRequest,
|
||||||
AdminRolesCreateRequest,
|
AdminRolesCreateRequest,
|
||||||
AdminRolesCreateResponse,
|
AdminRolesCreateResponse,
|
||||||
|
@ -2733,7 +2729,7 @@ type PagesUpdateRequest = operations['pages/update']['requestBody']['content']['
|
||||||
function parse(acct: string): Acct;
|
function parse(acct: string): Acct;
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
export const permissions: readonly ["read:account", "write:account", "read:blocks", "write:blocks", "read:drive", "write:drive", "read:favorites", "write:favorites", "read:following", "write:following", "read:messaging", "write:messaging", "read:mutes", "write:mutes", "write:notes", "read:notifications", "write:notifications", "read:reactions", "write:reactions", "write:votes", "read:pages", "write:pages", "write:page-likes", "read:page-likes", "read:user-groups", "write:user-groups", "read:channels", "write:channels", "read:gallery", "write:gallery", "read:gallery-likes", "write:gallery-likes", "read:flash", "write:flash", "read:flash-likes", "write:flash-likes", "read:admin:abuse-user-reports", "read:admin:abuse-report-resolvers", "write:admin:abuse-report-resolvers", "write:admin:delete-account", "write:admin:delete-all-files-of-a-user", "read:admin:index-stats", "read:admin:table-stats", "read:admin:user-ips", "read:admin:meta", "write:admin:reset-password", "write:admin:resolve-abuse-user-report", "write:admin:send-email", "read:admin:server-info", "read:admin:show-moderation-log", "read:admin:show-user", "read:admin:show-users", "write:admin:suspend-user", "write:admin:unset-user-avatar", "write:admin:unset-user-banner", "write:admin:unsuspend-user", "write:admin:meta", "write:admin:user-note", "write:admin:roles", "read:admin:roles", "write:admin:relays", "read:admin:relays", "write:admin:invite-codes", "read:admin:invite-codes", "write:admin:announcements", "read:admin:announcements", "write:admin:avatar-decorations", "read:admin:avatar-decorations", "write:admin:federation", "write:admin:indie-auth", "read:admin:indie-auth", "write:admin:account", "read:admin:account", "write:admin:emoji", "read:admin:emoji", "write:admin:queue", "read:admin:queue", "write:admin:promo", "write:admin:drive", "read:admin:drive", "write:admin:sso", "read:admin:sso", "write:admin:ad", "read:admin:ad", "write:invite-codes", "read:invite-codes", "write:clip-favorite", "read:clip-favorite", "read:federation", "write:report-abuse"];
|
export const permissions: readonly ["read:account", "write:account", "read:blocks", "write:blocks", "read:drive", "write:drive", "read:favorites", "write:favorites", "read:following", "write:following", "read:messaging", "write:messaging", "read:mutes", "write:mutes", "write:notes", "read:notifications", "write:notifications", "read:reactions", "write:reactions", "write:votes", "read:pages", "write:pages", "write:page-likes", "read:page-likes", "read:user-groups", "write:user-groups", "read:channels", "write:channels", "read:gallery", "write:gallery", "read:gallery-likes", "write:gallery-likes", "read:flash", "write:flash", "read:flash-likes", "write:flash-likes", "read:admin:abuse-user-reports", "read:admin:abuse-report-resolvers", "write:admin:abuse-report-resolvers", "read:admin:index-stats", "read:admin:table-stats", "read:admin:user-ips", "read:admin:meta", "write:admin:reset-password", "write:admin:resolve-abuse-user-report", "write:admin:send-email", "read:admin:server-info", "read:admin:show-moderation-log", "read:admin:show-user", "read:admin:show-users", "write:admin:suspend-user", "write:admin:unset-user-avatar", "write:admin:unset-user-banner", "write:admin:unsuspend-user", "write:admin:meta", "write:admin:user-note", "write:admin:roles", "read:admin:roles", "write:admin:relays", "read:admin:relays", "write:admin:invite-codes", "read:admin:invite-codes", "write:admin:announcements", "read:admin:announcements", "write:admin:avatar-decorations", "read:admin:avatar-decorations", "write:admin:federation", "write:admin:indie-auth", "read:admin:indie-auth", "write:admin:account", "read:admin:account", "write:admin:emoji", "read:admin:emoji", "write:admin:queue", "read:admin:queue", "write:admin:promo", "write:admin:drive", "read:admin:drive", "write:admin:sso", "read:admin:sso", "write:admin:ad", "read:admin:ad", "write:invite-codes", "read:invite-codes", "write:clip-favorite", "read:clip-favorite", "read:federation", "write:report-abuse"];
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type PingResponse = operations['ping']['responses']['200']['content']['application/json'];
|
type PingResponse = operations['ping']['responses']['200']['content']['application/json'];
|
||||||
|
|
|
@ -234,17 +234,6 @@ declare module '../api.js' {
|
||||||
credential?: string | null,
|
credential?: string | null,
|
||||||
): Promise<SwitchCaseResponseType<E, P>>;
|
): Promise<SwitchCaseResponseType<E, P>>;
|
||||||
|
|
||||||
/**
|
|
||||||
* No description provided.
|
|
||||||
*
|
|
||||||
* **Credential required**: *Yes* / **Permission**: *write:admin:delete-all-files-of-a-user*
|
|
||||||
*/
|
|
||||||
request<E extends 'admin/delete-all-files-of-a-user', P extends Endpoints[E]['req']>(
|
|
||||||
endpoint: E,
|
|
||||||
params: P,
|
|
||||||
credential?: string | null,
|
|
||||||
): Promise<SwitchCaseResponseType<E, P>>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* No description provided.
|
* No description provided.
|
||||||
*
|
*
|
||||||
|
@ -289,6 +278,17 @@ declare module '../api.js' {
|
||||||
credential?: string | null,
|
credential?: string | null,
|
||||||
): Promise<SwitchCaseResponseType<E, P>>;
|
): Promise<SwitchCaseResponseType<E, P>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No description provided.
|
||||||
|
*
|
||||||
|
* **Credential required**: *Yes* / **Permission**: *write:admin:drive*
|
||||||
|
*/
|
||||||
|
request<E extends 'admin/drive/delete-all-files-of-a-user', P extends Endpoints[E]['req']>(
|
||||||
|
endpoint: E,
|
||||||
|
params: P,
|
||||||
|
credential?: string | null,
|
||||||
|
): Promise<SwitchCaseResponseType<E, P>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* No description provided.
|
* No description provided.
|
||||||
*
|
*
|
||||||
|
@ -807,17 +807,6 @@ declare module '../api.js' {
|
||||||
credential?: string | null,
|
credential?: string | null,
|
||||||
): Promise<SwitchCaseResponseType<E, P>>;
|
): Promise<SwitchCaseResponseType<E, P>>;
|
||||||
|
|
||||||
/**
|
|
||||||
* No description provided.
|
|
||||||
*
|
|
||||||
* **Credential required**: *Yes* / **Permission**: *write:admin:delete-account*
|
|
||||||
*/
|
|
||||||
request<E extends 'admin/delete-account', P extends Endpoints[E]['req']>(
|
|
||||||
endpoint: E,
|
|
||||||
params: P,
|
|
||||||
credential?: string | null,
|
|
||||||
): Promise<SwitchCaseResponseType<E, P>>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* No description provided.
|
* No description provided.
|
||||||
*
|
*
|
||||||
|
|
|
@ -32,9 +32,9 @@ import type {
|
||||||
AdminAvatarDecorationsListRequest,
|
AdminAvatarDecorationsListRequest,
|
||||||
AdminAvatarDecorationsListResponse,
|
AdminAvatarDecorationsListResponse,
|
||||||
AdminAvatarDecorationsUpdateRequest,
|
AdminAvatarDecorationsUpdateRequest,
|
||||||
AdminDeleteAllFilesOfAUserRequest,
|
|
||||||
AdminUnsetUserAvatarRequest,
|
AdminUnsetUserAvatarRequest,
|
||||||
AdminUnsetUserBannerRequest,
|
AdminUnsetUserBannerRequest,
|
||||||
|
AdminDriveDeleteAllFilesOfAUserRequest,
|
||||||
AdminDriveFilesRequest,
|
AdminDriveFilesRequest,
|
||||||
AdminDriveFilesResponse,
|
AdminDriveFilesResponse,
|
||||||
AdminDriveShowFileRequest,
|
AdminDriveShowFileRequest,
|
||||||
|
@ -97,7 +97,6 @@ import type {
|
||||||
AdminSuspendUserRequest,
|
AdminSuspendUserRequest,
|
||||||
AdminUnsuspendUserRequest,
|
AdminUnsuspendUserRequest,
|
||||||
AdminUpdateMetaRequest,
|
AdminUpdateMetaRequest,
|
||||||
AdminDeleteAccountRequest,
|
|
||||||
AdminUpdateUserNoteRequest,
|
AdminUpdateUserNoteRequest,
|
||||||
AdminRolesCreateRequest,
|
AdminRolesCreateRequest,
|
||||||
AdminRolesCreateResponse,
|
AdminRolesCreateResponse,
|
||||||
|
@ -596,11 +595,11 @@ export type Endpoints = {
|
||||||
'admin/avatar-decorations/delete': { req: AdminAvatarDecorationsDeleteRequest; res: EmptyResponse };
|
'admin/avatar-decorations/delete': { req: AdminAvatarDecorationsDeleteRequest; res: EmptyResponse };
|
||||||
'admin/avatar-decorations/list': { req: AdminAvatarDecorationsListRequest; res: AdminAvatarDecorationsListResponse };
|
'admin/avatar-decorations/list': { req: AdminAvatarDecorationsListRequest; res: AdminAvatarDecorationsListResponse };
|
||||||
'admin/avatar-decorations/update': { req: AdminAvatarDecorationsUpdateRequest; res: EmptyResponse };
|
'admin/avatar-decorations/update': { req: AdminAvatarDecorationsUpdateRequest; res: EmptyResponse };
|
||||||
'admin/delete-all-files-of-a-user': { req: AdminDeleteAllFilesOfAUserRequest; res: EmptyResponse };
|
|
||||||
'admin/unset-user-avatar': { req: AdminUnsetUserAvatarRequest; res: EmptyResponse };
|
'admin/unset-user-avatar': { req: AdminUnsetUserAvatarRequest; res: EmptyResponse };
|
||||||
'admin/unset-user-banner': { req: AdminUnsetUserBannerRequest; res: EmptyResponse };
|
'admin/unset-user-banner': { req: AdminUnsetUserBannerRequest; res: EmptyResponse };
|
||||||
'admin/drive/clean-remote-files': { req: EmptyRequest; res: EmptyResponse };
|
'admin/drive/clean-remote-files': { req: EmptyRequest; res: EmptyResponse };
|
||||||
'admin/drive/cleanup': { req: EmptyRequest; res: EmptyResponse };
|
'admin/drive/cleanup': { req: EmptyRequest; res: EmptyResponse };
|
||||||
|
'admin/drive/delete-all-files-of-a-user': { req: AdminDriveDeleteAllFilesOfAUserRequest; res: EmptyResponse };
|
||||||
'admin/drive/files': { req: AdminDriveFilesRequest; res: AdminDriveFilesResponse };
|
'admin/drive/files': { req: AdminDriveFilesRequest; res: AdminDriveFilesResponse };
|
||||||
'admin/drive/show-file': { req: AdminDriveShowFileRequest; res: AdminDriveShowFileResponse };
|
'admin/drive/show-file': { req: AdminDriveShowFileRequest; res: AdminDriveShowFileResponse };
|
||||||
'admin/emoji/add-aliases-bulk': { req: AdminEmojiAddAliasesBulkRequest; res: EmptyResponse };
|
'admin/emoji/add-aliases-bulk': { req: AdminEmojiAddAliasesBulkRequest; res: EmptyResponse };
|
||||||
|
@ -648,7 +647,6 @@ export type Endpoints = {
|
||||||
'admin/suspend-user': { req: AdminSuspendUserRequest; res: EmptyResponse };
|
'admin/suspend-user': { req: AdminSuspendUserRequest; res: EmptyResponse };
|
||||||
'admin/unsuspend-user': { req: AdminUnsuspendUserRequest; res: EmptyResponse };
|
'admin/unsuspend-user': { req: AdminUnsuspendUserRequest; res: EmptyResponse };
|
||||||
'admin/update-meta': { req: AdminUpdateMetaRequest; res: EmptyResponse };
|
'admin/update-meta': { req: AdminUpdateMetaRequest; res: EmptyResponse };
|
||||||
'admin/delete-account': { req: AdminDeleteAccountRequest; res: EmptyResponse };
|
|
||||||
'admin/update-user-note': { req: AdminUpdateUserNoteRequest; res: EmptyResponse };
|
'admin/update-user-note': { req: AdminUpdateUserNoteRequest; res: EmptyResponse };
|
||||||
'admin/roles/create': { req: AdminRolesCreateRequest; res: AdminRolesCreateResponse };
|
'admin/roles/create': { req: AdminRolesCreateRequest; res: AdminRolesCreateResponse };
|
||||||
'admin/roles/delete': { req: AdminRolesDeleteRequest; res: EmptyResponse };
|
'admin/roles/delete': { req: AdminRolesDeleteRequest; res: EmptyResponse };
|
||||||
|
|
|
@ -34,9 +34,9 @@ export type AdminAvatarDecorationsDeleteRequest = operations['admin/avatar-decor
|
||||||
export type AdminAvatarDecorationsListRequest = operations['admin/avatar-decorations/list']['requestBody']['content']['application/json'];
|
export type AdminAvatarDecorationsListRequest = operations['admin/avatar-decorations/list']['requestBody']['content']['application/json'];
|
||||||
export type AdminAvatarDecorationsListResponse = operations['admin/avatar-decorations/list']['responses']['200']['content']['application/json'];
|
export type AdminAvatarDecorationsListResponse = operations['admin/avatar-decorations/list']['responses']['200']['content']['application/json'];
|
||||||
export type AdminAvatarDecorationsUpdateRequest = operations['admin/avatar-decorations/update']['requestBody']['content']['application/json'];
|
export type AdminAvatarDecorationsUpdateRequest = operations['admin/avatar-decorations/update']['requestBody']['content']['application/json'];
|
||||||
export type AdminDeleteAllFilesOfAUserRequest = operations['admin/delete-all-files-of-a-user']['requestBody']['content']['application/json'];
|
|
||||||
export type AdminUnsetUserAvatarRequest = operations['admin/unset-user-avatar']['requestBody']['content']['application/json'];
|
export type AdminUnsetUserAvatarRequest = operations['admin/unset-user-avatar']['requestBody']['content']['application/json'];
|
||||||
export type AdminUnsetUserBannerRequest = operations['admin/unset-user-banner']['requestBody']['content']['application/json'];
|
export type AdminUnsetUserBannerRequest = operations['admin/unset-user-banner']['requestBody']['content']['application/json'];
|
||||||
|
export type AdminDriveDeleteAllFilesOfAUserRequest = operations['admin/drive/delete-all-files-of-a-user']['requestBody']['content']['application/json'];
|
||||||
export type AdminDriveFilesRequest = operations['admin/drive/files']['requestBody']['content']['application/json'];
|
export type AdminDriveFilesRequest = operations['admin/drive/files']['requestBody']['content']['application/json'];
|
||||||
export type AdminDriveFilesResponse = operations['admin/drive/files']['responses']['200']['content']['application/json'];
|
export type AdminDriveFilesResponse = operations['admin/drive/files']['responses']['200']['content']['application/json'];
|
||||||
export type AdminDriveShowFileRequest = operations['admin/drive/show-file']['requestBody']['content']['application/json'];
|
export type AdminDriveShowFileRequest = operations['admin/drive/show-file']['requestBody']['content']['application/json'];
|
||||||
|
@ -99,7 +99,6 @@ export type AdminShowUsersResponse = operations['admin/show-users']['responses']
|
||||||
export type AdminSuspendUserRequest = operations['admin/suspend-user']['requestBody']['content']['application/json'];
|
export type AdminSuspendUserRequest = operations['admin/suspend-user']['requestBody']['content']['application/json'];
|
||||||
export type AdminUnsuspendUserRequest = operations['admin/unsuspend-user']['requestBody']['content']['application/json'];
|
export type AdminUnsuspendUserRequest = operations['admin/unsuspend-user']['requestBody']['content']['application/json'];
|
||||||
export type AdminUpdateMetaRequest = operations['admin/update-meta']['requestBody']['content']['application/json'];
|
export type AdminUpdateMetaRequest = operations['admin/update-meta']['requestBody']['content']['application/json'];
|
||||||
export type AdminDeleteAccountRequest = operations['admin/delete-account']['requestBody']['content']['application/json'];
|
|
||||||
export type AdminUpdateUserNoteRequest = operations['admin/update-user-note']['requestBody']['content']['application/json'];
|
export type AdminUpdateUserNoteRequest = operations['admin/update-user-note']['requestBody']['content']['application/json'];
|
||||||
export type AdminRolesCreateRequest = operations['admin/roles/create']['requestBody']['content']['application/json'];
|
export type AdminRolesCreateRequest = operations['admin/roles/create']['requestBody']['content']['application/json'];
|
||||||
export type AdminRolesCreateResponse = operations['admin/roles/create']['responses']['200']['content']['application/json'];
|
export type AdminRolesCreateResponse = operations['admin/roles/create']['responses']['200']['content']['application/json'];
|
||||||
|
|
|
@ -201,15 +201,6 @@ export type paths = {
|
||||||
*/
|
*/
|
||||||
post: operations['admin/avatar-decorations/update'];
|
post: operations['admin/avatar-decorations/update'];
|
||||||
};
|
};
|
||||||
'/admin/delete-all-files-of-a-user': {
|
|
||||||
/**
|
|
||||||
* admin/delete-all-files-of-a-user
|
|
||||||
* @description No description provided.
|
|
||||||
*
|
|
||||||
* **Credential required**: *Yes* / **Permission**: *write:admin:delete-all-files-of-a-user*
|
|
||||||
*/
|
|
||||||
post: operations['admin/delete-all-files-of-a-user'];
|
|
||||||
};
|
|
||||||
'/admin/unset-user-avatar': {
|
'/admin/unset-user-avatar': {
|
||||||
/**
|
/**
|
||||||
* admin/unset-user-avatar
|
* admin/unset-user-avatar
|
||||||
|
@ -246,6 +237,15 @@ export type paths = {
|
||||||
*/
|
*/
|
||||||
post: operations['admin/drive/cleanup'];
|
post: operations['admin/drive/cleanup'];
|
||||||
};
|
};
|
||||||
|
'/admin/drive/delete-all-files-of-a-user': {
|
||||||
|
/**
|
||||||
|
* admin/drive/delete-all-files-of-a-user
|
||||||
|
* @description No description provided.
|
||||||
|
*
|
||||||
|
* **Credential required**: *Yes* / **Permission**: *write:admin:drive*
|
||||||
|
*/
|
||||||
|
post: operations['admin/drive/delete-all-files-of-a-user'];
|
||||||
|
};
|
||||||
'/admin/drive/files': {
|
'/admin/drive/files': {
|
||||||
/**
|
/**
|
||||||
* admin/drive/files
|
* admin/drive/files
|
||||||
|
@ -670,15 +670,6 @@ export type paths = {
|
||||||
*/
|
*/
|
||||||
post: operations['admin/update-meta'];
|
post: operations['admin/update-meta'];
|
||||||
};
|
};
|
||||||
'/admin/delete-account': {
|
|
||||||
/**
|
|
||||||
* admin/delete-account
|
|
||||||
* @description No description provided.
|
|
||||||
*
|
|
||||||
* **Credential required**: *Yes* / **Permission**: *write:admin:delete-account*
|
|
||||||
*/
|
|
||||||
post: operations['admin/delete-account'];
|
|
||||||
};
|
|
||||||
'/admin/update-user-note': {
|
'/admin/update-user-note': {
|
||||||
/**
|
/**
|
||||||
* admin/update-user-note
|
* admin/update-user-note
|
||||||
|
@ -4844,6 +4835,7 @@ export type components = {
|
||||||
canCreateContent: boolean;
|
canCreateContent: boolean;
|
||||||
canUpdateContent: boolean;
|
canUpdateContent: boolean;
|
||||||
canDeleteContent: boolean;
|
canDeleteContent: boolean;
|
||||||
|
canPurgeAccount: boolean;
|
||||||
canUpdateAvatar: boolean;
|
canUpdateAvatar: boolean;
|
||||||
canUpdateBanner: boolean;
|
canUpdateBanner: boolean;
|
||||||
mentionLimit: number;
|
mentionLimit: number;
|
||||||
|
@ -6512,58 +6504,6 @@ export type operations = {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
/**
|
|
||||||
* admin/delete-all-files-of-a-user
|
|
||||||
* @description No description provided.
|
|
||||||
*
|
|
||||||
* **Credential required**: *Yes* / **Permission**: *write:admin:delete-all-files-of-a-user*
|
|
||||||
*/
|
|
||||||
'admin/delete-all-files-of-a-user': {
|
|
||||||
requestBody: {
|
|
||||||
content: {
|
|
||||||
'application/json': {
|
|
||||||
/** Format: misskey:id */
|
|
||||||
userId: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
responses: {
|
|
||||||
/** @description OK (without any results) */
|
|
||||||
204: {
|
|
||||||
content: never;
|
|
||||||
};
|
|
||||||
/** @description Client error */
|
|
||||||
400: {
|
|
||||||
content: {
|
|
||||||
'application/json': components['schemas']['Error'];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
/** @description Authentication error */
|
|
||||||
401: {
|
|
||||||
content: {
|
|
||||||
'application/json': components['schemas']['Error'];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
/** @description Forbidden error */
|
|
||||||
403: {
|
|
||||||
content: {
|
|
||||||
'application/json': components['schemas']['Error'];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
/** @description I'm Ai */
|
|
||||||
418: {
|
|
||||||
content: {
|
|
||||||
'application/json': components['schemas']['Error'];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
/** @description Internal server error */
|
|
||||||
500: {
|
|
||||||
content: {
|
|
||||||
'application/json': components['schemas']['Error'];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
/**
|
/**
|
||||||
* admin/unset-user-avatar
|
* admin/unset-user-avatar
|
||||||
* @description No description provided.
|
* @description No description provided.
|
||||||
|
@ -6756,6 +6696,58 @@ export type operations = {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* admin/drive/delete-all-files-of-a-user
|
||||||
|
* @description No description provided.
|
||||||
|
*
|
||||||
|
* **Credential required**: *Yes* / **Permission**: *write:admin:drive*
|
||||||
|
*/
|
||||||
|
'admin/drive/delete-all-files-of-a-user': {
|
||||||
|
requestBody: {
|
||||||
|
content: {
|
||||||
|
'application/json': {
|
||||||
|
/** Format: misskey:id */
|
||||||
|
userId: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description OK (without any results) */
|
||||||
|
204: {
|
||||||
|
content: never;
|
||||||
|
};
|
||||||
|
/** @description Client error */
|
||||||
|
400: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Authentication error */
|
||||||
|
401: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Forbidden error */
|
||||||
|
403: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description I'm Ai */
|
||||||
|
418: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Internal server error */
|
||||||
|
500: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* admin/drive/files
|
* admin/drive/files
|
||||||
* @description No description provided.
|
* @description No description provided.
|
||||||
|
@ -9321,6 +9313,8 @@ export type operations = {
|
||||||
};
|
};
|
||||||
isModerator: boolean;
|
isModerator: boolean;
|
||||||
isSilenced: boolean;
|
isSilenced: boolean;
|
||||||
|
isLimited: boolean;
|
||||||
|
isDeleted: boolean;
|
||||||
isSuspended: boolean;
|
isSuspended: boolean;
|
||||||
isHibernated: boolean;
|
isHibernated: boolean;
|
||||||
lastActiveDate: string | null;
|
lastActiveDate: string | null;
|
||||||
|
@ -9703,58 +9697,6 @@ export type operations = {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
/**
|
|
||||||
* admin/delete-account
|
|
||||||
* @description No description provided.
|
|
||||||
*
|
|
||||||
* **Credential required**: *Yes* / **Permission**: *write:admin:delete-account*
|
|
||||||
*/
|
|
||||||
'admin/delete-account': {
|
|
||||||
requestBody: {
|
|
||||||
content: {
|
|
||||||
'application/json': {
|
|
||||||
/** Format: misskey:id */
|
|
||||||
userId: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
responses: {
|
|
||||||
/** @description OK (without any results) */
|
|
||||||
204: {
|
|
||||||
content: never;
|
|
||||||
};
|
|
||||||
/** @description Client error */
|
|
||||||
400: {
|
|
||||||
content: {
|
|
||||||
'application/json': components['schemas']['Error'];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
/** @description Authentication error */
|
|
||||||
401: {
|
|
||||||
content: {
|
|
||||||
'application/json': components['schemas']['Error'];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
/** @description Forbidden error */
|
|
||||||
403: {
|
|
||||||
content: {
|
|
||||||
'application/json': components['schemas']['Error'];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
/** @description I'm Ai */
|
|
||||||
418: {
|
|
||||||
content: {
|
|
||||||
'application/json': components['schemas']['Error'];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
/** @description Internal server error */
|
|
||||||
500: {
|
|
||||||
content: {
|
|
||||||
'application/json': components['schemas']['Error'];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
/**
|
/**
|
||||||
* admin/update-user-note
|
* admin/update-user-note
|
||||||
* @description No description provided.
|
* @description No description provided.
|
||||||
|
|
|
@ -48,8 +48,6 @@ export const permissions = [
|
||||||
'read:admin:abuse-user-reports',
|
'read:admin:abuse-user-reports',
|
||||||
'read:admin:abuse-report-resolvers',
|
'read:admin:abuse-report-resolvers',
|
||||||
'write:admin:abuse-report-resolvers',
|
'write:admin:abuse-report-resolvers',
|
||||||
'write:admin:delete-account',
|
|
||||||
'write:admin:delete-all-files-of-a-user',
|
|
||||||
'read:admin:index-stats',
|
'read:admin:index-stats',
|
||||||
'read:admin:table-stats',
|
'read:admin:table-stats',
|
||||||
'read:admin:user-ips',
|
'read:admin:user-ips',
|
||||||
|
|
Loading…
Reference in New Issue