Files
fluxer/packages/api/src/admin/controllers/GuildAdminController.tsx
2026-02-17 12:22:36 +00:00

442 lines
15 KiB
TypeScript

/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {createGuildID} from '@fluxer/api/src/BrandedTypes';
import {requireAdminACL} from '@fluxer/api/src/middleware/AdminMiddleware';
import {RateLimitMiddleware} from '@fluxer/api/src/middleware/RateLimitMiddleware';
import {OpenAPI} from '@fluxer/api/src/middleware/ResponseTypeMiddleware';
import {RateLimitConfigs} from '@fluxer/api/src/RateLimitConfig';
import {AdminRateLimitConfigs} from '@fluxer/api/src/rate_limit_configs/AdminRateLimitConfig';
import type {HonoApp} from '@fluxer/api/src/types/HonoEnv';
import {Validator} from '@fluxer/api/src/Validator';
import {AdminACLs} from '@fluxer/constants/src/AdminACLs';
import {
BanGuildMemberRequest,
ClearGuildFieldsRequest,
DeleteGuildRequest,
ForceAddUserToGuildRequest,
KickGuildMemberRequest,
ListGuildMembersRequest,
LookupGuildRequest,
ReloadGuildRequest,
ShutdownGuildRequest,
TransferGuildOwnershipRequest,
UpdateGuildFeaturesRequest,
UpdateGuildNameRequest,
UpdateGuildSettingsRequest,
UpdateGuildVanityRequest,
} from '@fluxer/schema/src/domains/admin/AdminGuildSchemas';
import {
GuildUpdateResponse,
ListGuildEmojisResponse,
ListGuildMembersResponse,
ListGuildStickersResponse,
LookupGuildResponse,
SuccessResponse,
} from '@fluxer/schema/src/domains/admin/AdminSchemas';
import {GuildIdParam} from '@fluxer/schema/src/domains/common/CommonParamSchemas';
export function GuildAdminController(app: HonoApp) {
app.post(
'/admin/guilds/lookup',
RateLimitMiddleware(RateLimitConfigs.ADMIN_LOOKUP),
requireAdminACL(AdminACLs.GUILD_LOOKUP),
Validator('json', LookupGuildRequest),
OpenAPI({
operationId: 'lookup_guild',
summary: 'Look up guild',
description:
'Retrieves complete guild details including metadata, settings, and statistics. Look up by guild ID or vanity slug. Requires GUILD_LOOKUP permission.',
responseSchema: LookupGuildResponse,
statusCode: 200,
security: 'adminApiKey',
tags: 'Admin',
}),
async (ctx) => {
const adminService = ctx.get('adminService');
return ctx.json(await adminService.lookupGuild(ctx.req.valid('json')));
},
);
app.post(
'/admin/guilds/list-members',
RateLimitMiddleware(RateLimitConfigs.ADMIN_LOOKUP),
requireAdminACL(AdminACLs.GUILD_LIST_MEMBERS),
Validator('json', ListGuildMembersRequest),
OpenAPI({
operationId: 'list_guild_members',
summary: 'List guild members',
description:
'Lists all guild members with pagination. Returns member IDs, join dates, and roles. Requires GUILD_LIST_MEMBERS permission.',
responseSchema: ListGuildMembersResponse,
statusCode: 200,
security: 'adminApiKey',
tags: 'Admin',
}),
async (ctx) => {
const adminService = ctx.get('adminService');
return ctx.json(await adminService.listGuildMembers(ctx.req.valid('json')));
},
);
app.get(
'/admin/guilds/:guild_id/emojis',
RateLimitMiddleware(AdminRateLimitConfigs.ADMIN_LOOKUP),
requireAdminACL(AdminACLs.ASSET_PURGE),
Validator('param', GuildIdParam),
OpenAPI({
operationId: 'list_guild_emojis',
summary: 'List guild emojis',
description:
'Lists all custom emojis in a guild. Returns ID, name, and creation date. Used for asset inventory and purge operations. Requires ASSET_PURGE permission.',
responseSchema: ListGuildEmojisResponse,
statusCode: 200,
security: 'adminApiKey',
tags: 'Admin',
}),
async (ctx) => {
const adminService = ctx.get('adminService');
const guildId = createGuildID(ctx.req.valid('param').guild_id);
return ctx.json(await adminService.listGuildEmojis(guildId));
},
);
app.get(
'/admin/guilds/:guild_id/stickers',
RateLimitMiddleware(AdminRateLimitConfigs.ADMIN_LOOKUP),
requireAdminACL(AdminACLs.ASSET_PURGE),
Validator('param', GuildIdParam),
OpenAPI({
operationId: 'list_guild_stickers',
summary: 'List guild stickers',
description:
'Lists all stickers in a guild. Returns ID, name, and asset information. Used for asset inventory and purge operations. Requires ASSET_PURGE permission.',
responseSchema: ListGuildStickersResponse,
statusCode: 200,
security: 'adminApiKey',
tags: 'Admin',
}),
async (ctx) => {
const adminService = ctx.get('adminService');
const guildId = createGuildID(ctx.req.valid('param').guild_id);
return ctx.json(await adminService.listGuildStickers(guildId));
},
);
app.post(
'/admin/guilds/clear-fields',
RateLimitMiddleware(RateLimitConfigs.ADMIN_GUILD_MODIFY),
requireAdminACL(AdminACLs.GUILD_UPDATE_SETTINGS),
Validator('json', ClearGuildFieldsRequest),
OpenAPI({
operationId: 'clear_guild_fields',
summary: 'Clear guild fields',
description:
'Clears specified optional guild fields such as icon, banner, or description. Logged to audit log. Requires GUILD_UPDATE_SETTINGS permission.',
responseSchema: null,
statusCode: 204,
security: 'adminApiKey',
tags: 'Admin',
}),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
await adminService.clearGuildFields(ctx.req.valid('json'), adminUserId, auditLogReason);
return ctx.body(null, 204);
},
);
app.post(
'/admin/guilds/update-features',
RateLimitMiddleware(RateLimitConfigs.ADMIN_GUILD_MODIFY),
requireAdminACL(AdminACLs.GUILD_UPDATE_FEATURES),
Validator('json', UpdateGuildFeaturesRequest),
OpenAPI({
operationId: 'update_guild_features',
summary: 'Update guild features',
description:
'Enables or disables guild feature flags. Modifies verification levels and community settings. Changes are logged to audit log. Requires GUILD_UPDATE_FEATURES permission.',
responseSchema: GuildUpdateResponse,
statusCode: 200,
security: 'adminApiKey',
tags: 'Admin',
}),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
const body = ctx.req.valid('json');
const guildId = createGuildID(body.guild_id);
return ctx.json(
await adminService.updateGuildFeatures({
guildId,
addFeatures: body.add_features,
removeFeatures: body.remove_features,
adminUserId,
auditLogReason,
}),
);
},
);
app.post(
'/admin/guilds/update-name',
RateLimitMiddleware(RateLimitConfigs.ADMIN_GUILD_MODIFY),
requireAdminACL(AdminACLs.GUILD_UPDATE_NAME),
Validator('json', UpdateGuildNameRequest),
OpenAPI({
operationId: 'update_guild_name',
summary: 'Update guild name',
description:
'Changes a guild name. Used for removing inappropriate names or correcting display issues. Logged to audit log. Requires GUILD_UPDATE_NAME permission.',
responseSchema: GuildUpdateResponse,
statusCode: 200,
security: 'adminApiKey',
tags: 'Admin',
}),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(await adminService.updateGuildName(ctx.req.valid('json'), adminUserId, auditLogReason));
},
);
app.post(
'/admin/guilds/update-settings',
RateLimitMiddleware(RateLimitConfigs.ADMIN_GUILD_MODIFY),
requireAdminACL(AdminACLs.GUILD_UPDATE_SETTINGS),
Validator('json', UpdateGuildSettingsRequest),
OpenAPI({
operationId: 'update_guild_settings',
summary: 'Update guild settings',
description:
'Modifies guild configuration including description, region, language and other settings. Logged to audit log. Requires GUILD_UPDATE_SETTINGS permission.',
responseSchema: GuildUpdateResponse,
statusCode: 200,
security: 'adminApiKey',
tags: 'Admin',
}),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(await adminService.updateGuildSettings(ctx.req.valid('json'), adminUserId, auditLogReason));
},
);
app.post(
'/admin/guilds/transfer-ownership',
RateLimitMiddleware(RateLimitConfigs.ADMIN_GUILD_MODIFY),
requireAdminACL(AdminACLs.GUILD_TRANSFER_OWNERSHIP),
Validator('json', TransferGuildOwnershipRequest),
OpenAPI({
operationId: 'transfer_guild_ownership',
summary: 'Transfer guild ownership',
description:
'Transfers guild ownership to another user. Used when owner is inactive or for administrative recovery. Logged to audit log. Requires GUILD_TRANSFER_OWNERSHIP permission.',
responseSchema: GuildUpdateResponse,
statusCode: 200,
security: 'adminApiKey',
tags: 'Admin',
}),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(await adminService.transferGuildOwnership(ctx.req.valid('json'), adminUserId, auditLogReason));
},
);
app.post(
'/admin/guilds/update-vanity',
RateLimitMiddleware(RateLimitConfigs.ADMIN_GUILD_MODIFY),
requireAdminACL(AdminACLs.GUILD_UPDATE_VANITY),
Validator('json', UpdateGuildVanityRequest),
OpenAPI({
operationId: 'update_guild_vanity',
summary: 'Update guild vanity',
description:
'Updates a guild vanity URL slug. Sets custom short URL and prevents duplicate slugs. Logged to audit log. Requires GUILD_UPDATE_VANITY permission.',
responseSchema: GuildUpdateResponse,
statusCode: 200,
security: 'adminApiKey',
tags: 'Admin',
}),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(await adminService.updateGuildVanity(ctx.req.valid('json'), adminUserId, auditLogReason));
},
);
app.post(
'/admin/guilds/force-add-user',
RateLimitMiddleware(RateLimitConfigs.ADMIN_GUILD_MODIFY),
requireAdminACL(AdminACLs.GUILD_FORCE_ADD_MEMBER),
Validator('json', ForceAddUserToGuildRequest),
OpenAPI({
operationId: 'force_add_user_to_guild',
summary: 'Force add user to guild',
description:
'Forcefully adds a user to a guild. Bypasses normal invite flow for administrative account recovery. Logged to audit log. Requires GUILD_FORCE_ADD_MEMBER permission.',
responseSchema: SuccessResponse,
statusCode: 200,
security: 'adminApiKey',
tags: 'Admin',
}),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
const requestCache = ctx.get('requestCache');
return ctx.json(
await adminService.forceAddUserToGuild({
data: ctx.req.valid('json'),
requestCache,
adminUserId,
auditLogReason,
}),
);
},
);
app.post(
'/admin/guilds/ban-member',
RateLimitMiddleware(RateLimitConfigs.ADMIN_GUILD_MODIFY),
requireAdminACL(AdminACLs.GUILD_BAN_MEMBER),
Validator('json', BanGuildMemberRequest),
OpenAPI({
operationId: 'ban_guild_member',
summary: 'Ban guild member',
description:
'Permanently bans a user from a guild. Prevents user from joining. Logged to audit log. Requires GUILD_BAN_MEMBER permission.',
responseSchema: null,
statusCode: 204,
security: 'adminApiKey',
tags: 'Admin',
}),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
await adminService.banGuildMember(ctx.req.valid('json'), adminUserId, auditLogReason);
return ctx.body(null, 204);
},
);
app.post(
'/admin/guilds/kick-member',
RateLimitMiddleware(RateLimitConfigs.ADMIN_GUILD_MODIFY),
requireAdminACL(AdminACLs.GUILD_KICK_MEMBER),
Validator('json', KickGuildMemberRequest),
OpenAPI({
operationId: 'kick_guild_member',
summary: 'Kick guild member',
description:
'Temporarily removes a user from a guild. User can rejoin. Logged to audit log. Requires GUILD_KICK_MEMBER permission.',
responseSchema: null,
statusCode: 204,
security: 'adminApiKey',
tags: 'Admin',
}),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
await adminService.kickGuildMember(ctx.req.valid('json'), adminUserId, auditLogReason);
return ctx.body(null, 204);
},
);
app.post(
'/admin/guilds/reload',
RateLimitMiddleware(RateLimitConfigs.ADMIN_GUILD_MODIFY),
requireAdminACL(AdminACLs.GUILD_RELOAD),
Validator('json', ReloadGuildRequest),
OpenAPI({
operationId: 'reload_guild',
summary: 'Reload guild',
description:
'Reloads a single guild state from database. Used to recover from corruption or sync issues. Logged to audit log. Requires GUILD_RELOAD permission.',
responseSchema: SuccessResponse,
statusCode: 200,
security: 'adminApiKey',
tags: 'Admin',
}),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
const body = ctx.req.valid('json');
return ctx.json(await adminService.reloadGuild(body.guild_id, adminUserId, auditLogReason));
},
);
app.post(
'/admin/guilds/shutdown',
RateLimitMiddleware(RateLimitConfigs.ADMIN_GUILD_MODIFY),
requireAdminACL(AdminACLs.GUILD_SHUTDOWN),
Validator('json', ShutdownGuildRequest),
OpenAPI({
operationId: 'shutdown_guild',
summary: 'Shutdown guild',
description:
'Shuts down and unloads a guild from the gateway. Guild data remains in database. Used for emergency resource cleanup. Logged to audit log. Requires GUILD_SHUTDOWN permission.',
responseSchema: SuccessResponse,
statusCode: 200,
security: 'adminApiKey',
tags: 'Admin',
}),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
const body = ctx.req.valid('json');
return ctx.json(await adminService.shutdownGuild(body.guild_id, adminUserId, auditLogReason));
},
);
app.post(
'/admin/guilds/delete',
RateLimitMiddleware(RateLimitConfigs.ADMIN_GUILD_MODIFY),
requireAdminACL(AdminACLs.GUILD_DELETE),
Validator('json', DeleteGuildRequest),
OpenAPI({
operationId: 'delete_guild',
summary: 'Delete guild',
description:
'Permanently deletes a guild. Deletes all channels, messages, and settings. Irreversible operation. Logged to audit log. Requires GUILD_DELETE permission.',
responseSchema: SuccessResponse,
statusCode: 200,
security: 'adminApiKey',
tags: 'Admin',
}),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
const body = ctx.req.valid('json');
return ctx.json(await adminService.deleteGuild(body.guild_id, adminUserId, auditLogReason));
},
);
}