Files
fluxer/packages/admin/src/routes/Reports.tsx
2026-02-17 12:22:36 +00:00

147 lines
4.9 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/>.
*/
/** @jsxRuntime automatic */
/** @jsxImportSource hono/jsx */
import {getErrorMessage} from '@fluxer/admin/src/api/Errors';
import {getReportDetail, resolveReport} from '@fluxer/admin/src/api/Reports';
import {redirectWithFlash} from '@fluxer/admin/src/middleware/Auth';
import {ReportDetailFragment, ReportDetailPage} from '@fluxer/admin/src/pages/ReportDetailPage';
import {ReportsPage} from '@fluxer/admin/src/pages/ReportsPage';
import {getRouteContext} from '@fluxer/admin/src/routes/RouteContext';
import type {RouteFactoryDeps} from '@fluxer/admin/src/routes/RouteTypes';
import {getPageConfig} from '@fluxer/admin/src/SelfHostedOverride';
import type {AppVariables} from '@fluxer/admin/src/types/App';
import {getOptionalString, type ParsedBody} from '@fluxer/admin/src/utils/Forms';
import {Hono} from 'hono';
export function createReportsRoutes({config, assetVersion, requireAuth}: RouteFactoryDeps) {
const router = new Hono<{Variables: AppVariables}>();
router.get('/reports', requireAuth, async (c) => {
const {session, currentAdmin, flash, csrfToken} = getRouteContext(c);
const pageConfig = getPageConfig(c, config);
const query = c.req.query('q');
const page = parseInt(c.req.query('page') ?? '0', 10);
const limit = parseInt(c.req.query('limit') ?? '25', 10);
const statusParam = c.req.query('status');
const typeParam = c.req.query('type');
const categoryFilter = c.req.query('category');
const sort = c.req.query('sort');
const statusFilter = statusParam ? parseInt(statusParam, 10) : undefined;
const typeFilter = typeParam ? parseInt(typeParam, 10) : undefined;
const pageResult = await ReportsPage({
config: pageConfig,
session,
currentAdmin,
flash,
assetVersion,
query,
page,
limit,
statusFilter,
typeFilter,
categoryFilter,
sort,
csrfToken,
});
return c.html(pageResult ?? '');
});
router.get('/reports/:reportId', requireAuth, async (c) => {
const {session, currentAdmin, flash, csrfToken} = getRouteContext(c);
const pageConfig = getPageConfig(c, config);
const reportId = c.req.param('reportId');
const reportResult = await getReportDetail(config, session, reportId);
if (!reportResult.ok) {
return redirectWithFlash(c, `${config.basePath}/reports`, {
message: getErrorMessage(reportResult.error),
type: 'error',
});
}
return c.html(
<ReportDetailPage
config={pageConfig}
session={session}
currentAdmin={currentAdmin}
flash={flash}
report={reportResult.data}
assetVersion={assetVersion}
csrfToken={csrfToken}
/>,
);
});
router.get('/reports/:reportId/fragment', requireAuth, async (c) => {
const session = c.get('session')!;
const pageConfig = getPageConfig(c, config);
const reportId = c.req.param('reportId');
const reportResult = await getReportDetail(config, session, reportId);
if (!reportResult.ok) {
return c.html(
<div data-report-fragment="" class="rounded-xl border border-red-200 bg-red-50 p-4 text-red-800 text-sm">
Failed to load report: {getErrorMessage(reportResult.error)}
</div>,
);
}
return c.html(<ReportDetailFragment config={pageConfig} report={reportResult.data} />);
});
router.post('/reports/:reportId/resolve', requireAuth, async (c) => {
const session = c.get('session')!;
const reportId = c.req.param('reportId');
const redirectUrl = `${config.basePath}/reports/${reportId}`;
const isBackground = c.req.query('background') === '1';
try {
const formData = (await c.req.parseBody()) as ParsedBody;
const publicComment = getOptionalString(formData, 'public_comment');
const auditLogReason = getOptionalString(formData, 'audit_log_reason');
const result = await resolveReport(config, session, reportId, publicComment, auditLogReason);
if (isBackground) {
if (!result.ok) {
return c.text(getErrorMessage(result.error), 400);
}
return c.body(null, 204);
}
return redirectWithFlash(c, redirectUrl, {
message: result.ok ? 'Report resolved' : getErrorMessage(result.error),
type: result.ok ? 'success' : 'error',
});
} catch {
if (isBackground) {
return c.text('Invalid form data', 400);
}
return redirectWithFlash(c, redirectUrl, {message: 'Invalid form data', type: 'error'});
}
});
return router;
}