mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 13:35:42 +09:00
feat(api): add moderation events and visibility actions (#3219)
This commit is contained in:
1 parent
532e828fe6
commit
05257d6439
39 files changed
+2464
-88
No files matched your search
@@ -178,7 +178,7 @@ export function ReportAdminController(app: HonoApp) {
|
||||
const adminUserId = ctx.get('adminUserId');
|
||||
const auditLogReason = ctx.get('auditLogReason');
|
||||
const {report_id} = ctx.req.valid('param');
|
||||
const {public_comment, notify_reporter} = ctx.req.valid('json');
|
||||
const {public_comment, notify_reporter, resolution} = ctx.req.valid('json');
|
||||
return ctx.json(
|
||||
await adminService.reportServiceAggregate.resolveReport(
|
||||
createReportID(report_id),
|
||||
@@ -186,6 +186,7 @@ export function ReportAdminController(app: HonoApp) {
|
||||
public_comment || null,
|
||||
auditLogReason,
|
||||
notify_reporter,
|
||||
resolution,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -261,7 +261,8 @@ export class AdminMessageService {
|
||||
|
||||
private async getMessageResponseAccessForAdmin(channelId: ChannelID): Promise<MessageResponseAccessContext> {
|
||||
const channel = await this.deps.channelRepository.findUnique(channelId);
|
||||
return channel ? messageResponseAccessForChannel(channel) : messageResponseAccessForGuild(null);
|
||||
const access = channel ? messageResponseAccessForChannel(channel) : messageResponseAccessForGuild(null);
|
||||
return {...access, includeHidden: true};
|
||||
}
|
||||
|
||||
private async listMessageResponsesForAdmin(params: {
|
||||
|
||||
@@ -34,11 +34,12 @@ import type {User} from '@app/api/models/User';
|
||||
import type {IARMessageContext, IARSubmission} from '@app/api/report/IReportRepository';
|
||||
import type {ReportService} from '@app/api/report/ReportService';
|
||||
import {getReportSearchService} from '@app/api/SearchFactory';
|
||||
import {isHiddenPartial} from '@app/api/user/ProfileVisibility';
|
||||
import type {UserChannelService} from '@app/api/user/services/UserChannelService';
|
||||
import {assertSafeByteSize} from '@app/api/utils/ByteSizeUtils';
|
||||
import {AdminACLs} from '@fluxer/constants/src/AdminACLs';
|
||||
import {FeatureTemporarilyDisabledError} from '@fluxer/errors/src/domains/core/FeatureTemporarilyDisabledError';
|
||||
import type {SearchReportsRequest} from '@fluxer/schema/src/domains/admin/AdminSchemas';
|
||||
import type {SearchReportsRequest, UpdateReportRequest} from '@fluxer/schema/src/domains/admin/AdminSchemas';
|
||||
import type {MessageResponse} from '@fluxer/schema/src/domains/message/MessageResponseSchemas';
|
||||
import {getEmailTemplate} from '@pkgs/email/src/email_i18n/EmailI18n';
|
||||
import {seconds} from 'itty-time';
|
||||
@@ -56,6 +57,8 @@ interface AdminReportServiceDeps {
|
||||
ncmecSubmissionService: NcmecSubmissionService;
|
||||
}
|
||||
|
||||
type StaffReportResolution = NonNullable<UpdateReportRequest['resolution']>;
|
||||
|
||||
interface ReportNsfwLookupCache {
|
||||
channelNsfwByChannelId: Map<string, boolean | null>;
|
||||
guildNsfwLevelByGuildId: Map<string, number | null>;
|
||||
@@ -105,10 +108,14 @@ export class AdminReportService {
|
||||
publicComment: string | null,
|
||||
auditLogReason: string | null,
|
||||
notifyReporter: boolean,
|
||||
resolution?: StaffReportResolution,
|
||||
) {
|
||||
const {reportService, auditService} = this.deps;
|
||||
const {users: userRepository, email: emailService} = this.deps.apiContext.services;
|
||||
const resolvedReport = await reportService.resolveReport(reportId, adminUserId, publicComment, auditLogReason);
|
||||
const resolvedReport = await reportService.resolveReport(reportId, adminUserId, publicComment, auditLogReason, {
|
||||
outcome: resolution,
|
||||
resolvedBy: 'staff',
|
||||
});
|
||||
let reporterDmSent = false;
|
||||
let reporterEmailSent = false;
|
||||
const reporter =
|
||||
@@ -147,6 +154,7 @@ export class AdminReportService {
|
||||
['notify_reporter', notifyReporter ? 'true' : 'false'],
|
||||
['reporter_dm_sent', reporterDmSent ? 'true' : 'false'],
|
||||
['reporter_email_sent', reporterEmailSent ? 'true' : 'false'],
|
||||
...(resolution ? [['resolution', resolution] as [string, string]] : []),
|
||||
]),
|
||||
});
|
||||
return {
|
||||
@@ -418,7 +426,8 @@ export class AdminReportService {
|
||||
|
||||
private async getMessageResponseAccessForAdmin(channelId: ChannelID): Promise<MessageResponseAccessContext> {
|
||||
const channel = await this.deps.channelRepository.findUnique(channelId);
|
||||
return channel ? messageResponseAccessForChannel(channel) : messageResponseAccessForGuild(null);
|
||||
const access = channel ? messageResponseAccessForChannel(channel) : messageResponseAccessForGuild(null);
|
||||
return {...access, includeHidden: true};
|
||||
}
|
||||
|
||||
private async getMutualDmChannelId(report: IARSubmission): Promise<string | null> {
|
||||
@@ -617,7 +626,11 @@ export class AdminReportService {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const user = await this.deps.userCacheService.getUserPartialResponse(userId, requestCache);
|
||||
const cached = await this.deps.userCacheService.getUserPartialResponse(userId, requestCache);
|
||||
const stored = isHiddenPartial(cached) ? await this.deps.apiContext.services.users.findUnique(userId) : null;
|
||||
const user = stored
|
||||
? {username: stored.username, global_name: stored.globalName, discriminator: stored.discriminator.toString()}
|
||||
: cached;
|
||||
const discriminator = user.discriminator?.padStart(4, '0') ?? '0000';
|
||||
return {
|
||||
tag: `${user.username}#${discriminator}`,
|
||||
|
||||
@@ -19,6 +19,7 @@ import type {ReportService} from '@app/api/report/ReportService';
|
||||
import {getReportSearchService} from '@app/api/SearchFactory';
|
||||
import type {StoreEntitlementService} from '@app/api/store_billing/StoreEntitlementService';
|
||||
import {clearNewConversationLimit} from '@app/api/user/NewConversationLimit';
|
||||
import {isEnforcementDeletionReason} from '@app/api/user/ProfileVisibility';
|
||||
import {clearPendingDeletion, reschedulePendingDeletion} from '@app/api/user/services/PendingDeletionCoordinator';
|
||||
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
|
||||
import {DeletionReasons} from '@fluxer/constants/src/Core';
|
||||
@@ -246,7 +247,7 @@ export class AdminUserDeletionService {
|
||||
let knownIps: ReadonlySet<string> = new Set();
|
||||
if (data.reason_code !== DeletionReasons.USER_REQUESTED) {
|
||||
knownIps = await this.banIdentifiersForScheduledDeletion({user, adminUserId, auditLogReason});
|
||||
await this.resolvePendingReportsAgainstUser({user, adminUserId});
|
||||
await this.resolvePendingReportsAgainstUser({user, adminUserId, reasonCode: data.reason_code});
|
||||
}
|
||||
await emitAdminAction(adminUserId, userId, 'schedule_deletion', {reasonCode: data.reason_code, ips: knownIps});
|
||||
await updatePropagator.propagateUserUpdate({userId, oldUser: user, updatedUser: updatedUser});
|
||||
@@ -381,8 +382,13 @@ export class AdminUserDeletionService {
|
||||
return knownIps;
|
||||
}
|
||||
|
||||
private async resolvePendingReportsAgainstUser(params: {user: User; adminUserId: UserID}): Promise<void> {
|
||||
const {user, adminUserId} = params;
|
||||
private async resolvePendingReportsAgainstUser(params: {
|
||||
user: User;
|
||||
adminUserId: UserID;
|
||||
reasonCode: number;
|
||||
}): Promise<void> {
|
||||
const {user, adminUserId, reasonCode} = params;
|
||||
const outcome = isEnforcementDeletionReason(reasonCode) ? 'actioned' : 'auto_resolved';
|
||||
const {reportService, auditService} = this.deps;
|
||||
const reportSearchService = getReportSearchService();
|
||||
if (!reportSearchService) {
|
||||
@@ -423,7 +429,10 @@ export class AdminUserDeletionService {
|
||||
for (const hitId of pendingReportIds) {
|
||||
const reportId = createReportID(BigInt(hitId));
|
||||
try {
|
||||
await reportService.resolveReport(reportId, adminUserId, null, auditLogReason);
|
||||
await reportService.resolveReport(reportId, adminUserId, null, auditLogReason, {
|
||||
outcome,
|
||||
resolvedBy: 'system',
|
||||
});
|
||||
resolvedCount++;
|
||||
} catch (error) {
|
||||
if (error instanceof ReportAlreadyResolvedError) continue;
|
||||
|
||||
@@ -78,6 +78,7 @@ export class AdminGuildMembershipService {
|
||||
reason: data.reason ?? undefined,
|
||||
banDurationSeconds: data.ban_duration_seconds ?? undefined,
|
||||
skipGuildAuditLog: true,
|
||||
by: 'staff',
|
||||
},
|
||||
auditLogReason,
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user