add moderation logs for many endpoints

- `/admin/delete-all-files-of-a-user`
- `/admin/nsfw-user`
- `/admin/unnsfw-user`
- `/admin/silence-user`
- `/admin/unsilence-user`
- `/admin/accounts/create`
- `/admin/drive/clean-remote-files`
- `/admin/drive/cleanup`
- `/admin/emoji/set-category-bulk`
- `/admin/emoji/set-license-bulk`
- `/admin/emoji/set-aliases-bulk`
- `/admin/emoji/add-aliases-bulk`
- `/admin/emoji/remove-aliases-bulk`
- `/admin/emoji/import-zip`
- `/admin/federation/delete-all-files`
- `/admin/federation/remove-all-following`
- `/admin/promo/create`
- `/admin/relay/add`
- `/admin/relay/remove`
This commit is contained in:
Hazelnoot 2025-02-26 23:18:30 -05:00
parent 504e90c190
commit 27d43879a2
25 changed files with 480 additions and 43 deletions

View file

@ -15,6 +15,7 @@ import type { Config } from '@/config.js';
import { ApiError } from '@/server/api/error.js';
import { Packed } from '@/misc/json-schema.js';
import { RoleService } from '@/core/RoleService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
export const meta = {
tags: ['admin'],
@ -96,6 +97,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private userEntityService: UserEntityService,
private signupService: SignupService,
private instanceActorService: InstanceActorService,
private readonly moderationLogService: ModerationLogService,
) {
super(meta, paramDef, async (ps, _me, token) => {
const me = _me ? await this.usersRepository.findOneByOrFail({ id: _me.id }) : null;
@ -137,6 +139,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
approved: true,
});
if (me) {
await this.moderationLogService.log(me, 'createAccount', {
userId: account.id,
userUsername: account.username,
});
}
const res = await this.userEntityService.pack(account, account, {
schema: 'MeDetailed',
includeSecrets: true,

View file

@ -8,6 +8,8 @@ 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';
import { ModerationLogService } from '@/core/ModerationLogService.js';
import { CacheService } from '@/core/CacheService.js';
export const meta = {
tags: ['admin'],
@ -30,14 +32,23 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
constructor(
@Inject(DI.driveFilesRepository)
private driveFilesRepository: DriveFilesRepository,
private readonly cacheService: CacheService,
private readonly moderationLogService: ModerationLogService,
private driveService: DriveService,
) {
super(meta, paramDef, async (ps, me) => {
const user = await this.cacheService.findUserById(ps.userId);
const files = await this.driveFilesRepository.findBy({
userId: ps.userId,
});
await this.moderationLogService.log(me, 'clearUserFiles', {
userId: ps.userId,
userUsername: user.username,
userHost: user.host,
count: files.length,
});
for (const file of files) {
this.driveService.deleteFile(file, false, me);
}

View file

@ -6,6 +6,7 @@
import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { QueueService } from '@/core/QueueService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
export const meta = {
tags: ['admin'],
@ -25,9 +26,11 @@ export const paramDef = {
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private queueService: QueueService,
private readonly moderationLogService: ModerationLogService,
) {
super(meta, paramDef, async (ps, me) => {
this.queueService.createCleanRemoteFilesJob();
await this.moderationLogService.log(me, 'clearRemoteFiles', {});
await this.queueService.createCleanRemoteFilesJob();
});
}
}

View file

@ -9,6 +9,7 @@ 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';
import { ModerationLogService } from '@/core/ModerationLogService.js';
export const meta = {
tags: ['admin'],
@ -29,7 +30,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
constructor(
@Inject(DI.driveFilesRepository)
private driveFilesRepository: DriveFilesRepository,
private readonly moderationLogService: ModerationLogService,
private driveService: DriveService,
) {
super(meta, paramDef, async (ps, me) => {
@ -37,6 +38,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
userId: IsNull(),
});
await this.moderationLogService.log(me, 'clearOwnerlessFiles', {
count: files.length,
});
for (const file of files) {
this.driveService.deleteFile(file);
}

View file

@ -6,6 +6,7 @@
import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
export const meta = {
tags: ['admin'],
@ -32,8 +33,13 @@ export const paramDef = {
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private customEmojiService: CustomEmojiService,
private readonly moderationLogService: ModerationLogService,
) {
super(meta, paramDef, async (ps, me) => {
await this.moderationLogService.log(me, 'updateCustomEmojis', {
ids: ps.ids,
addAliases: ps.aliases,
});
await this.customEmojiService.addAliasesBulk(ps.ids, ps.aliases.map(a => a.normalize('NFC')));
});
}

View file

@ -3,9 +3,12 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Injectable } from '@nestjs/common';
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { QueueService } from '@/core/QueueService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
import type { DriveFilesRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
export const meta = {
secure: true,
@ -25,9 +28,17 @@ export const paramDef = {
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private queueService: QueueService,
private readonly moderationLogService: ModerationLogService,
@Inject(DI.driveFilesRepository)
private readonly driveFilesRepository: DriveFilesRepository,
) {
super(meta, paramDef, async (ps, me) => {
this.queueService.createImportCustomEmojisJob(me, ps.fileId);
const file = await driveFilesRepository.findOneByOrFail({ id: ps.fileId });
await this.moderationLogService.log(me, 'importCustomEmojis', {
fileId: file.id,
fileName: file.name,
});
await this.queueService.createImportCustomEmojisJob(me, ps.fileId);
});
}
}

View file

@ -6,6 +6,7 @@
import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
export const meta = {
tags: ['admin'],
@ -32,8 +33,13 @@ export const paramDef = {
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private customEmojiService: CustomEmojiService,
private readonly moderationLogService: ModerationLogService,
) {
super(meta, paramDef, async (ps, me) => {
await this.moderationLogService.log(me, 'updateCustomEmojis', {
ids: ps.ids,
delAliases: ps.aliases,
});
await this.customEmojiService.removeAliasesBulk(ps.ids, ps.aliases.map(a => a.normalize('NFC')));
});
}

View file

@ -6,6 +6,7 @@
import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
export const meta = {
tags: ['admin'],
@ -32,8 +33,13 @@ export const paramDef = {
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private customEmojiService: CustomEmojiService,
private readonly moderationLogService: ModerationLogService,
) {
super(meta, paramDef, async (ps, me) => {
await this.moderationLogService.log(me, 'updateCustomEmojis', {
ids: ps.ids,
setAliases: ps.aliases,
});
await this.customEmojiService.setAliasesBulk(ps.ids, ps.aliases.map(a => a.normalize('NFC')));
});
}

View file

@ -6,6 +6,7 @@
import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
export const meta = {
tags: ['admin'],
@ -34,8 +35,13 @@ export const paramDef = {
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private customEmojiService: CustomEmojiService,
private readonly moderationLogService: ModerationLogService,
) {
super(meta, paramDef, async (ps, me) => {
await this.moderationLogService.log(me, 'updateCustomEmojis', {
ids: ps.ids,
category: ps.category,
});
await this.customEmojiService.setCategoryBulk(ps.ids, ps.category?.normalize('NFC') ?? null);
});
}

View file

@ -6,6 +6,7 @@
import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
export const meta = {
tags: ['admin'],
@ -34,8 +35,13 @@ export const paramDef = {
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private customEmojiService: CustomEmojiService,
private readonly moderationLogService: ModerationLogService,
) {
super(meta, paramDef, async (ps, me) => {
await this.moderationLogService.log(me, 'updateCustomEmojis', {
ids: ps.ids,
license: ps.license,
});
await this.customEmojiService.setLicenseBulk(ps.ids, ps.license ?? null);
});
}

View file

@ -8,6 +8,7 @@ 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';
import { ModerationLogService } from '@/core/ModerationLogService.js';
export const meta = {
tags: ['admin'],
@ -30,7 +31,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
constructor(
@Inject(DI.driveFilesRepository)
private driveFilesRepository: DriveFilesRepository,
private readonly moderationLogService: ModerationLogService,
private driveService: DriveService,
) {
super(meta, paramDef, async (ps, me) => {
@ -38,6 +39,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
userHost: ps.host,
});
await this.moderationLogService.log(me, 'clearInstanceFiles', {
host: ps.host,
count: files.length,
});
for (const file of files) {
this.driveService.deleteFile(file);
}

View file

@ -8,6 +8,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
import type { FollowingsRepository, UsersRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { QueueService } from '@/core/QueueService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
export const meta = {
tags: ['admin'],
@ -35,6 +36,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private followingsRepository: FollowingsRepository,
private queueService: QueueService,
private readonly moderationLogService: ModerationLogService,
) {
super(meta, paramDef, async (ps, me) => {
const followings = await this.followingsRepository.findBy([
@ -51,6 +53,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
this.usersRepository.findOneByOrFail({ id: f.followeeId }),
]).then(([from, to]) => [{ id: from.id }, { id: to.id }])));
await this.moderationLogService.log(me, 'severFollowRelations', {
host: ps.host,
});
this.queueService.createUnfollowJob(pairs.map(p => ({ from: p[0], to: p[1], silent: true })));
});
}

View file

@ -5,9 +5,10 @@
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { UsersRepository, UserProfilesRepository } from '@/models/_.js';
import type { UserProfilesRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { CacheService } from '@/core/CacheService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
export const meta = {
tags: ['admin'],
@ -28,20 +29,19 @@ export const paramDef = {
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
@Inject(DI.usersRepository)
private readonly usersRepository: UsersRepository,
@Inject(DI.userProfilesRepository)
private readonly userProfilesRepository: UserProfilesRepository,
private readonly moderationLogService: ModerationLogService,
private readonly cacheService: CacheService,
) {
super(meta, paramDef, async (ps, me) => {
const user = await this.usersRepository.findOneBy({ id: ps.userId });
const user = await this.cacheService.findUserById(ps.userId);
if (user == null) {
throw new Error('user not found');
}
await this.moderationLogService.log(me, 'nsfwUser', {
userId: ps.userId,
userUsername: user.username,
userHost: user.host,
});
await this.userProfilesRepository.update(user.id, {
alwaysMarkNsfw: true,

View file

@ -8,6 +8,8 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
import type { PromoNotesRepository } from '@/models/_.js';
import { GetterService } from '@/server/api/GetterService.js';
import { DI } from '@/di-symbols.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
import { CacheService } from '@/core/CacheService.js';
import { ApiError } from '../../../error.js';
export const meta = {
@ -46,7 +48,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
constructor(
@Inject(DI.promoNotesRepository)
private promoNotesRepository: PromoNotesRepository,
private readonly moderationLogService: ModerationLogService,
private readonly cacheService: CacheService,
private getterService: GetterService,
) {
super(meta, paramDef, async (ps, me) => {
@ -61,6 +64,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new ApiError(meta.errors.alreadyPromoted);
}
const user = await this.cacheService.findUserById(note.userId);
await this.moderationLogService.log(me, 'createPromo', {
noteId: note.id,
noteUserId: user.id,
noteUserUsername: user.username,
noteUserHost: user.host,
note: note,
});
await this.promoNotesRepository.insert({
noteId: note.id,
expiresAt: new Date(ps.expiresAt),

View file

@ -8,6 +8,7 @@ import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { RelayService } from '@/core/RelayService.js';
import { ApiError } from '../../../error.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
export const meta = {
tags: ['admin'],
@ -64,6 +65,7 @@ export const paramDef = {
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private relayService: RelayService,
private readonly moderationLogService: ModerationLogService,
) {
super(meta, paramDef, async (ps, me) => {
try {
@ -72,6 +74,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new ApiError(meta.errors.invalidUrl);
}
await this.moderationLogService.log(me, 'addRelay', {
inbox: ps.inbox,
});
return await this.relayService.addRelay(ps.inbox);
});
}

View file

@ -6,6 +6,7 @@
import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { RelayService } from '@/core/RelayService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
export const meta = {
tags: ['admin'],
@ -27,9 +28,13 @@ export const paramDef = {
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
private relayService: RelayService,
private readonly moderationLogService: ModerationLogService,
) {
super(meta, paramDef, async (ps, me) => {
return await this.relayService.removeRelay(ps.inbox);
await this.moderationLogService.log(me, 'removeRelay', {
inbox: ps.inbox,
});
await this.relayService.removeRelay(ps.inbox);
});
}
}

View file

@ -8,6 +8,9 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
import type { UsersRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { RoleService } from '@/core/RoleService.js';
import { CacheService } from '@/core/CacheService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
export const meta = {
tags: ['admin'],
@ -29,24 +32,32 @@ export const paramDef = {
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
private roleService: RoleService,
private readonly usersRepository: UsersRepository,
private readonly cacheService: CacheService,
private readonly moderationLogService: ModerationLogService,
private readonly roleService: RoleService,
private readonly globalEventService: GlobalEventService,
) {
super(meta, paramDef, async (ps, me) => {
const user = await this.usersRepository.findOneBy({ id: ps.userId });
if (user == null) {
throw new Error('user not found');
}
const user = await this.cacheService.findUserById(ps.userId);
if (await this.roleService.isModerator(user)) {
throw new Error('cannot silence moderator account');
}
await this.moderationLogService.log(me, 'silenceUser', {
userId: ps.userId,
userUsername: user.username,
userHost: user.host,
});
await this.usersRepository.update(user.id, {
isSilenced: true,
});
this.globalEventService.publishInternalEvent(user.host == null ? 'localUserUpdated' : 'remoteUserUpdated', {
id: user.id,
});
});
}
}

View file

@ -5,8 +5,10 @@
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { UsersRepository, UserProfilesRepository } from '@/models/_.js';
import type { UserProfilesRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { CacheService } from '@/core/CacheService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
export const meta = {
tags: ['admin'],
@ -27,18 +29,19 @@ export const paramDef = {
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
private readonly cacheService: CacheService,
private readonly moderationLogService: ModerationLogService,
@Inject(DI.userProfilesRepository)
private userProfilesRepository: UserProfilesRepository,
private readonly userProfilesRepository: UserProfilesRepository,
) {
super(meta, paramDef, async (ps, me) => {
const user = await this.usersRepository.findOneBy({ id: ps.userId });
const user = await this.cacheService.findUserById(ps.userId);
if (user == null) {
throw new Error('user not found');
}
await this.moderationLogService.log(me, 'unNsfwUser', {
userId: ps.userId,
userUsername: user.username,
userHost: user.host,
});
await this.userProfilesRepository.update(user.id, {
alwaysMarkNsfw: false,

View file

@ -7,6 +7,9 @@ import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { UsersRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
import { CacheService } from '@/core/CacheService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
export const meta = {
tags: ['admin'],
@ -28,18 +31,27 @@ export const paramDef = {
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
private readonly usersRepository: UsersRepository,
private readonly cacheService: CacheService,
private readonly moderationLogService: ModerationLogService,
private readonly globalEventService: GlobalEventService,
) {
super(meta, paramDef, async (ps, me) => {
const user = await this.usersRepository.findOneBy({ id: ps.userId });
const user = await this.cacheService.findUserById(ps.userId);
if (user == null) {
throw new Error('user not found');
}
await this.moderationLogService.log(me, 'unSilenceUser', {
userId: ps.userId,
userUsername: user.username,
userHost: user.host,
});
await this.usersRepository.update(user.id, {
isSilenced: false,
});
this.globalEventService.publishInternalEvent(user.host == null ? 'localUserUpdated' : 'remoteUserUpdated', {
id: user.id,
});
});
}
}