feat(admin): add data tools for export, import, and wipe

Add admin.data.export and admin.data.import permissions
Implement backend logic and API endpoints for data bundle management
Add Data Tools tab to admin interface with scope selection
Support Pokemon, Habitats, Items, Recipes, and Daily CheckList scopes
This commit is contained in:
2026-05-04 00:56:37 +08:00
parent fa06d24826
commit f2a8b67ebf
8 changed files with 941 additions and 4 deletions

View File

@@ -59,14 +59,17 @@ import {
deleteLifePostReaction,
deletePokemon,
deleteRecipe,
exportAdminData,
fetchPokemonData,
fetchPokemonImageOptions,
getAdminDataToolsSummary,
getHabitat,
getItem,
getOptions,
getPokemon,
getPublicUserProfile,
getRecipe,
importAdminData,
isConfigType,
listEntityDiscussionComments,
listConfig,
@@ -101,7 +104,8 @@ import {
updateLanguage,
updateLifePost,
updatePokemon,
updateRecipe
updateRecipe,
wipeAdminData
} from './queries.ts';
import {
getAiModerationSettings,
@@ -178,7 +182,7 @@ app.setErrorHandler(async (error, _request, reply) => {
return reply.code(409).send({ message: await serverMessage(locale, 'duplicate') });
}
if (pgError.code === '23514') {
if (pgError.code === '23502' || pgError.code === '23514') {
return reply.code(400).send({ message: await serverMessage(locale, 'invalidField') });
}
@@ -1817,6 +1821,26 @@ app.put('/api/admin/rate-limits', async (request, reply) => {
return updateRateLimitSettings(request.body as Record<string, unknown>, user.id);
});
app.get('/api/admin/data-tools/summary', async (request, reply) => {
const user = await requireAnyPermission(request, reply, ['admin.data.export', 'admin.data.import']);
return user ? getAdminDataToolsSummary() : undefined;
});
app.post('/api/admin/data-tools/export', async (request, reply) => {
const user = await requirePermissionWithRateLimits(request, reply, 'admin.data.export', 'adminWrite');
return user ? exportAdminData(request.body as Record<string, unknown>) : undefined;
});
app.post('/api/admin/data-tools/import', async (request, reply) => {
const user = await requirePermissionWithRateLimits(request, reply, 'admin.data.import', 'adminWrite');
return user ? importAdminData(request.body as Record<string, unknown>) : undefined;
});
app.post('/api/admin/data-tools/wipe', async (request, reply) => {
const user = await requirePermissionWithRateLimits(request, reply, 'admin.data.import', 'adminWrite');
return user ? wipeAdminData(request.body as Record<string, unknown>) : undefined;
});
app.get('/api/admin/config/:type', async (request, reply) => {
const user = await requirePermission(request, reply, 'admin.config.read');
if (!user) {