enhance: improve moderation log
This commit is contained in:
@@ -7,7 +7,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Brackets } from 'typeorm';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { MiUser } from '@/models/User.js';
|
||||
import type { AnnouncementReadsRepository, AnnouncementsRepository, MiAnnouncement, MiAnnouncementRead } from '@/models/_.js';
|
||||
import type { AnnouncementReadsRepository, AnnouncementsRepository, MiAnnouncement, MiAnnouncementRead, UsersRepository } from '@/models/_.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { Packed } from '@/misc/json-schema.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
@@ -23,6 +23,9 @@ export class AnnouncementService {
|
||||
@Inject(DI.announcementReadsRepository)
|
||||
private announcementReadsRepository: AnnouncementReadsRepository,
|
||||
|
||||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
|
||||
private idService: IdService,
|
||||
private globalEventService: GlobalEventService,
|
||||
private moderationLogService: ModerationLogService,
|
||||
@@ -83,10 +86,13 @@ export class AnnouncementService {
|
||||
});
|
||||
|
||||
if (moderator) {
|
||||
const user = await this.usersRepository.findOneByOrFail({ id: values.userId });
|
||||
this.moderationLogService.log(moderator, 'createUserAnnouncement', {
|
||||
announcementId: announcement.id,
|
||||
announcement: announcement,
|
||||
userId: values.userId,
|
||||
userUsername: user.username,
|
||||
userHost: user.host,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
@@ -127,10 +133,14 @@ export class AnnouncementService {
|
||||
|
||||
if (moderator) {
|
||||
if (announcement.userId) {
|
||||
const user = await this.usersRepository.findOneByOrFail({ id: announcement.userId });
|
||||
this.moderationLogService.log(moderator, 'updateUserAnnouncement', {
|
||||
announcementId: announcement.id,
|
||||
before: announcement,
|
||||
after: after,
|
||||
userId: announcement.userId,
|
||||
userUsername: user.username,
|
||||
userHost: user.host,
|
||||
});
|
||||
} else {
|
||||
this.moderationLogService.log(moderator, 'updateGlobalAnnouncement', {
|
||||
|
@@ -134,11 +134,11 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
||||
|
||||
this.localEmojisCache.refresh();
|
||||
|
||||
const updated = await this.emojiEntityService.packDetailed(emoji.id);
|
||||
const packed = await this.emojiEntityService.packDetailed(emoji.id);
|
||||
|
||||
if (emoji.name === data.name) {
|
||||
this.globalEventService.publishBroadcastStream('emojiUpdated', {
|
||||
emojis: [updated],
|
||||
emojis: [packed],
|
||||
});
|
||||
} else {
|
||||
this.globalEventService.publishBroadcastStream('emojiDeleted', {
|
||||
@@ -146,11 +146,12 @@ export class CustomEmojiService implements OnApplicationShutdown {
|
||||
});
|
||||
|
||||
this.globalEventService.publishBroadcastStream('emojiAdded', {
|
||||
emoji: updated,
|
||||
emoji: packed,
|
||||
});
|
||||
}
|
||||
|
||||
if (moderator) {
|
||||
const updated = await this.emojisRepository.findOneByOrFail({ id: id });
|
||||
this.moderationLogService.log(moderator, 'updateCustomEmoji', {
|
||||
emojiId: emoji.id,
|
||||
before: emoji,
|
||||
|
@@ -686,15 +686,20 @@ export class DriveService {
|
||||
|
||||
if (await this.roleService.isModerator(updater) && (file.userId !== updater.id)) {
|
||||
if (values.isSensitive !== undefined && values.isSensitive !== file.isSensitive) {
|
||||
const user = file.userId ? await this.usersRepository.findOneByOrFail({ id: file.userId }) : null;
|
||||
if (values.isSensitive) {
|
||||
this.moderationLogService.log(updater, 'markSensitiveDriveFile', {
|
||||
fileId: file.id,
|
||||
fileUserId: file.userId,
|
||||
fileUserUsername: user?.username ?? null,
|
||||
fileUserHost: user?.host ?? null,
|
||||
});
|
||||
} else {
|
||||
this.moderationLogService.log(updater, 'unmarkSensitiveDriveFile', {
|
||||
fileId: file.id,
|
||||
fileUserId: file.userId,
|
||||
fileUserUsername: user?.username ?? null,
|
||||
fileUserHost: user?.host ?? null,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -795,9 +800,12 @@ export class DriveService {
|
||||
}
|
||||
|
||||
if (deleter && await this.roleService.isModerator(deleter) && (file.userId !== deleter.id)) {
|
||||
const user = file.userId ? await this.usersRepository.findOneByOrFail({ id: file.userId }) : null;
|
||||
this.moderationLogService.log(deleter, 'deleteDriveFile', {
|
||||
fileId: file.id,
|
||||
fileUserId: file.userId,
|
||||
fileUserUsername: user?.username ?? null,
|
||||
fileUserHost: user?.host ?? null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -135,9 +135,12 @@ export class NoteDeleteService {
|
||||
});
|
||||
|
||||
if (deleter && (note.userId !== deleter.id)) {
|
||||
const user = await this.usersRepository.findOneByOrFail({ id: note.userId });
|
||||
this.moderationLogService.log(deleter, 'deleteNote', {
|
||||
noteId: note.id,
|
||||
noteUserId: note.userId,
|
||||
noteUserUsername: user.username,
|
||||
noteUserHost: user.host,
|
||||
note: note,
|
||||
});
|
||||
}
|
||||
|
@@ -412,10 +412,13 @@ export class RoleService implements OnApplicationShutdown {
|
||||
this.globalEventService.publishInternalEvent('userRoleAssigned', created);
|
||||
|
||||
if (moderator) {
|
||||
const user = await this.usersRepository.findOneByOrFail({ id: userId });
|
||||
this.moderationLogService.log(moderator, 'assignRole', {
|
||||
roleId: roleId,
|
||||
roleName: role.name,
|
||||
userId: userId,
|
||||
userUsername: user.username,
|
||||
userHost: user.host,
|
||||
expiresAt: expiresAt ? expiresAt.toISOString() : null,
|
||||
});
|
||||
}
|
||||
@@ -445,11 +448,16 @@ export class RoleService implements OnApplicationShutdown {
|
||||
this.globalEventService.publishInternalEvent('userRoleUnassigned', existing);
|
||||
|
||||
if (moderator) {
|
||||
const role = await this.rolesRepository.findOneByOrFail({ id: roleId });
|
||||
const [user, role] = await Promise.all([
|
||||
this.usersRepository.findOneByOrFail({ id: userId }),
|
||||
this.rolesRepository.findOneByOrFail({ id: roleId }),
|
||||
]);
|
||||
this.moderationLogService.log(moderator, 'unassignRole', {
|
||||
roleId: roleId,
|
||||
roleName: role.name,
|
||||
userId: userId,
|
||||
userUsername: user.username,
|
||||
userHost: user.host,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -473,6 +481,42 @@ export class RoleService implements OnApplicationShutdown {
|
||||
redisPipeline.exec();
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async create(values: Partial<MiRole>, moderator?: MiUser): Promise<MiRole> {
|
||||
const date = new Date();
|
||||
const created = await this.rolesRepository.insert({
|
||||
id: this.idService.genId(),
|
||||
createdAt: date,
|
||||
updatedAt: date,
|
||||
lastUsedAt: date,
|
||||
name: values.name,
|
||||
description: values.description,
|
||||
color: values.color,
|
||||
iconUrl: values.iconUrl,
|
||||
target: values.target,
|
||||
condFormula: values.condFormula,
|
||||
isPublic: values.isPublic,
|
||||
isAdministrator: values.isAdministrator,
|
||||
isModerator: values.isModerator,
|
||||
isExplorable: values.isExplorable,
|
||||
asBadge: values.asBadge,
|
||||
canEditMembersByModerator: values.canEditMembersByModerator,
|
||||
displayOrder: values.displayOrder,
|
||||
policies: values.policies,
|
||||
}).then(x => this.rolesRepository.findOneByOrFail(x.identifiers[0]));
|
||||
|
||||
this.globalEventService.publishInternalEvent('roleCreated', created);
|
||||
|
||||
if (moderator) {
|
||||
this.moderationLogService.log(moderator, 'createRole', {
|
||||
roleId: created.id,
|
||||
role: created,
|
||||
});
|
||||
}
|
||||
|
||||
return created;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async update(role: MiRole, params: Partial<MiRole>, moderator?: MiUser): Promise<void> {
|
||||
const date = new Date();
|
||||
|
@@ -73,7 +73,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
});
|
||||
|
||||
this.moderationLogService.log(me, 'resetPassword', {
|
||||
targetId: user.id,
|
||||
userId: user.id,
|
||||
userUsername: user.username,
|
||||
userHost: user.host,
|
||||
});
|
||||
|
||||
return {
|
||||
|
@@ -5,11 +5,8 @@
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { RolesRepository } from '@/models/_.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { RoleEntityService } from '@/core/entities/RoleEntityService.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin', 'role'],
|
||||
@@ -58,37 +55,11 @@ export const paramDef = {
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
@Inject(DI.rolesRepository)
|
||||
private rolesRepository: RolesRepository,
|
||||
|
||||
private globalEventService: GlobalEventService,
|
||||
private idService: IdService,
|
||||
private roleEntityService: RoleEntityService,
|
||||
private roleService: RoleService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const date = new Date();
|
||||
const created = await this.rolesRepository.insert({
|
||||
id: this.idService.genId(),
|
||||
createdAt: date,
|
||||
updatedAt: date,
|
||||
lastUsedAt: date,
|
||||
name: ps.name,
|
||||
description: ps.description,
|
||||
color: ps.color,
|
||||
iconUrl: ps.iconUrl,
|
||||
target: ps.target,
|
||||
condFormula: ps.condFormula,
|
||||
isPublic: ps.isPublic,
|
||||
isAdministrator: ps.isAdministrator,
|
||||
isModerator: ps.isModerator,
|
||||
isExplorable: ps.isExplorable,
|
||||
asBadge: ps.asBadge,
|
||||
canEditMembersByModerator: ps.canEditMembersByModerator,
|
||||
displayOrder: ps.displayOrder,
|
||||
policies: ps.policies,
|
||||
}).then(x => this.rolesRepository.findOneByOrFail(x.identifiers[0]));
|
||||
|
||||
this.globalEventService.publishInternalEvent('roleCreated', created);
|
||||
const created = await this.roleService.create(ps, me);
|
||||
|
||||
return await this.roleEntityService.pack(created, me);
|
||||
});
|
||||
|
@@ -79,9 +79,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
throw new ApiError(meta.errors.noSuchRole);
|
||||
}
|
||||
|
||||
const date = new Date();
|
||||
await this.roleService.update(role, {
|
||||
updatedAt: date,
|
||||
name: ps.name,
|
||||
description: ps.description,
|
||||
color: ps.color,
|
||||
|
@@ -61,7 +61,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
});
|
||||
|
||||
this.moderationLogService.log(me, 'suspend', {
|
||||
targetId: user.id,
|
||||
userId: user.id,
|
||||
userUsername: user.username,
|
||||
userHost: user.host,
|
||||
});
|
||||
|
||||
(async () => {
|
||||
|
@@ -46,7 +46,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
});
|
||||
|
||||
this.moderationLogService.log(me, 'unsuspend', {
|
||||
targetId: user.id,
|
||||
userId: user.id,
|
||||
userUsername: user.username,
|
||||
userHost: user.host,
|
||||
});
|
||||
|
||||
this.userSuspendService.doPostUnsuspend(user);
|
||||
|
@@ -51,6 +51,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
|
||||
this.moderationLogService.log(me, 'updateUserNote', {
|
||||
userId: user.id,
|
||||
userUsername: user.username,
|
||||
userHost: user.host,
|
||||
before: currentProfile.moderationNote,
|
||||
after: ps.text,
|
||||
});
|
||||
|
@@ -37,6 +37,7 @@ export const moderationLogTypes = [
|
||||
'deleteCustomEmoji',
|
||||
'assignRole',
|
||||
'unassignRole',
|
||||
'createRole',
|
||||
'updateRole',
|
||||
'deleteRole',
|
||||
'clearQueue',
|
||||
@@ -62,13 +63,19 @@ export type ModerationLogPayloads = {
|
||||
after: any | null;
|
||||
};
|
||||
suspend: {
|
||||
targetId: string;
|
||||
userId: string;
|
||||
userUsername: string;
|
||||
userHost: string | null;
|
||||
};
|
||||
unsuspend: {
|
||||
targetId: string;
|
||||
userId: string;
|
||||
userUsername: string;
|
||||
userHost: string | null;
|
||||
};
|
||||
updateUserNote: {
|
||||
userId: string;
|
||||
userUsername: string;
|
||||
userHost: string | null;
|
||||
before: string | null;
|
||||
after: string | null;
|
||||
};
|
||||
@@ -87,15 +94,23 @@ export type ModerationLogPayloads = {
|
||||
};
|
||||
assignRole: {
|
||||
userId: string;
|
||||
userUsername: string;
|
||||
userHost: string | null;
|
||||
roleId: string;
|
||||
roleName: string;
|
||||
expiresAt: string | null;
|
||||
};
|
||||
unassignRole: {
|
||||
userId: string;
|
||||
userUsername: string;
|
||||
userHost: string | null;
|
||||
roleId: string;
|
||||
roleName: string;
|
||||
};
|
||||
createRole: {
|
||||
roleId: string;
|
||||
role: any;
|
||||
};
|
||||
updateRole: {
|
||||
roleId: string;
|
||||
before: any;
|
||||
@@ -110,10 +125,14 @@ export type ModerationLogPayloads = {
|
||||
deleteDriveFile: {
|
||||
fileId: string;
|
||||
fileUserId: string | null;
|
||||
fileUserUsername: string | null;
|
||||
fileUserHost: string | null;
|
||||
};
|
||||
deleteNote: {
|
||||
noteId: string;
|
||||
noteUserId: string;
|
||||
noteUserUsername: string;
|
||||
noteUserHost: string | null;
|
||||
note: any;
|
||||
};
|
||||
createGlobalAnnouncement: {
|
||||
@@ -124,6 +143,8 @@ export type ModerationLogPayloads = {
|
||||
announcementId: string;
|
||||
announcement: any;
|
||||
userId: string;
|
||||
userUsername: string;
|
||||
userHost: string | null;
|
||||
};
|
||||
updateGlobalAnnouncement: {
|
||||
announcementId: string;
|
||||
@@ -134,6 +155,9 @@ export type ModerationLogPayloads = {
|
||||
announcementId: string;
|
||||
before: any;
|
||||
after: any;
|
||||
userId: string;
|
||||
userUsername: string;
|
||||
userHost: string | null;
|
||||
};
|
||||
deleteGlobalAnnouncement: {
|
||||
announcementId: string;
|
||||
@@ -144,7 +168,9 @@ export type ModerationLogPayloads = {
|
||||
announcement: any;
|
||||
};
|
||||
resetPassword: {
|
||||
targetId: string;
|
||||
userId: string;
|
||||
userUsername: string;
|
||||
userHost: string | null;
|
||||
};
|
||||
suspendRemoteInstance: {
|
||||
id: string;
|
||||
@@ -157,9 +183,13 @@ export type ModerationLogPayloads = {
|
||||
markSensitiveDriveFile: {
|
||||
fileId: string;
|
||||
fileUserId: string | null;
|
||||
fileUserUsername: string | null;
|
||||
fileUserHost: string | null;
|
||||
};
|
||||
unmarkSensitiveDriveFile: {
|
||||
fileId: string;
|
||||
fileUserId: string | null;
|
||||
fileUserUsername: string | null;
|
||||
fileUserHost: string | null;
|
||||
};
|
||||
};
|
||||
|
Reference in New Issue
Block a user