Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: 管理用APIの追加 #39

Merged
merged 3 commits into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 15 additions & 15 deletions docker-compose.local.yml
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
version: "3"

services:
web:
build: .
restart: unless-stopped
links:
- db
- redis
# - es
ports:
- "3000:3000"
networks:
- internal_network
- external_network
volumes:
- ./files:/misskey/files
- ./.config:/misskey/.config:ro
# web:
# build: .
# restart: unless-stopped
# links:
# - db
# - redis
## - es
# ports:
# - "3000:3000"
# networks:
# - internal_network
# - external_network
# volumes:
# - ./files:/misskey/files
# - ./.config:/misskey/.config:ro

redis:
restart: unless-stopped
Expand Down
4 changes: 4 additions & 0 deletions packages/backend/src/server/api/EndpointsModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import * as ep___admin_emoji_update from './endpoints/admin/emoji/update.js';
import * as ep___admin_federation_deleteAllFiles from './endpoints/admin/federation/delete-all-files.js';
import * as ep___admin_federation_refreshRemoteInstanceMetadata from './endpoints/admin/federation/refresh-remote-instance-metadata.js';
import * as ep___admin_federation_removeAllFollowing from './endpoints/admin/federation/remove-all-following.js';
import * as ep___admin_federation_removeAllFollowingByUserId from './endpoints/admin/federation/remove-all-following-by-user-id.js';
import * as ep___admin_federation_updateInstance from './endpoints/admin/federation/update-instance.js';
import * as ep___admin_getIndexStats from './endpoints/admin/get-index-stats.js';
import * as ep___admin_getTableStats from './endpoints/admin/get-table-stats.js';
Expand Down Expand Up @@ -384,6 +385,7 @@ const $admin_emoji_update: Provider = { provide: 'ep:admin/emoji/update', useCla
const $admin_federation_deleteAllFiles: Provider = { provide: 'ep:admin/federation/delete-all-files', useClass: ep___admin_federation_deleteAllFiles.default };
const $admin_federation_refreshRemoteInstanceMetadata: Provider = { provide: 'ep:admin/federation/refresh-remote-instance-metadata', useClass: ep___admin_federation_refreshRemoteInstanceMetadata.default };
const $admin_federation_removeAllFollowing: Provider = { provide: 'ep:admin/federation/remove-all-following', useClass: ep___admin_federation_removeAllFollowing.default };
const $admin_federation_removeAllFollowingByUserId: Provider = { provide: 'ep:admin/federation/remove-all-following-by-user-id', useClass: ep___admin_federation_removeAllFollowingByUserId.default };
const $admin_federation_updateInstance: Provider = { provide: 'ep:admin/federation/update-instance', useClass: ep___admin_federation_updateInstance.default };
const $admin_getIndexStats: Provider = { provide: 'ep:admin/get-index-stats', useClass: ep___admin_getIndexStats.default };
const $admin_getTableStats: Provider = { provide: 'ep:admin/get-table-stats', useClass: ep___admin_getTableStats.default };
Expand Down Expand Up @@ -739,6 +741,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$admin_federation_deleteAllFiles,
$admin_federation_refreshRemoteInstanceMetadata,
$admin_federation_removeAllFollowing,
$admin_federation_removeAllFollowingByUserId,
$admin_federation_updateInstance,
$admin_getIndexStats,
$admin_getTableStats,
Expand Down Expand Up @@ -1088,6 +1091,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$admin_federation_deleteAllFiles,
$admin_federation_refreshRemoteInstanceMetadata,
$admin_federation_removeAllFollowing,
$admin_federation_removeAllFollowingByUserId,
$admin_federation_updateInstance,
$admin_getIndexStats,
$admin_getTableStats,
Expand Down
2 changes: 2 additions & 0 deletions packages/backend/src/server/api/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import * as ep___admin_emoji_update from './endpoints/admin/emoji/update.js';
import * as ep___admin_federation_deleteAllFiles from './endpoints/admin/federation/delete-all-files.js';
import * as ep___admin_federation_refreshRemoteInstanceMetadata from './endpoints/admin/federation/refresh-remote-instance-metadata.js';
import * as ep___admin_federation_removeAllFollowing from './endpoints/admin/federation/remove-all-following.js';
import * as ep___admin_federation_removeAllFollowingByUserId from './endpoints/admin/federation/remove-all-following-by-user-id.js';
import * as ep___admin_federation_updateInstance from './endpoints/admin/federation/update-instance.js';
import * as ep___admin_getIndexStats from './endpoints/admin/get-index-stats.js';
import * as ep___admin_getTableStats from './endpoints/admin/get-table-stats.js';
Expand Down Expand Up @@ -382,6 +383,7 @@ const eps = [
['admin/federation/delete-all-files', ep___admin_federation_deleteAllFiles],
['admin/federation/refresh-remote-instance-metadata', ep___admin_federation_refreshRemoteInstanceMetadata],
['admin/federation/remove-all-following', ep___admin_federation_removeAllFollowing],
['admin/federation/remove-all-following-by-user-id', ep___admin_federation_removeAllFollowingByUserId],
['admin/federation/update-instance', ep___admin_federation_updateInstance],
['admin/get-index-stats', ep___admin_getIndexStats],
['admin/get-table-stats', ep___admin_getTableStats],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { FollowingsRepository, User, UsersRepository } from '@/models/index.js';
import { UserFollowingService } from '@/core/UserFollowingService.js';
import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';

export const meta = {
tags: ['admin'],

requireCredential: true,
requireModerator: true,
} as const;

export const paramDef = {
type: 'object',
properties: {
userId: { type: 'string' },
},
required: ['userId'],
} as const;

// eslint-disable-next-line import/no-default-export
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> {
constructor(
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,

@Inject(DI.followingsRepository)
private followingsRepository: FollowingsRepository,

private userFollowingService: UserFollowingService,
) {
super(meta, paramDef, async (ps, me) => {
const follower = await this.usersRepository.findOne({ where: { id: ps.userId } });

if (!follower) {
throw new Error(`User not found: ${ps.userId}`);
}

await this.unFollowAll(follower);
});
}

@bindThis
private async unFollowAll(follower: User) {
const followings = await this.followingsRepository.findBy({
followerId: follower.id,
});

for (const following of followings) {
const followee = await this.usersRepository.findOneBy({
id: following.followeeId,
});

if (followee == null) {
throw `Cant find followee ${following.followeeId}`;
}

await this.userFollowingService.unfollow(follower, followee, true);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,

@Inject(DI.notesRepository)
@Inject(DI.followingsRepository)
private followingsRepository: FollowingsRepository,

private userFollowingService: UserFollowingService,
Expand Down
25 changes: 25 additions & 0 deletions packages/frontend/src/pages/user-info.vue
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@
<MkButton v-if="user.host == null && iAmModerator" inline style="margin-right: 8px" @click="resetPassword">
<i class="ti ti-key"></i> {{ i18n.ts.resetPassword }}
</MkButton>
<MkButton v-if="$i.isAdmin" inline danger @click="unfollowAll">{{ i18n.ts.unfollowAll }}</MkButton>
<MkButton v-if="$i.isAdmin" inline danger @click="deleteAccount">{{ i18n.ts.deleteAccount }}</MkButton>
</div>

Expand Down Expand Up @@ -401,6 +402,30 @@ async function deleteAccount() {
}
}

async function unfollowAll() {
const confirm = await os.confirm({
type: 'warning',
text: i18n.ts.unfollowAllConfirm,
});
if (confirm.canceled) return;

const typed = await os.inputText({
text: i18n.t('typeToConfirm', { x: user?.username }),
});
if (typed.canceled) return;

if (typed.result === user?.username) {
await os.apiWithDialog('admin/federation/remove-all-following-by-user-id', {
userId: user.id,
});
} else {
os.alert({
type: 'error',
text: 'input not match',
});
}
}

async function assignRole() {
const roles = await os.api('admin/roles/list');

Expand Down
123 changes: 60 additions & 63 deletions packages/frontend/src/scripts/get-user-menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,59 @@ export function getUserMenu(user: misskey.entities.UserDetailed, router: Router
});
}

async function assignRole() {
const roles = await os.api('admin/roles/list');

const { canceled, result: roleId } = await os.select({
title: i18n.ts._role.chooseRoleToAssign,
items: roles.map((r) => ({ text: r.name, value: r.id })),
});
if (canceled) return;

const { canceled: canceled2, result: period } = await os.select({
title: i18n.ts.period,
items: [
{
value: 'indefinitely',
text: i18n.ts.indefinitely,
},
{
value: 'oneHour',
text: i18n.ts.oneHour,
},
{
value: 'oneDay',
text: i18n.ts.oneDay,
},
{
value: 'oneWeek',
text: i18n.ts.oneWeek,
},
{
value: 'oneMonth',
text: i18n.ts.oneMonth,
},
],
default: 'indefinitely',
});
if (canceled2) return;

const expiresAt =
period === 'indefinitely'
? null
: period === 'oneHour'
? Date.now() + 1000 * 60 * 60
: period === 'oneDay'
? Date.now() + 1000 * 60 * 60 * 24
: period === 'oneWeek'
? Date.now() + 1000 * 60 * 60 * 24 * 7
: period === 'oneMonth'
? Date.now() + 1000 * 60 * 60 * 24 * 30
: null;

await os.apiWithDialog('admin/roles/assign', { roleId, userId: user.id, expiresAt });
}

async function toggleMute() {
if (user.isMuted) {
os.apiWithDialog('mute/delete', {
Expand Down Expand Up @@ -216,6 +269,13 @@ export function getUserMenu(user: misskey.entities.UserDetailed, router: Router
action: inviteGroup,
}
: undefined,
iAmModerator
? {
icon: 'ti ti-users',
text: i18n.ts.assignRole,
action: assignRole,
}
: undefined,
null,
{
type: 'parent',
Expand All @@ -238,69 +298,6 @@ export function getUserMenu(user: misskey.entities.UserDetailed, router: Router
] as any;

if ($i && meId !== user.id) {
if (iAmModerator) {
menu = menu.concat([
{
type: 'parent',
icon: 'ti ti-badges',
text: i18n.ts.roles,
children: async () => {
const roles = await os.api('admin/roles/list');

return roles
.filter((r) => r.target === 'manual')
.map((r) => ({
text: r.name,
action: async () => {
const { canceled, result: period } = await os.select({
title: i18n.ts.period,
items: [
{
value: 'indefinitely',
text: i18n.ts.indefinitely,
},
{
value: 'oneHour',
text: i18n.ts.oneHour,
},
{
value: 'oneDay',
text: i18n.ts.oneDay,
},
{
value: 'oneWeek',
text: i18n.ts.oneWeek,
},
{
value: 'oneMonth',
text: i18n.ts.oneMonth,
},
],
default: 'indefinitely',
});
if (canceled) return;

const expiresAt =
period === 'indefinitely'
? null
: period === 'oneHour'
? Date.now() + 1000 * 60 * 60
: period === 'oneDay'
? Date.now() + 1000 * 60 * 60 * 24
: period === 'oneWeek'
? Date.now() + 1000 * 60 * 60 * 24 * 7
: period === 'oneMonth'
? Date.now() + 1000 * 60 * 60 * 24 * 30
: null;

os.apiWithDialog('admin/roles/assign', { roleId: r.id, userId: user.id, expiresAt });
},
}));
},
},
]);
}

menu = menu.concat([
null,
{
Expand Down
Loading