mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-11 21:18:29 +09:00
feat(admin)!: move the admin api to rest and fix its defects (#2515)
This commit is contained in:
1 parent
a70924d4b0
commit
7f8f09ee51
143 files changed
+15238
-12790
No files matched your search
@@ -1,21 +1,21 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {AdminACLs} from '@fluxer/constants/src/AdminACLs';
|
||||
import {ValidationErrorCodes} from '@fluxer/constants/src/ValidationErrorCodes';
|
||||
import {InputValidationError} from '@fluxer/errors/src/domains/core/InputValidationError';
|
||||
import {
|
||||
BrowseChannelRequest,
|
||||
AdminChannelMessageListQuery,
|
||||
AdminMessageSearchQuery,
|
||||
AdminMessageSearchResponse,
|
||||
BrowseChannelResponse,
|
||||
SearchChannelMessagesRequest,
|
||||
SearchChannelMessagesResponse,
|
||||
} from '@fluxer/schema/src/domains/admin/AdminMessageBrowseSchemas';
|
||||
import {
|
||||
DeleteAllUserMessagesRequest,
|
||||
AdminMessageDetailQuery,
|
||||
AdminUserMessageDeleteQuery,
|
||||
AdminUserMessageShredRequest,
|
||||
DeleteAllUserMessagesResponse,
|
||||
DeleteMessageRequest,
|
||||
LookupMessageByAttachmentRequest,
|
||||
LookupMessageRequest,
|
||||
MessageShredRequest,
|
||||
MessageShredJobIdParam,
|
||||
MessageShredResponse,
|
||||
MessageShredStatusRequest,
|
||||
ReportAttachmentToNcmecRequest,
|
||||
} from '@fluxer/schema/src/domains/admin/AdminMessageSchemas';
|
||||
import {
|
||||
@@ -24,6 +24,11 @@ import {
|
||||
MessageShredStatusResponse,
|
||||
NcmecAttachmentSubmitResultResponse,
|
||||
} from '@fluxer/schema/src/domains/admin/AdminSchemas';
|
||||
import {
|
||||
ChannelIdMessageIdParam,
|
||||
ChannelIdParam,
|
||||
UserIdParam,
|
||||
} from '@fluxer/schema/src/domains/common/CommonParamSchemas';
|
||||
import {createAttachmentID, createChannelID, createMessageID, createReportID} from '../../BrandedTypes';
|
||||
import {requireAdminACL} from '../../middleware/AdminMiddleware';
|
||||
import {RateLimitMiddleware} from '../../middleware/RateLimitMiddleware';
|
||||
@@ -33,48 +38,57 @@ import type {HonoApp} from '../../types/HonoEnv';
|
||||
import {Validator} from '../../Validator';
|
||||
|
||||
export function MessageAdminController(app: HonoApp) {
|
||||
app.post(
|
||||
'/admin/messages/lookup',
|
||||
app.get(
|
||||
'/admin/messages',
|
||||
RateLimitMiddleware(RateLimitConfigs.ADMIN_MESSAGE_OPERATION),
|
||||
requireAdminACL(AdminACLs.MESSAGE_LOOKUP),
|
||||
Validator('json', LookupMessageRequest),
|
||||
Validator('query', AdminMessageSearchQuery),
|
||||
OpenAPI({
|
||||
operationId: 'lookup_message',
|
||||
summary: 'Look up message details',
|
||||
operationId: 'search_admin_messages',
|
||||
summary: 'Search messages',
|
||||
description:
|
||||
'Retrieves complete message details including content, attachments, edits, and metadata. Look up by message ID and channel. Requires MESSAGE_LOOKUP permission.',
|
||||
responseSchema: LookupMessageResponse,
|
||||
'Searches the messages of a channel by content, or resolves a single message by its ID or by one of its attachments. Passing message_id returns that message with the messages surrounding it; passing attachment_id together with filename returns the message carrying that attachment with its surrounding context. Requires MESSAGE_LOOKUP permission.',
|
||||
responseSchema: AdminMessageSearchResponse,
|
||||
statusCode: 200,
|
||||
security: 'adminApiKey',
|
||||
tags: 'Admin',
|
||||
}),
|
||||
async (ctx) => {
|
||||
const adminService = ctx.get('adminService');
|
||||
return ctx.json(await adminService.messageService.lookupMessage(ctx.req.valid('json')));
|
||||
const query = ctx.req.valid('query');
|
||||
if (query.message_id != null) {
|
||||
return ctx.json(
|
||||
await adminService.messageService.lookupMessage({
|
||||
channel_id: query.channel_id,
|
||||
message_id: query.message_id,
|
||||
context_limit: query.context_limit,
|
||||
}),
|
||||
);
|
||||
}
|
||||
if (query.attachment_id != null) {
|
||||
if (query.filename == null) {
|
||||
throw InputValidationError.fromCode('filename', ValidationErrorCodes.INVALID_FORMAT);
|
||||
}
|
||||
return ctx.json(
|
||||
await adminService.messageService.lookupMessageByAttachment({
|
||||
channel_id: query.channel_id,
|
||||
attachment_id: query.attachment_id,
|
||||
filename: query.filename,
|
||||
context_limit: query.context_limit,
|
||||
}),
|
||||
);
|
||||
}
|
||||
return ctx.json(
|
||||
await adminService.messageService.searchChannelMessages({
|
||||
channel_id: query.channel_id,
|
||||
query: query.q ?? '',
|
||||
limit: query.limit,
|
||||
}),
|
||||
);
|
||||
},
|
||||
);
|
||||
app.post(
|
||||
'/admin/messages/lookup-by-attachment',
|
||||
RateLimitMiddleware(RateLimitConfigs.ADMIN_MESSAGE_OPERATION),
|
||||
requireAdminACL(AdminACLs.MESSAGE_LOOKUP),
|
||||
Validator('json', LookupMessageByAttachmentRequest),
|
||||
OpenAPI({
|
||||
operationId: 'lookup_message_by_attachment',
|
||||
summary: 'Look up message by attachment',
|
||||
description:
|
||||
'Finds and retrieves message containing a specific attachment by ID. Used to locate messages with sensitive or illegal content. Requires MESSAGE_LOOKUP permission.',
|
||||
responseSchema: LookupMessageResponse,
|
||||
statusCode: 200,
|
||||
security: 'adminApiKey',
|
||||
tags: 'Admin',
|
||||
}),
|
||||
async (ctx) => {
|
||||
const adminService = ctx.get('adminService');
|
||||
return ctx.json(await adminService.messageService.lookupMessageByAttachment(ctx.req.valid('json')));
|
||||
},
|
||||
);
|
||||
app.post(
|
||||
'/admin/messages/report-to-ncmec',
|
||||
'/admin/messages/ncmec-reports',
|
||||
RateLimitMiddleware(RateLimitConfigs.ADMIN_MESSAGE_OPERATION),
|
||||
requireAdminACL(AdminACLs.CSAM_SUBMIT_NCMEC),
|
||||
requireAdminACL(AdminACLs.MESSAGE_DELETE),
|
||||
@@ -82,8 +96,8 @@ export function MessageAdminController(app: HonoApp) {
|
||||
requireAdminACL(AdminACLs.ARCHIVE_TRIGGER_USER),
|
||||
Validator('json', ReportAttachmentToNcmecRequest),
|
||||
OpenAPI({
|
||||
operationId: 'report_message_attachment_to_ncmec',
|
||||
summary: 'Report an image attachment to NCMEC',
|
||||
operationId: 'create_admin_ncmec_report',
|
||||
summary: 'Report an attachment to NCMEC',
|
||||
description:
|
||||
'Submits a specific image attachment to NCMEC, creates an audit log entry, silently disables the user, triggers one archive for the user, and schedules content deletion after the archive completes.',
|
||||
responseSchema: NcmecAttachmentSubmitResultResponse,
|
||||
@@ -107,16 +121,96 @@ export function MessageAdminController(app: HonoApp) {
|
||||
return ctx.json(result);
|
||||
},
|
||||
);
|
||||
app.post(
|
||||
'/admin/messages/delete',
|
||||
app.get(
|
||||
'/admin/messages/shreds/:job_id',
|
||||
RateLimitMiddleware(RateLimitConfigs.ADMIN_MESSAGE_OPERATION),
|
||||
requireAdminACL(AdminACLs.MESSAGE_SHRED),
|
||||
Validator('param', MessageShredJobIdParam),
|
||||
OpenAPI({
|
||||
operationId: 'get_admin_message_shred',
|
||||
summary: 'Get message shred job',
|
||||
description:
|
||||
'Returns the progress of a queued message shred job, including whether it is complete. Requires MESSAGE_SHRED permission.',
|
||||
responseSchema: MessageShredStatusResponse,
|
||||
statusCode: 200,
|
||||
security: 'adminApiKey',
|
||||
tags: 'Admin',
|
||||
}),
|
||||
async (ctx) => {
|
||||
const adminService = ctx.get('adminService');
|
||||
const {job_id} = ctx.req.valid('param');
|
||||
return ctx.json(await adminService.messageShredService.getMessageShredStatus(job_id.toString()));
|
||||
},
|
||||
);
|
||||
app.get(
|
||||
'/admin/channels/:channel_id/messages',
|
||||
RateLimitMiddleware(RateLimitConfigs.ADMIN_MESSAGE_OPERATION),
|
||||
requireAdminACL(AdminACLs.MESSAGE_LOOKUP),
|
||||
Validator('param', ChannelIdParam),
|
||||
Validator('query', AdminChannelMessageListQuery),
|
||||
OpenAPI({
|
||||
operationId: 'list_admin_channel_messages',
|
||||
summary: 'List channel messages',
|
||||
description:
|
||||
'Pages through the messages of a channel, newest first, with cursor-based pagination. Requires MESSAGE_LOOKUP permission.',
|
||||
responseSchema: BrowseChannelResponse,
|
||||
statusCode: 200,
|
||||
security: 'adminApiKey',
|
||||
tags: 'Admin',
|
||||
}),
|
||||
async (ctx) => {
|
||||
const adminService = ctx.get('adminService');
|
||||
const {channel_id} = ctx.req.valid('param');
|
||||
const {limit, before, after} = ctx.req.valid('query');
|
||||
return ctx.json(
|
||||
await adminService.messageService.browseChannel({
|
||||
channel_id,
|
||||
before,
|
||||
after,
|
||||
limit,
|
||||
}),
|
||||
);
|
||||
},
|
||||
);
|
||||
app.get(
|
||||
'/admin/channels/:channel_id/messages/:message_id',
|
||||
RateLimitMiddleware(RateLimitConfigs.ADMIN_MESSAGE_OPERATION),
|
||||
requireAdminACL(AdminACLs.MESSAGE_LOOKUP),
|
||||
Validator('param', ChannelIdMessageIdParam),
|
||||
Validator('query', AdminMessageDetailQuery),
|
||||
OpenAPI({
|
||||
operationId: 'get_admin_message',
|
||||
summary: 'Get message',
|
||||
description:
|
||||
'Retrieves complete message details including content, attachments, edits, and metadata, together with the messages surrounding it. Requires MESSAGE_LOOKUP permission.',
|
||||
responseSchema: LookupMessageResponse,
|
||||
statusCode: 200,
|
||||
security: 'adminApiKey',
|
||||
tags: 'Admin',
|
||||
}),
|
||||
async (ctx) => {
|
||||
const adminService = ctx.get('adminService');
|
||||
const {channel_id, message_id} = ctx.req.valid('param');
|
||||
const {context_limit} = ctx.req.valid('query');
|
||||
return ctx.json(
|
||||
await adminService.messageService.lookupMessage({
|
||||
channel_id,
|
||||
message_id,
|
||||
context_limit,
|
||||
}),
|
||||
);
|
||||
},
|
||||
);
|
||||
app.delete(
|
||||
'/admin/channels/:channel_id/messages/:message_id',
|
||||
RateLimitMiddleware(RateLimitConfigs.ADMIN_MESSAGE_OPERATION),
|
||||
requireAdminACL(AdminACLs.MESSAGE_DELETE),
|
||||
Validator('json', DeleteMessageRequest),
|
||||
Validator('param', ChannelIdMessageIdParam),
|
||||
OpenAPI({
|
||||
operationId: 'admin_delete_message',
|
||||
summary: 'Delete single message',
|
||||
operationId: 'delete_admin_message',
|
||||
summary: 'Delete message',
|
||||
description:
|
||||
'Deletes a single message permanently. Used for removing inappropriate or harmful content. Logged to audit log. Requires MESSAGE_DELETE permission.',
|
||||
'Deletes a single message permanently and purges its attachments. Used for removing inappropriate or harmful content. Logged to audit log. Requires MESSAGE_DELETE permission.',
|
||||
responseSchema: DeleteMessageResponse,
|
||||
statusCode: 200,
|
||||
security: 'adminApiKey',
|
||||
@@ -126,21 +220,23 @@ export function MessageAdminController(app: HonoApp) {
|
||||
const adminService = ctx.get('adminService');
|
||||
const adminUserId = ctx.get('adminUserId');
|
||||
const auditLogReason = ctx.get('auditLogReason');
|
||||
const {channel_id, message_id} = ctx.req.valid('param');
|
||||
return ctx.json(
|
||||
await adminService.messageService.deleteMessage(ctx.req.valid('json'), adminUserId, auditLogReason),
|
||||
await adminService.messageService.deleteMessage({channel_id, message_id}, adminUserId, auditLogReason),
|
||||
);
|
||||
},
|
||||
);
|
||||
app.post(
|
||||
'/admin/messages/shred',
|
||||
'/admin/users/:user_id/message-shreds',
|
||||
RateLimitMiddleware(RateLimitConfigs.ADMIN_MESSAGE_OPERATION),
|
||||
requireAdminACL(AdminACLs.MESSAGE_SHRED),
|
||||
Validator('json', MessageShredRequest),
|
||||
Validator('param', UserIdParam),
|
||||
Validator('json', AdminUserMessageShredRequest),
|
||||
OpenAPI({
|
||||
operationId: 'queue_message_shred',
|
||||
summary: 'Queue message shred operation',
|
||||
operationId: 'shred_admin_user_messages',
|
||||
summary: 'Shred user messages',
|
||||
description:
|
||||
'Queues bulk message shredding with attachment deletion. Returns job ID to track progress asynchronously. Used for large-scale content removal. Requires MESSAGE_SHRED permission.',
|
||||
'Queues bulk shredding of the given messages of a user, with attachment deletion. Returns a job ID to track progress asynchronously. Used for large-scale content removal. Requires MESSAGE_SHRED permission.',
|
||||
responseSchema: MessageShredResponse,
|
||||
statusCode: 200,
|
||||
security: 'adminApiKey',
|
||||
@@ -150,21 +246,24 @@ export function MessageAdminController(app: HonoApp) {
|
||||
const adminService = ctx.get('adminService');
|
||||
const adminUserId = ctx.get('adminUserId');
|
||||
const auditLogReason = ctx.get('auditLogReason');
|
||||
const {user_id} = ctx.req.valid('param');
|
||||
const {entries} = ctx.req.valid('json');
|
||||
return ctx.json(
|
||||
await adminService.messageShredService.queueMessageShred(ctx.req.valid('json'), adminUserId, auditLogReason),
|
||||
await adminService.messageShredService.queueMessageShred({user_id, entries}, adminUserId, auditLogReason),
|
||||
);
|
||||
},
|
||||
);
|
||||
app.post(
|
||||
'/admin/messages/delete-all',
|
||||
app.delete(
|
||||
'/admin/users/:user_id/messages',
|
||||
RateLimitMiddleware(RateLimitConfigs.ADMIN_MESSAGE_OPERATION),
|
||||
requireAdminACL(AdminACLs.MESSAGE_DELETE_ALL),
|
||||
Validator('json', DeleteAllUserMessagesRequest),
|
||||
Validator('param', UserIdParam),
|
||||
Validator('query', AdminUserMessageDeleteQuery),
|
||||
OpenAPI({
|
||||
operationId: 'delete_all_user_messages',
|
||||
operationId: 'delete_admin_user_messages',
|
||||
summary: 'Delete all user messages',
|
||||
description:
|
||||
'Deletes all messages from a specific user across all channels. Permanent operation used for account suspension or policy violation. Requires MESSAGE_DELETE_ALL permission.',
|
||||
'Deletes all messages from a specific user across all channels. Permanent operation used for account suspension or policy violation. Pass dry_run=false to delete; the default counts without deleting. Requires MESSAGE_DELETE_ALL permission.',
|
||||
responseSchema: DeleteAllUserMessagesResponse,
|
||||
statusCode: 200,
|
||||
security: 'adminApiKey',
|
||||
@@ -174,73 +273,15 @@ export function MessageAdminController(app: HonoApp) {
|
||||
const adminService = ctx.get('adminService');
|
||||
const adminUserId = ctx.get('adminUserId');
|
||||
const auditLogReason = ctx.get('auditLogReason');
|
||||
const {user_id} = ctx.req.valid('param');
|
||||
const {dry_run} = ctx.req.valid('query');
|
||||
return ctx.json(
|
||||
await adminService.messageDeletionService.deleteAllUserMessages(
|
||||
ctx.req.valid('json'),
|
||||
{user_id, dry_run},
|
||||
adminUserId,
|
||||
auditLogReason,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
app.post(
|
||||
'/admin/messages/shred-status',
|
||||
RateLimitMiddleware(RateLimitConfigs.ADMIN_MESSAGE_OPERATION),
|
||||
requireAdminACL(AdminACLs.MESSAGE_SHRED),
|
||||
Validator('json', MessageShredStatusRequest),
|
||||
OpenAPI({
|
||||
operationId: 'get_message_shred_status',
|
||||
summary: 'Get message shred status',
|
||||
description:
|
||||
'Polls status of a queued message shred operation. Returns progress percentage and whether the job is complete. Requires MESSAGE_SHRED permission.',
|
||||
responseSchema: MessageShredStatusResponse,
|
||||
statusCode: 200,
|
||||
security: 'adminApiKey',
|
||||
tags: 'Admin',
|
||||
}),
|
||||
async (ctx) => {
|
||||
const adminService = ctx.get('adminService');
|
||||
const body = ctx.req.valid('json');
|
||||
return ctx.json(await adminService.messageShredService.getMessageShredStatus(body.job_id));
|
||||
},
|
||||
);
|
||||
app.post(
|
||||
'/admin/messages/browse',
|
||||
RateLimitMiddleware(RateLimitConfigs.ADMIN_MESSAGE_OPERATION),
|
||||
requireAdminACL(AdminACLs.MESSAGE_LOOKUP),
|
||||
Validator('json', BrowseChannelRequest),
|
||||
OpenAPI({
|
||||
operationId: 'browse_channel_messages',
|
||||
summary: 'Browse channel messages',
|
||||
description:
|
||||
'Browses messages in a channel with cursor-based pagination. Returns messages in reverse chronological order. Requires MESSAGE_LOOKUP permission.',
|
||||
responseSchema: BrowseChannelResponse,
|
||||
statusCode: 200,
|
||||
security: 'adminApiKey',
|
||||
tags: 'Admin',
|
||||
}),
|
||||
async (ctx) => {
|
||||
const adminService = ctx.get('adminService');
|
||||
return ctx.json(await adminService.messageService.browseChannel(ctx.req.valid('json')));
|
||||
},
|
||||
);
|
||||
app.post(
|
||||
'/admin/messages/search',
|
||||
RateLimitMiddleware(RateLimitConfigs.ADMIN_MESSAGE_OPERATION),
|
||||
requireAdminACL(AdminACLs.MESSAGE_LOOKUP),
|
||||
Validator('json', SearchChannelMessagesRequest),
|
||||
OpenAPI({
|
||||
operationId: 'search_channel_messages',
|
||||
summary: 'Search channel messages',
|
||||
description: 'Searches messages within a channel by content. Requires MESSAGE_LOOKUP permission.',
|
||||
responseSchema: SearchChannelMessagesResponse,
|
||||
statusCode: 200,
|
||||
security: 'adminApiKey',
|
||||
tags: 'Admin',
|
||||
}),
|
||||
async (ctx) => {
|
||||
const adminService = ctx.get('adminService');
|
||||
return ctx.json(await adminService.messageService.searchChannelMessages(ctx.req.valid('json')));
|
||||
},
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user