initial commit

This commit is contained in:
Hampus Kraft
2026-01-01 20:42:59 +00:00
commit 2f557eda8c
9029 changed files with 1490197 additions and 0 deletions

View File

@@ -0,0 +1,155 @@
/*
* 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 type {HonoApp} from '~/App';
import {createGuildID, createUserID} from '~/BrandedTypes';
import {AdminACLs} from '~/Constants';
import {MissingACLError} from '~/Errors';
import {requireAdminACL, requireAnyAdminACL} from '~/middleware/AdminMiddleware';
import {RateLimitMiddleware} from '~/middleware/RateLimitMiddleware';
import {RateLimitConfigs} from '~/RateLimitConfig';
import {Validator} from '~/Validator';
import type {ListArchivesRequest} from '../models';
import {
ListArchivesRequest as ListArchivesSchema,
TriggerGuildArchiveRequest,
TriggerUserArchiveRequest,
} from '../models';
const canViewArchive = (adminAcls: Set<string>, subjectType: 'user' | 'guild'): boolean => {
if (adminAcls.has(AdminACLs.WILDCARD) || adminAcls.has(AdminACLs.ARCHIVE_VIEW_ALL)) return true;
if (subjectType === 'user') return adminAcls.has(AdminACLs.ARCHIVE_TRIGGER_USER);
return adminAcls.has(AdminACLs.ARCHIVE_TRIGGER_GUILD);
};
export const ArchiveAdminController = (app: HonoApp) => {
app.post(
'/admin/archives/user',
RateLimitMiddleware(RateLimitConfigs.ADMIN_LOOKUP),
requireAdminACL(AdminACLs.ARCHIVE_TRIGGER_USER),
Validator('json', TriggerUserArchiveRequest),
async (ctx) => {
const adminArchiveService = ctx.get('adminArchiveService');
const adminUserId = ctx.get('adminUserId');
const body = ctx.req.valid('json');
const result = await adminArchiveService.triggerUserArchive(createUserID(BigInt(body.user_id)), adminUserId);
return ctx.json(result, 200);
},
);
app.post(
'/admin/archives/guild',
RateLimitMiddleware(RateLimitConfigs.ADMIN_LOOKUP),
requireAdminACL(AdminACLs.ARCHIVE_TRIGGER_GUILD),
Validator('json', TriggerGuildArchiveRequest),
async (ctx) => {
const adminArchiveService = ctx.get('adminArchiveService');
const adminUserId = ctx.get('adminUserId');
const body = ctx.req.valid('json');
const result = await adminArchiveService.triggerGuildArchive(createGuildID(BigInt(body.guild_id)), adminUserId);
return ctx.json(result, 200);
},
);
app.post(
'/admin/archives/list',
RateLimitMiddleware(RateLimitConfigs.ADMIN_LOOKUP),
requireAnyAdminACL([AdminACLs.ARCHIVE_VIEW_ALL, AdminACLs.ARCHIVE_TRIGGER_USER, AdminACLs.ARCHIVE_TRIGGER_GUILD]),
Validator('json', ListArchivesSchema),
async (ctx) => {
const adminArchiveService = ctx.get('adminArchiveService');
const adminAcls = ctx.get('adminUserAcls');
const body = ctx.req.valid('json') as ListArchivesRequest;
if (
body.subject_type === 'all' &&
!adminAcls.has(AdminACLs.ARCHIVE_VIEW_ALL) &&
!adminAcls.has(AdminACLs.WILDCARD)
) {
throw new MissingACLError(AdminACLs.ARCHIVE_VIEW_ALL);
}
if (
body.subject_type !== 'all' &&
!canViewArchive(adminAcls, body.subject_type) &&
!adminAcls.has(AdminACLs.WILDCARD)
) {
throw new MissingACLError(
body.subject_type === 'user' ? AdminACLs.ARCHIVE_TRIGGER_USER : AdminACLs.ARCHIVE_TRIGGER_GUILD,
);
}
const result = await adminArchiveService.listArchives({
subjectType: body.subject_type as 'user' | 'guild' | 'all',
subjectId: body.subject_id ? BigInt(body.subject_id) : undefined,
requestedBy: body.requested_by ? BigInt(body.requested_by) : undefined,
limit: body.limit,
includeExpired: body.include_expired,
});
return ctx.json({archives: result}, 200);
},
);
app.get(
'/admin/archives/:subjectType/:subjectId/:archiveId',
RateLimitMiddleware(RateLimitConfigs.ADMIN_LOOKUP),
requireAnyAdminACL([AdminACLs.ARCHIVE_VIEW_ALL, AdminACLs.ARCHIVE_TRIGGER_USER, AdminACLs.ARCHIVE_TRIGGER_GUILD]),
async (ctx) => {
const adminArchiveService = ctx.get('adminArchiveService');
const adminAcls = ctx.get('adminUserAcls');
const subjectType = ctx.req.param('subjectType') as 'user' | 'guild';
if (!canViewArchive(adminAcls, subjectType) && !adminAcls.has(AdminACLs.WILDCARD)) {
throw new MissingACLError(
subjectType === 'user' ? AdminACLs.ARCHIVE_TRIGGER_USER : AdminACLs.ARCHIVE_TRIGGER_GUILD,
);
}
const subjectId = BigInt(ctx.req.param('subjectId'));
const archiveId = BigInt(ctx.req.param('archiveId'));
const archive = await adminArchiveService.getArchive(subjectType, subjectId, archiveId);
return ctx.json({archive}, 200);
},
);
app.get(
'/admin/archives/:subjectType/:subjectId/:archiveId/download',
RateLimitMiddleware(RateLimitConfigs.ADMIN_LOOKUP),
requireAnyAdminACL([AdminACLs.ARCHIVE_VIEW_ALL, AdminACLs.ARCHIVE_TRIGGER_USER, AdminACLs.ARCHIVE_TRIGGER_GUILD]),
async (ctx) => {
const adminArchiveService = ctx.get('adminArchiveService');
const adminAcls = ctx.get('adminUserAcls');
const subjectType = ctx.req.param('subjectType') as 'user' | 'guild';
if (!canViewArchive(adminAcls, subjectType) && !adminAcls.has(AdminACLs.WILDCARD)) {
throw new MissingACLError(
subjectType === 'user' ? AdminACLs.ARCHIVE_TRIGGER_USER : AdminACLs.ARCHIVE_TRIGGER_GUILD,
);
}
const subjectId = BigInt(ctx.req.param('subjectId'));
const archiveId = BigInt(ctx.req.param('archiveId'));
const result = await adminArchiveService.getDownloadUrl(subjectType, subjectId, archiveId);
return ctx.json(result, 200);
},
);
};

View File

@@ -0,0 +1,41 @@
/*
* 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 type {HonoApp} from '~/App';
import {AdminACLs} from '~/Constants';
import {requireAdminACL} from '~/middleware/AdminMiddleware';
import {RateLimitMiddleware} from '~/middleware/RateLimitMiddleware';
import {AdminRateLimitConfigs} from '~/rate_limit_configs/AdminRateLimitConfig';
import {Validator} from '~/Validator';
import {PurgeGuildAssetsRequest} from '../AdminModel';
export const AssetAdminController = (app: HonoApp) => {
app.post(
'/admin/assets/purge',
RateLimitMiddleware(AdminRateLimitConfigs.ADMIN_GUILD_MODIFY),
requireAdminACL(AdminACLs.ASSET_PURGE),
Validator('json', PurgeGuildAssetsRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(await adminService.purgeGuildAssets(ctx.req.valid('json'), adminUserId, auditLogReason));
},
);
};

View File

@@ -0,0 +1,50 @@
/*
* 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 type {HonoApp} from '~/App';
import {AdminACLs} from '~/Constants';
import {requireAdminACL} from '~/middleware/AdminMiddleware';
import {RateLimitMiddleware} from '~/middleware/RateLimitMiddleware';
import {RateLimitConfigs} from '~/RateLimitConfig';
import {Validator} from '~/Validator';
import {ListAuditLogsRequest, SearchAuditLogsRequest} from '../AdminModel';
export const AuditLogAdminController = (app: HonoApp) => {
app.post(
'/admin/audit-logs',
RateLimitMiddleware(RateLimitConfigs.ADMIN_AUDIT_LOG),
requireAdminACL(AdminACLs.AUDIT_LOG_VIEW),
Validator('json', ListAuditLogsRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
return ctx.json(await adminService.listAuditLogs(ctx.req.valid('json')));
},
);
app.post(
'/admin/audit-logs/search',
RateLimitMiddleware(RateLimitConfigs.ADMIN_AUDIT_LOG),
requireAdminACL(AdminACLs.AUDIT_LOG_VIEW),
Validator('json', SearchAuditLogsRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
return ctx.json(await adminService.searchAuditLogs(ctx.req.valid('json')));
},
);
};

View File

@@ -0,0 +1,139 @@
/*
* 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 type {HonoApp} from '~/App';
import {AdminACLs} from '~/Constants';
import {requireAdminACL} from '~/middleware/AdminMiddleware';
import {RateLimitMiddleware} from '~/middleware/RateLimitMiddleware';
import {RateLimitConfigs} from '~/RateLimitConfig';
import {Validator} from '~/Validator';
import {BanEmailRequest, BanIpRequest, BanPhoneRequest} from '../AdminModel';
export const BanAdminController = (app: HonoApp) => {
app.post(
'/admin/bans/ip/add',
RateLimitMiddleware(RateLimitConfigs.ADMIN_BAN_OPERATION),
requireAdminACL(AdminACLs.BAN_IP_ADD),
Validator('json', BanIpRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(await adminService.banIp(ctx.req.valid('json'), adminUserId, auditLogReason));
},
);
app.post(
'/admin/bans/ip/remove',
RateLimitMiddleware(RateLimitConfigs.ADMIN_BAN_OPERATION),
requireAdminACL(AdminACLs.BAN_IP_REMOVE),
Validator('json', BanIpRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(await adminService.unbanIp(ctx.req.valid('json'), adminUserId, auditLogReason));
},
);
app.post(
'/admin/bans/ip/check',
RateLimitMiddleware(RateLimitConfigs.ADMIN_BAN_OPERATION),
requireAdminACL(AdminACLs.BAN_IP_CHECK),
Validator('json', BanIpRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
return ctx.json(await adminService.checkIpBan(ctx.req.valid('json')));
},
);
app.post(
'/admin/bans/email/add',
RateLimitMiddleware(RateLimitConfigs.ADMIN_BAN_OPERATION),
requireAdminACL(AdminACLs.BAN_EMAIL_ADD),
Validator('json', BanEmailRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(await adminService.banEmail(ctx.req.valid('json'), adminUserId, auditLogReason));
},
);
app.post(
'/admin/bans/email/remove',
RateLimitMiddleware(RateLimitConfigs.ADMIN_BAN_OPERATION),
requireAdminACL(AdminACLs.BAN_EMAIL_REMOVE),
Validator('json', BanEmailRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(await adminService.unbanEmail(ctx.req.valid('json'), adminUserId, auditLogReason));
},
);
app.post(
'/admin/bans/email/check',
RateLimitMiddleware(RateLimitConfigs.ADMIN_BAN_OPERATION),
requireAdminACL(AdminACLs.BAN_EMAIL_CHECK),
Validator('json', BanEmailRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
return ctx.json(await adminService.checkEmailBan(ctx.req.valid('json')));
},
);
app.post(
'/admin/bans/phone/add',
RateLimitMiddleware(RateLimitConfigs.ADMIN_BAN_OPERATION),
requireAdminACL(AdminACLs.BAN_PHONE_ADD),
Validator('json', BanPhoneRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(await adminService.banPhone(ctx.req.valid('json'), adminUserId, auditLogReason));
},
);
app.post(
'/admin/bans/phone/remove',
RateLimitMiddleware(RateLimitConfigs.ADMIN_BAN_OPERATION),
requireAdminACL(AdminACLs.BAN_PHONE_REMOVE),
Validator('json', BanPhoneRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(await adminService.unbanPhone(ctx.req.valid('json'), adminUserId, auditLogReason));
},
);
app.post(
'/admin/bans/phone/check',
RateLimitMiddleware(RateLimitConfigs.ADMIN_BAN_OPERATION),
requireAdminACL(AdminACLs.BAN_PHONE_CHECK),
Validator('json', BanPhoneRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
return ctx.json(await adminService.checkPhoneBan(ctx.req.valid('json')));
},
);
};

View File

@@ -0,0 +1,85 @@
/*
* 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 type {HonoApp} from '~/App';
import {AdminACLs} from '~/Constants';
import {requireAdminACL} from '~/middleware/AdminMiddleware';
import {RateLimitMiddleware} from '~/middleware/RateLimitMiddleware';
import {RateLimitConfigs} from '~/RateLimitConfig';
import {Validator} from '~/Validator';
import {
BulkAddGuildMembersRequest,
BulkScheduleUserDeletionRequest,
BulkUpdateGuildFeaturesRequest,
BulkUpdateUserFlagsRequest,
} from '../AdminModel';
export const BulkAdminController = (app: HonoApp) => {
app.post(
'/admin/bulk/update-user-flags',
RateLimitMiddleware(RateLimitConfigs.ADMIN_BULK_OPERATION),
requireAdminACL(AdminACLs.BULK_UPDATE_USER_FLAGS),
Validator('json', BulkUpdateUserFlagsRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(await adminService.bulkUpdateUserFlags(ctx.req.valid('json'), adminUserId, auditLogReason));
},
);
app.post(
'/admin/bulk/update-guild-features',
RateLimitMiddleware(RateLimitConfigs.ADMIN_BULK_OPERATION),
requireAdminACL(AdminACLs.BULK_UPDATE_GUILD_FEATURES),
Validator('json', BulkUpdateGuildFeaturesRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(await adminService.bulkUpdateGuildFeatures(ctx.req.valid('json'), adminUserId, auditLogReason));
},
);
app.post(
'/admin/bulk/add-guild-members',
RateLimitMiddleware(RateLimitConfigs.ADMIN_BULK_OPERATION),
requireAdminACL(AdminACLs.BULK_ADD_GUILD_MEMBERS),
Validator('json', BulkAddGuildMembersRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(await adminService.bulkAddGuildMembers(ctx.req.valid('json'), adminUserId, auditLogReason));
},
);
app.post(
'/admin/bulk/schedule-user-deletion',
RateLimitMiddleware(RateLimitConfigs.ADMIN_BULK_OPERATION),
requireAdminACL(AdminACLs.BULK_DELETE_USERS),
Validator('json', BulkScheduleUserDeletionRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(await adminService.bulkScheduleUserDeletion(ctx.req.valid('json'), adminUserId, auditLogReason));
},
);
};

View File

@@ -0,0 +1,66 @@
/*
* 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 type {HonoApp} from '~/App';
import {Config} from '~/Config';
import {AdminACLs} from '~/Constants';
import {requireAdminACL} from '~/middleware/AdminMiddleware';
import {RateLimitMiddleware} from '~/middleware/RateLimitMiddleware';
import {RateLimitConfigs} from '~/RateLimitConfig';
import {ProductType} from '~/stripe/ProductRegistry';
import {Validator} from '~/Validator';
import {GenerateBetaCodesRequest, GenerateGiftCodesRequest, type GiftProductType} from '../models/CodeRequestTypes';
const trimTrailingSlash = (value: string): string => (value.endsWith('/') ? value.slice(0, -1) : value);
const giftDurations: Record<GiftProductType, number> = {
[ProductType.GIFT_1_MONTH]: 1,
[ProductType.GIFT_1_YEAR]: 12,
[ProductType.GIFT_VISIONARY]: 0,
};
export const CodesAdminController = (app: HonoApp) => {
app.post(
'/admin/codes/beta',
RateLimitMiddleware(RateLimitConfigs.ADMIN_CODE_GENERATION),
requireAdminACL(AdminACLs.BETA_CODES_GENERATE),
Validator('json', GenerateBetaCodesRequest),
async (ctx) => {
const {count} = ctx.req.valid('json');
const codes = await ctx.get('adminService').generateBetaCodes(count);
return ctx.json({codes});
},
);
app.post(
'/admin/codes/gift',
RateLimitMiddleware(RateLimitConfigs.ADMIN_CODE_GENERATION),
requireAdminACL(AdminACLs.GIFT_CODES_GENERATE),
Validator('json', GenerateGiftCodesRequest),
async (ctx) => {
const {count, product_type} = ctx.req.valid('json');
const durationMonths = giftDurations[product_type];
const codes = await ctx.get('adminService').generateGiftCodes(count, durationMonths);
const baseUrl = trimTrailingSlash(Config.endpoints.gift);
return ctx.json({
codes: codes.map((code) => `${baseUrl}/${code}`),
});
},
);
};

View File

@@ -0,0 +1,111 @@
/*
* 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 type {HonoApp} from '~/App';
import {AdminACLs} from '~/Constants';
import {ALL_FEATURE_FLAGS, FeatureFlags} from '~/constants/FeatureFlags';
import {requireAdminACL} from '~/middleware/AdminMiddleware';
import {RateLimitMiddleware} from '~/middleware/RateLimitMiddleware';
import {RateLimitConfigs} from '~/RateLimitConfig';
import {z} from '~/Schema';
import {Validator} from '~/Validator';
export const FeatureFlagAdminController = (app: HonoApp) => {
app.post(
'/admin/feature-flags/get',
RateLimitMiddleware(RateLimitConfigs.ADMIN_LOOKUP),
requireAdminACL(AdminACLs.FEATURE_FLAG_VIEW),
async (ctx) => {
const featureFlagService = ctx.get('featureFlagService');
const config = featureFlagService.getConfigForSession();
return ctx.json({
feature_flags: config,
});
},
);
app.post(
'/admin/feature-flags/update',
RateLimitMiddleware(RateLimitConfigs.ADMIN_USER_MODIFY),
requireAdminACL(AdminACLs.FEATURE_FLAG_MANAGE),
Validator(
'json',
z.object({
flag: z.enum([FeatureFlags.MESSAGE_SCHEDULING, FeatureFlags.EXPRESSION_PACKS]),
guild_ids: z.string(),
}),
),
async (ctx) => {
const {flag, guild_ids} = ctx.req.valid('json');
const featureFlagService = ctx.get('featureFlagService');
const guildIdArray = guild_ids
.split(',')
.map((id) => id.trim())
.filter((id) => id.length > 0);
const guildIdSet = new Set(guildIdArray);
await featureFlagService.setFeatureGuildIds(flag, guildIdSet);
const updatedConfig = featureFlagService.getConfigForSession();
return ctx.json({
feature_flags: updatedConfig,
});
},
);
app.post(
'/admin/feature-flags/list',
RateLimitMiddleware(RateLimitConfigs.ADMIN_LOOKUP),
requireAdminACL(AdminACLs.FEATURE_FLAG_VIEW),
async (ctx) => {
return ctx.json({
flags: ALL_FEATURE_FLAGS.map((flag) => ({
key: flag,
label: getFeatureFlagLabel(flag),
description: getFeatureFlagDescription(flag),
})),
});
},
);
};
function getFeatureFlagLabel(flag: string): string {
switch (flag) {
case FeatureFlags.MESSAGE_SCHEDULING:
return 'Message Scheduling';
case FeatureFlags.EXPRESSION_PACKS:
return 'Expression Packs';
default:
return flag;
}
}
function getFeatureFlagDescription(flag: string): string {
switch (flag) {
case FeatureFlags.MESSAGE_SCHEDULING:
return 'Allows users to schedule messages to be sent later';
case FeatureFlags.EXPRESSION_PACKS:
return 'Allows users to create and share emoji/sticker packs';
default:
return '';
}
}

View File

@@ -0,0 +1,65 @@
/*
* 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 type {HonoApp} from '~/App';
import {createGuildID} from '~/BrandedTypes';
import {AdminACLs} from '~/Constants';
import {requireAdminACL} from '~/middleware/AdminMiddleware';
import {RateLimitMiddleware} from '~/middleware/RateLimitMiddleware';
import {RateLimitConfigs} from '~/RateLimitConfig';
import {Int64Type, z} from '~/Schema';
import {Validator} from '~/Validator';
import {GetProcessMemoryStatsRequest} from '../AdminModel';
export const GatewayAdminController = (app: HonoApp) => {
app.post(
'/admin/gateway/memory-stats',
RateLimitMiddleware(RateLimitConfigs.ADMIN_LOOKUP),
requireAdminACL(AdminACLs.GATEWAY_MEMORY_STATS),
Validator('json', GetProcessMemoryStatsRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const body = ctx.req.valid('json');
return ctx.json(await adminService.getGuildMemoryStats(body.limit));
},
);
app.post(
'/admin/gateway/reload-all',
RateLimitMiddleware(RateLimitConfigs.ADMIN_GATEWAY_RELOAD),
requireAdminACL(AdminACLs.GATEWAY_RELOAD_ALL),
Validator('json', z.object({guild_ids: z.array(Int64Type)})),
async (ctx) => {
const adminService = ctx.get('adminService');
const body = ctx.req.valid('json');
const guildIds = body.guild_ids.map((id) => createGuildID(id));
return ctx.json(await adminService.reloadAllGuilds(guildIds));
},
);
app.get(
'/admin/gateway/stats',
RateLimitMiddleware(RateLimitConfigs.ADMIN_LOOKUP),
requireAdminACL(AdminACLs.GATEWAY_MEMORY_STATS),
async (ctx) => {
const adminService = ctx.get('adminService');
return ctx.json(await adminService.getNodeStats());
},
);
};

View File

@@ -0,0 +1,242 @@
/*
* 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 z from 'zod';
import type {HonoApp} from '~/App';
import {createGuildID} from '~/BrandedTypes';
import {AdminACLs} from '~/Constants';
import {requireAdminACL} from '~/middleware/AdminMiddleware';
import {RateLimitMiddleware} from '~/middleware/RateLimitMiddleware';
import {RateLimitConfigs} from '~/RateLimitConfig';
import {AdminRateLimitConfigs} from '~/rate_limit_configs/AdminRateLimitConfig';
import {Int64Type} from '~/Schema';
import {Validator} from '~/Validator';
import {
ClearGuildFieldsRequest,
DeleteGuildRequest,
ForceAddUserToGuildRequest,
ListGuildMembersRequest,
LookupGuildRequest,
ReloadGuildRequest,
ShutdownGuildRequest,
TransferGuildOwnershipRequest,
UpdateGuildFeaturesRequest,
UpdateGuildNameRequest,
UpdateGuildSettingsRequest,
UpdateGuildVanityRequest,
} from '../AdminModel';
export const GuildAdminController = (app: HonoApp) => {
app.post(
'/admin/guilds/lookup',
RateLimitMiddleware(RateLimitConfigs.ADMIN_LOOKUP),
requireAdminACL(AdminACLs.GUILD_LOOKUP),
Validator('json', LookupGuildRequest),
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),
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', z.object({guild_id: Int64Type})),
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', z.object({guild_id: Int64Type})),
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),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(await adminService.clearGuildFields(ctx.req.valid('json'), adminUserId, auditLogReason));
},
);
app.post(
'/admin/guilds/update-features',
RateLimitMiddleware(RateLimitConfigs.ADMIN_GUILD_MODIFY),
requireAdminACL(AdminACLs.GUILD_UPDATE_FEATURES),
Validator('json', UpdateGuildFeaturesRequest),
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),
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),
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),
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),
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),
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/reload',
RateLimitMiddleware(RateLimitConfigs.ADMIN_GUILD_MODIFY),
requireAdminACL(AdminACLs.GUILD_RELOAD),
Validator('json', ReloadGuildRequest),
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),
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),
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));
},
);
};

View File

@@ -0,0 +1,110 @@
/*
* 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 type {HonoApp} from '~/App';
import {AdminACLs} from '~/Constants';
import {InstanceConfigRepository} from '~/instance/InstanceConfigRepository';
import {requireAdminACL} from '~/middleware/AdminMiddleware';
import {RateLimitMiddleware} from '~/middleware/RateLimitMiddleware';
import {RateLimitConfigs} from '~/RateLimitConfig';
import {z} from '~/Schema';
import {Validator} from '~/Validator';
const instanceConfigRepository = new InstanceConfigRepository();
export const InstanceConfigAdminController = (app: HonoApp) => {
app.post(
'/admin/instance-config/get',
RateLimitMiddleware(RateLimitConfigs.ADMIN_LOOKUP),
requireAdminACL(AdminACLs.INSTANCE_CONFIG_VIEW),
async (ctx) => {
const config = await instanceConfigRepository.getInstanceConfig();
const isActiveNow = instanceConfigRepository.isManualReviewActiveNow(config);
return ctx.json({
manual_review_enabled: config.manualReviewEnabled,
manual_review_schedule_enabled: config.manualReviewScheduleEnabled,
manual_review_schedule_start_hour_utc: config.manualReviewScheduleStartHourUtc,
manual_review_schedule_end_hour_utc: config.manualReviewScheduleEndHourUtc,
manual_review_active_now: isActiveNow,
registration_alerts_webhook_url: config.registrationAlertsWebhookUrl,
system_alerts_webhook_url: config.systemAlertsWebhookUrl,
});
},
);
app.post(
'/admin/instance-config/update',
RateLimitMiddleware(RateLimitConfigs.ADMIN_USER_MODIFY),
requireAdminACL(AdminACLs.INSTANCE_CONFIG_UPDATE),
Validator(
'json',
z.object({
manual_review_enabled: z.boolean().optional(),
manual_review_schedule_enabled: z.boolean().optional(),
manual_review_schedule_start_hour_utc: z.number().min(0).max(23).optional(),
manual_review_schedule_end_hour_utc: z.number().min(0).max(23).optional(),
registration_alerts_webhook_url: z.string().url().nullable().optional(),
system_alerts_webhook_url: z.string().url().nullable().optional(),
}),
),
async (ctx) => {
const data = ctx.req.valid('json');
if (data.manual_review_enabled !== undefined) {
await instanceConfigRepository.setManualReviewEnabled(data.manual_review_enabled);
}
if (
data.manual_review_schedule_enabled !== undefined ||
data.manual_review_schedule_start_hour_utc !== undefined ||
data.manual_review_schedule_end_hour_utc !== undefined
) {
const currentConfig = await instanceConfigRepository.getInstanceConfig();
const scheduleEnabled = data.manual_review_schedule_enabled ?? currentConfig.manualReviewScheduleEnabled;
const startHour = data.manual_review_schedule_start_hour_utc ?? currentConfig.manualReviewScheduleStartHourUtc;
const endHour = data.manual_review_schedule_end_hour_utc ?? currentConfig.manualReviewScheduleEndHourUtc;
await instanceConfigRepository.setManualReviewSchedule(scheduleEnabled, startHour, endHour);
}
if (data.registration_alerts_webhook_url !== undefined) {
await instanceConfigRepository.setRegistrationAlertsWebhookUrl(data.registration_alerts_webhook_url);
}
if (data.system_alerts_webhook_url !== undefined) {
await instanceConfigRepository.setSystemAlertsWebhookUrl(data.system_alerts_webhook_url);
}
const updatedConfig = await instanceConfigRepository.getInstanceConfig();
const isActiveNow = instanceConfigRepository.isManualReviewActiveNow(updatedConfig);
return ctx.json({
manual_review_enabled: updatedConfig.manualReviewEnabled,
manual_review_schedule_enabled: updatedConfig.manualReviewScheduleEnabled,
manual_review_schedule_start_hour_utc: updatedConfig.manualReviewScheduleStartHourUtc,
manual_review_schedule_end_hour_utc: updatedConfig.manualReviewScheduleEndHourUtc,
manual_review_active_now: isActiveNow,
registration_alerts_webhook_url: updatedConfig.registrationAlertsWebhookUrl,
system_alerts_webhook_url: updatedConfig.systemAlertsWebhookUrl,
});
},
);
};

View File

@@ -0,0 +1,108 @@
/*
* 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 type {HonoApp} from '~/App';
import {AdminACLs} from '~/Constants';
import {requireAdminACL} from '~/middleware/AdminMiddleware';
import {RateLimitMiddleware} from '~/middleware/RateLimitMiddleware';
import {RateLimitConfigs} from '~/RateLimitConfig';
import {Validator} from '~/Validator';
import {
DeleteAllUserMessagesRequest,
DeleteMessageRequest,
LookupMessageByAttachmentRequest,
LookupMessageRequest,
MessageShredRequest,
MessageShredStatusRequest,
} from '../AdminModel';
export const MessageAdminController = (app: HonoApp) => {
app.post(
'/admin/messages/lookup',
RateLimitMiddleware(RateLimitConfigs.ADMIN_MESSAGE_OPERATION),
requireAdminACL(AdminACLs.MESSAGE_LOOKUP),
Validator('json', LookupMessageRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
return ctx.json(await adminService.lookupMessage(ctx.req.valid('json')));
},
);
app.post(
'/admin/messages/lookup-by-attachment',
RateLimitMiddleware(RateLimitConfigs.ADMIN_MESSAGE_OPERATION),
requireAdminACL(AdminACLs.MESSAGE_LOOKUP),
Validator('json', LookupMessageByAttachmentRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
return ctx.json(await adminService.lookupMessageByAttachment(ctx.req.valid('json')));
},
);
app.post(
'/admin/messages/delete',
RateLimitMiddleware(RateLimitConfigs.ADMIN_MESSAGE_OPERATION),
requireAdminACL(AdminACLs.MESSAGE_DELETE),
Validator('json', DeleteMessageRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(await adminService.deleteMessage(ctx.req.valid('json'), adminUserId, auditLogReason));
},
);
app.post(
'/admin/messages/shred',
RateLimitMiddleware(RateLimitConfigs.ADMIN_MESSAGE_OPERATION),
requireAdminACL(AdminACLs.MESSAGE_SHRED),
Validator('json', MessageShredRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(await adminService.queueMessageShred(ctx.req.valid('json'), adminUserId, auditLogReason));
},
);
app.post(
'/admin/messages/delete-all',
RateLimitMiddleware(RateLimitConfigs.ADMIN_MESSAGE_OPERATION),
requireAdminACL(AdminACLs.MESSAGE_DELETE_ALL),
Validator('json', DeleteAllUserMessagesRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(await adminService.deleteAllUserMessages(ctx.req.valid('json'), adminUserId, auditLogReason));
},
);
app.post(
'/admin/messages/shred-status',
RateLimitMiddleware(RateLimitConfigs.ADMIN_MESSAGE_OPERATION),
requireAdminACL(AdminACLs.MESSAGE_SHRED),
Validator('json', MessageShredStatusRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const body = ctx.req.valid('json');
return ctx.json(await adminService.getMessageShredStatus(body.job_id));
},
);
};

View File

@@ -0,0 +1,101 @@
/*
* 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 type {HonoApp} from '~/App';
import {createReportID} from '~/BrandedTypes';
import {AdminACLs} from '~/Constants';
import {requireAdminACL} from '~/middleware/AdminMiddleware';
import {RateLimitMiddleware} from '~/middleware/RateLimitMiddleware';
import {RateLimitConfigs} from '~/RateLimitConfig';
import {createStringType, Int64Type, z} from '~/Schema';
import {Validator} from '~/Validator';
import {SearchReportsRequest} from '../AdminModel';
export const ReportAdminController = (app: HonoApp) => {
app.post(
'/admin/reports/list',
RateLimitMiddleware(RateLimitConfigs.ADMIN_LOOKUP),
requireAdminACL(AdminACLs.REPORT_VIEW),
Validator(
'json',
z.object({
status: z.number().optional(),
limit: z.number().optional(),
offset: z.number().optional(),
}),
),
async (ctx) => {
const adminService = ctx.get('adminService');
const {status, limit, offset} = ctx.req.valid('json');
return ctx.json(await adminService.listReports(status ?? 0, limit, offset));
},
);
app.get(
'/admin/reports/:report_id',
RateLimitMiddleware(RateLimitConfigs.ADMIN_LOOKUP),
requireAdminACL(AdminACLs.REPORT_VIEW),
Validator('param', z.object({report_id: Int64Type})),
async (ctx) => {
const adminService = ctx.get('adminService');
const {report_id} = ctx.req.valid('param');
const report = await adminService.getReport(createReportID(report_id));
return ctx.json(report);
},
);
app.post(
'/admin/reports/resolve',
RateLimitMiddleware(RateLimitConfigs.ADMIN_LOOKUP),
requireAdminACL(AdminACLs.REPORT_RESOLVE),
Validator(
'json',
z.object({
report_id: Int64Type,
public_comment: createStringType(0, 512).optional(),
}),
),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
const {report_id, public_comment} = ctx.req.valid('json');
return ctx.json(
await adminService.resolveReport(
createReportID(report_id),
adminUserId,
public_comment || null,
auditLogReason,
),
);
},
);
app.post(
'/admin/reports/search',
RateLimitMiddleware(RateLimitConfigs.ADMIN_LOOKUP),
requireAdminACL(AdminACLs.REPORT_VIEW),
Validator('json', SearchReportsRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const body = ctx.req.valid('json');
return ctx.json(await adminService.searchReports(body));
},
);
};

View File

@@ -0,0 +1,83 @@
/*
* 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 type {HonoApp} from '~/App';
import {AdminACLs} from '~/Constants';
import {requireAdminACL} from '~/middleware/AdminMiddleware';
import {RateLimitMiddleware} from '~/middleware/RateLimitMiddleware';
import {RateLimitConfigs} from '~/RateLimitConfig';
import {Validator} from '~/Validator';
import {
GetIndexRefreshStatusRequest,
RefreshSearchIndexRequest,
SearchGuildsRequest,
SearchUsersRequest,
} from '../AdminModel';
export const SearchAdminController = (app: HonoApp) => {
app.post(
'/admin/guilds/search',
RateLimitMiddleware(RateLimitConfigs.ADMIN_LOOKUP),
requireAdminACL(AdminACLs.GUILD_LOOKUP),
Validator('json', SearchGuildsRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const body = ctx.req.valid('json');
return ctx.json(await adminService.searchGuilds(body));
},
);
app.post(
'/admin/users/search',
RateLimitMiddleware(RateLimitConfigs.ADMIN_LOOKUP),
requireAdminACL(AdminACLs.USER_LOOKUP),
Validator('json', SearchUsersRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const body = ctx.req.valid('json');
return ctx.json(await adminService.searchUsers(body));
},
);
app.post(
'/admin/search/refresh-index',
RateLimitMiddleware(RateLimitConfigs.ADMIN_LOOKUP),
requireAdminACL(AdminACLs.GUILD_LOOKUP),
Validator('json', RefreshSearchIndexRequest),
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.refreshSearchIndex(body, adminUserId, auditLogReason));
},
);
app.post(
'/admin/search/refresh-status',
RateLimitMiddleware(RateLimitConfigs.ADMIN_LOOKUP),
requireAdminACL(AdminACLs.GUILD_LOOKUP),
Validator('json', GetIndexRefreshStatusRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const body = ctx.req.valid('json');
return ctx.json(await adminService.getIndexRefreshStatus(body.job_id));
},
);
};

View File

@@ -0,0 +1,374 @@
/*
* 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 type {HonoApp} from '~/App';
import {createUserID} from '~/BrandedTypes';
import {AdminACLs} from '~/Constants';
import {requireAdminACL} from '~/middleware/AdminMiddleware';
import {RateLimitMiddleware} from '~/middleware/RateLimitMiddleware';
import {RateLimitConfigs} from '~/RateLimitConfig';
import {Validator} from '~/Validator';
import {
CancelBulkMessageDeletionRequest,
ChangeDobRequest,
ChangeEmailRequest,
ChangeUsernameRequest,
ClearUserFieldsRequest,
DisableForSuspiciousActivityRequest,
DisableMfaRequest,
ListUserChangeLogRequest,
ListUserGuildsRequest,
ListUserSessionsRequest,
LookupUserRequest,
ScheduleAccountDeletionRequest,
SendPasswordResetRequest,
SetUserAclsRequest,
SetUserBotStatusRequest,
SetUserSystemStatusRequest,
TempBanUserRequest,
TerminateSessionsRequest,
UnlinkPhoneRequest,
UpdateSuspiciousActivityFlagsRequest,
UpdateUserFlagsRequest,
VerifyUserEmailRequest,
} from '../AdminModel';
export const UserAdminController = (app: HonoApp) => {
app.post(
'/admin/users/lookup',
RateLimitMiddleware(RateLimitConfigs.ADMIN_LOOKUP),
requireAdminACL(AdminACLs.USER_LOOKUP),
Validator('json', LookupUserRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
return ctx.json(await adminService.lookupUser(ctx.req.valid('json')));
},
);
app.post(
'/admin/users/list-guilds',
RateLimitMiddleware(RateLimitConfigs.ADMIN_LOOKUP),
requireAdminACL(AdminACLs.USER_LIST_GUILDS),
Validator('json', ListUserGuildsRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
return ctx.json(await adminService.listUserGuilds(ctx.req.valid('json')));
},
);
app.post(
'/admin/users/disable-mfa',
RateLimitMiddleware(RateLimitConfigs.ADMIN_USER_MODIFY),
requireAdminACL(AdminACLs.USER_UPDATE_MFA),
Validator('json', DisableMfaRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
await adminService.disableMfa(ctx.req.valid('json'), adminUserId, auditLogReason);
return ctx.body(null, 204);
},
);
app.post(
'/admin/users/clear-fields',
RateLimitMiddleware(RateLimitConfigs.ADMIN_USER_MODIFY),
requireAdminACL(AdminACLs.USER_UPDATE_PROFILE),
Validator('json', ClearUserFieldsRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(await adminService.clearUserFields(ctx.req.valid('json'), adminUserId, auditLogReason));
},
);
app.post(
'/admin/users/set-bot-status',
RateLimitMiddleware(RateLimitConfigs.ADMIN_USER_MODIFY),
requireAdminACL(AdminACLs.USER_UPDATE_BOT_STATUS),
Validator('json', SetUserBotStatusRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(await adminService.setUserBotStatus(ctx.req.valid('json'), adminUserId, auditLogReason));
},
);
app.post(
'/admin/users/set-system-status',
RateLimitMiddleware(RateLimitConfigs.ADMIN_USER_MODIFY),
requireAdminACL(AdminACLs.USER_UPDATE_BOT_STATUS),
Validator('json', SetUserSystemStatusRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(await adminService.setUserSystemStatus(ctx.req.valid('json'), adminUserId, auditLogReason));
},
);
app.post(
'/admin/users/verify-email',
RateLimitMiddleware(RateLimitConfigs.ADMIN_USER_MODIFY),
requireAdminACL(AdminACLs.USER_UPDATE_EMAIL),
Validator('json', VerifyUserEmailRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(await adminService.verifyUserEmail(ctx.req.valid('json'), adminUserId, auditLogReason));
},
);
app.post(
'/admin/users/send-password-reset',
RateLimitMiddleware(RateLimitConfigs.ADMIN_USER_MODIFY),
requireAdminACL(AdminACLs.USER_UPDATE_EMAIL),
Validator('json', SendPasswordResetRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
await adminService.sendPasswordReset(ctx.req.valid('json'), adminUserId, auditLogReason);
return ctx.body(null, 204);
},
);
app.post(
'/admin/users/change-username',
RateLimitMiddleware(RateLimitConfigs.ADMIN_USER_MODIFY),
requireAdminACL(AdminACLs.USER_UPDATE_USERNAME),
Validator('json', ChangeUsernameRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(await adminService.changeUsername(ctx.req.valid('json'), adminUserId, auditLogReason));
},
);
app.post(
'/admin/users/change-email',
RateLimitMiddleware(RateLimitConfigs.ADMIN_USER_MODIFY),
requireAdminACL(AdminACLs.USER_UPDATE_EMAIL),
Validator('json', ChangeEmailRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(await adminService.changeEmail(ctx.req.valid('json'), adminUserId, auditLogReason));
},
);
app.post(
'/admin/users/terminate-sessions',
RateLimitMiddleware(RateLimitConfigs.ADMIN_USER_MODIFY),
requireAdminACL(AdminACLs.USER_UPDATE_FLAGS),
Validator('json', TerminateSessionsRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(await adminService.terminateSessions(ctx.req.valid('json'), adminUserId, auditLogReason));
},
);
app.post(
'/admin/users/temp-ban',
RateLimitMiddleware(RateLimitConfigs.ADMIN_USER_MODIFY),
requireAdminACL(AdminACLs.USER_TEMP_BAN),
Validator('json', TempBanUserRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(await adminService.tempBanUser(ctx.req.valid('json'), adminUserId, auditLogReason));
},
);
app.post(
'/admin/users/unban',
RateLimitMiddleware(RateLimitConfigs.ADMIN_USER_MODIFY),
requireAdminACL(AdminACLs.USER_TEMP_BAN),
Validator('json', DisableMfaRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(await adminService.unbanUser(ctx.req.valid('json'), adminUserId, auditLogReason));
},
);
app.post(
'/admin/users/schedule-deletion',
RateLimitMiddleware(RateLimitConfigs.ADMIN_USER_MODIFY),
requireAdminACL(AdminACLs.USER_DELETE),
Validator('json', ScheduleAccountDeletionRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(await adminService.scheduleAccountDeletion(ctx.req.valid('json'), adminUserId, auditLogReason));
},
);
app.post(
'/admin/users/cancel-deletion',
RateLimitMiddleware(RateLimitConfigs.ADMIN_USER_MODIFY),
requireAdminACL(AdminACLs.USER_DELETE),
Validator('json', DisableMfaRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(await adminService.cancelAccountDeletion(ctx.req.valid('json'), adminUserId, auditLogReason));
},
);
app.post(
'/admin/users/cancel-bulk-message-deletion',
RateLimitMiddleware(RateLimitConfigs.ADMIN_USER_MODIFY),
requireAdminACL(AdminACLs.USER_CANCEL_BULK_MESSAGE_DELETION),
Validator('json', CancelBulkMessageDeletionRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(await adminService.cancelBulkMessageDeletion(ctx.req.valid('json'), adminUserId, auditLogReason));
},
);
app.post(
'/admin/users/set-acls',
RateLimitMiddleware(RateLimitConfigs.ADMIN_USER_MODIFY),
requireAdminACL(AdminACLs.ACL_SET_USER),
Validator('json', SetUserAclsRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(await adminService.setUserAcls(ctx.req.valid('json'), adminUserId, auditLogReason));
},
);
app.post(
'/admin/users/update-flags',
RateLimitMiddleware(RateLimitConfigs.ADMIN_USER_MODIFY),
requireAdminACL(AdminACLs.USER_UPDATE_FLAGS),
Validator('json', UpdateUserFlagsRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
const body = ctx.req.valid('json');
const userId = createUserID(body.user_id);
return ctx.json(
await adminService.updateUserFlags({
userId,
data: {addFlags: body.add_flags, removeFlags: body.remove_flags},
adminUserId,
auditLogReason,
}),
);
},
);
app.post(
'/admin/users/unlink-phone',
RateLimitMiddleware(RateLimitConfigs.ADMIN_USER_MODIFY),
requireAdminACL(AdminACLs.USER_UPDATE_PHONE),
Validator('json', UnlinkPhoneRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(await adminService.unlinkPhone(ctx.req.valid('json'), adminUserId, auditLogReason));
},
);
app.post(
'/admin/users/change-dob',
RateLimitMiddleware(RateLimitConfigs.ADMIN_USER_MODIFY),
requireAdminACL(AdminACLs.USER_UPDATE_DOB),
Validator('json', ChangeDobRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(await adminService.changeDob(ctx.req.valid('json'), adminUserId, auditLogReason));
},
);
app.post(
'/admin/users/update-suspicious-activity-flags',
RateLimitMiddleware(RateLimitConfigs.ADMIN_USER_MODIFY),
requireAdminACL(AdminACLs.USER_UPDATE_SUSPICIOUS_ACTIVITY),
Validator('json', UpdateSuspiciousActivityFlagsRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(
await adminService.updateSuspiciousActivityFlags(ctx.req.valid('json'), adminUserId, auditLogReason),
);
},
);
app.post(
'/admin/users/disable-suspicious',
RateLimitMiddleware(RateLimitConfigs.ADMIN_USER_MODIFY),
requireAdminACL(AdminACLs.USER_DISABLE_SUSPICIOUS),
Validator('json', DisableForSuspiciousActivityRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(
await adminService.disableForSuspiciousActivity(ctx.req.valid('json'), adminUserId, auditLogReason),
);
},
);
app.post(
'/admin/users/list-sessions',
RateLimitMiddleware(RateLimitConfigs.ADMIN_USER_MODIFY),
requireAdminACL(AdminACLs.USER_LIST_SESSIONS),
Validator('json', ListUserSessionsRequest),
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.listUserSessions(body.user_id, adminUserId, auditLogReason));
},
);
app.post(
'/admin/users/change-log',
RateLimitMiddleware(RateLimitConfigs.ADMIN_LOOKUP),
requireAdminACL(AdminACLs.USER_LOOKUP),
Validator('json', ListUserChangeLogRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
return ctx.json(await adminService.listUserChangeLog(ctx.req.valid('json')));
},
);
};

View File

@@ -0,0 +1,99 @@
/*
* 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 type {HonoApp} from '~/App';
import {createUserID} from '~/BrandedTypes';
import {AdminACLs} from '~/Constants';
import {requireAdminACL} from '~/middleware/AdminMiddleware';
import {RateLimitMiddleware} from '~/middleware/RateLimitMiddleware';
import {RateLimitConfigs} from '~/RateLimitConfig';
import {Int64Type, z} from '~/Schema';
import {Validator} from '~/Validator';
export const VerificationAdminController = (app: HonoApp) => {
app.post(
'/admin/pending-verifications/list',
RateLimitMiddleware(RateLimitConfigs.ADMIN_LOOKUP),
requireAdminACL(AdminACLs.USER_LOOKUP),
Validator('json', z.object({limit: z.number().default(100)})),
async (ctx) => {
const adminService = ctx.get('adminService');
const {limit} = ctx.req.valid('json');
return ctx.json(await adminService.listPendingVerifications(limit));
},
);
app.post(
'/admin/pending-verifications/approve',
RateLimitMiddleware(RateLimitConfigs.ADMIN_USER_MODIFY),
requireAdminACL(AdminACLs.USER_UPDATE_FLAGS),
Validator('json', z.object({user_id: Int64Type})),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
const {user_id} = ctx.req.valid('json');
return ctx.json(await adminService.approveRegistration(createUserID(user_id), adminUserId, auditLogReason));
},
);
app.post(
'/admin/pending-verifications/reject',
RateLimitMiddleware(RateLimitConfigs.ADMIN_USER_MODIFY),
requireAdminACL(AdminACLs.USER_UPDATE_FLAGS),
Validator('json', z.object({user_id: Int64Type})),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
const {user_id} = ctx.req.valid('json');
return ctx.json(await adminService.rejectRegistration(createUserID(user_id), adminUserId, auditLogReason));
},
);
app.post(
'/admin/pending-verifications/bulk-approve',
RateLimitMiddleware(RateLimitConfigs.ADMIN_USER_MODIFY),
requireAdminACL(AdminACLs.USER_UPDATE_FLAGS),
Validator('json', z.object({user_ids: z.array(Int64Type).min(1)})),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
const {user_ids} = ctx.req.valid('json');
const parsedUserIds = user_ids.map(createUserID);
return ctx.json(await adminService.bulkApproveRegistrations(parsedUserIds, adminUserId, auditLogReason));
},
);
app.post(
'/admin/pending-verifications/bulk-reject',
RateLimitMiddleware(RateLimitConfigs.ADMIN_USER_MODIFY),
requireAdminACL(AdminACLs.USER_UPDATE_FLAGS),
Validator('json', z.object({user_ids: z.array(Int64Type).min(1)})),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
const {user_ids} = ctx.req.valid('json');
const parsedUserIds = user_ids.map(createUserID);
return ctx.json(await adminService.bulkRejectRegistrations(parsedUserIds, adminUserId, auditLogReason));
},
);
};

View File

@@ -0,0 +1,169 @@
/*
* 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 type {HonoApp} from '~/App';
import {AdminACLs} from '~/Constants';
import {requireAdminACL} from '~/middleware/AdminMiddleware';
import {RateLimitMiddleware} from '~/middleware/RateLimitMiddleware';
import {RateLimitConfigs} from '~/RateLimitConfig';
import {Validator} from '~/Validator';
import {
CreateVoiceRegionRequest,
CreateVoiceServerRequest,
DeleteVoiceRegionRequest,
DeleteVoiceServerRequest,
GetVoiceRegionRequest,
GetVoiceServerRequest,
ListVoiceRegionsRequest,
ListVoiceServersRequest,
UpdateVoiceRegionRequest,
UpdateVoiceServerRequest,
} from '../AdminModel';
export const VoiceAdminController = (app: HonoApp) => {
app.post(
'/admin/voice/regions/list',
RateLimitMiddleware(RateLimitConfigs.ADMIN_LOOKUP),
requireAdminACL(AdminACLs.VOICE_REGION_LIST),
Validator('json', ListVoiceRegionsRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(await adminService.listVoiceRegions(ctx.req.valid('json'), adminUserId, auditLogReason));
},
);
app.post(
'/admin/voice/regions/get',
RateLimitMiddleware(RateLimitConfigs.ADMIN_LOOKUP),
requireAdminACL(AdminACLs.VOICE_REGION_LIST),
Validator('json', GetVoiceRegionRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(await adminService.getVoiceRegion(ctx.req.valid('json'), adminUserId, auditLogReason));
},
);
app.post(
'/admin/voice/regions/create',
RateLimitMiddleware(RateLimitConfigs.ADMIN_GUILD_MODIFY),
requireAdminACL(AdminACLs.VOICE_REGION_CREATE),
Validator('json', CreateVoiceRegionRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(await adminService.createVoiceRegion(ctx.req.valid('json'), adminUserId, auditLogReason));
},
);
app.post(
'/admin/voice/regions/update',
RateLimitMiddleware(RateLimitConfigs.ADMIN_GUILD_MODIFY),
requireAdminACL(AdminACLs.VOICE_REGION_UPDATE),
Validator('json', UpdateVoiceRegionRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(await adminService.updateVoiceRegion(ctx.req.valid('json'), adminUserId, auditLogReason));
},
);
app.post(
'/admin/voice/regions/delete',
RateLimitMiddleware(RateLimitConfigs.ADMIN_GUILD_MODIFY),
requireAdminACL(AdminACLs.VOICE_REGION_DELETE),
Validator('json', DeleteVoiceRegionRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(await adminService.deleteVoiceRegion(ctx.req.valid('json'), adminUserId, auditLogReason));
},
);
app.post(
'/admin/voice/servers/list',
RateLimitMiddleware(RateLimitConfigs.ADMIN_LOOKUP),
requireAdminACL(AdminACLs.VOICE_SERVER_LIST),
Validator('json', ListVoiceServersRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(await adminService.listVoiceServers(ctx.req.valid('json'), adminUserId, auditLogReason));
},
);
app.post(
'/admin/voice/servers/get',
RateLimitMiddleware(RateLimitConfigs.ADMIN_LOOKUP),
requireAdminACL(AdminACLs.VOICE_SERVER_LIST),
Validator('json', GetVoiceServerRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(await adminService.getVoiceServer(ctx.req.valid('json'), adminUserId, auditLogReason));
},
);
app.post(
'/admin/voice/servers/create',
RateLimitMiddleware(RateLimitConfigs.ADMIN_GUILD_MODIFY),
requireAdminACL(AdminACLs.VOICE_SERVER_CREATE),
Validator('json', CreateVoiceServerRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(await adminService.createVoiceServer(ctx.req.valid('json'), adminUserId, auditLogReason));
},
);
app.post(
'/admin/voice/servers/update',
RateLimitMiddleware(RateLimitConfigs.ADMIN_GUILD_MODIFY),
requireAdminACL(AdminACLs.VOICE_SERVER_UPDATE),
Validator('json', UpdateVoiceServerRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(await adminService.updateVoiceServer(ctx.req.valid('json'), adminUserId, auditLogReason));
},
);
app.post(
'/admin/voice/servers/delete',
RateLimitMiddleware(RateLimitConfigs.ADMIN_GUILD_MODIFY),
requireAdminACL(AdminACLs.VOICE_SERVER_DELETE),
Validator('json', DeleteVoiceServerRequest),
async (ctx) => {
const adminService = ctx.get('adminService');
const adminUserId = ctx.get('adminUserId');
const auditLogReason = ctx.get('auditLogReason');
return ctx.json(await adminService.deleteVoiceServer(ctx.req.valid('json'), adminUserId, auditLogReason));
},
);
};

View File

@@ -0,0 +1,55 @@
/*
* 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 type {HonoApp} from '~/App';
import {ArchiveAdminController} from './ArchiveAdminController';
import {AssetAdminController} from './AssetAdminController';
import {AuditLogAdminController} from './AuditLogAdminController';
import {BanAdminController} from './BanAdminController';
import {BulkAdminController} from './BulkAdminController';
import {CodesAdminController} from './CodesAdminController';
import {FeatureFlagAdminController} from './FeatureFlagAdminController';
import {GatewayAdminController} from './GatewayAdminController';
import {GuildAdminController} from './GuildAdminController';
import {InstanceConfigAdminController} from './InstanceConfigAdminController';
import {MessageAdminController} from './MessageAdminController';
import {ReportAdminController} from './ReportAdminController';
import {SearchAdminController} from './SearchAdminController';
import {UserAdminController} from './UserAdminController';
import {VerificationAdminController} from './VerificationAdminController';
import {VoiceAdminController} from './VoiceAdminController';
export const registerAdminControllers = (app: HonoApp) => {
UserAdminController(app);
CodesAdminController(app);
GuildAdminController(app);
AssetAdminController(app);
BanAdminController(app);
InstanceConfigAdminController(app);
MessageAdminController(app);
BulkAdminController(app);
AuditLogAdminController(app);
ArchiveAdminController(app);
ReportAdminController(app);
VoiceAdminController(app);
GatewayAdminController(app);
SearchAdminController(app);
VerificationAdminController(app);
FeatureFlagAdminController(app);
};