mirror of
https://github.com/ZeppelinBot/Zeppelin.git
synced 2025-05-10 04:25:01 +00:00
feat(dashboard): add support for importing/exporting cases
This commit is contained in:
parent
f3dae65747
commit
45941e47d6
9 changed files with 593 additions and 2 deletions
163
backend/src/api/guilds/importExport.ts
Normal file
163
backend/src/api/guilds/importExport.ts
Normal file
|
@ -0,0 +1,163 @@
|
|||
import { ApiPermissions } from "@shared/apiPermissions";
|
||||
import express, { Request, Response } from "express";
|
||||
import { requireGuildPermission } from "../permissions";
|
||||
import { clientError, ok } from "../responses";
|
||||
import { GuildCases } from "../../data/GuildCases";
|
||||
import { z } from "zod";
|
||||
import { Case } from "../../data/entities/Case";
|
||||
|
||||
const caseHandlingModeSchema = z.union([
|
||||
z.literal("replace"),
|
||||
z.literal("bumpExistingCases"),
|
||||
z.literal("bumpImportedCases"),
|
||||
]);
|
||||
|
||||
type CaseHandlingMode = z.infer<typeof caseHandlingModeSchema>;
|
||||
|
||||
const caseNoteData = z.object({
|
||||
mod_id: z.string(),
|
||||
mod_name: z.string(),
|
||||
body: z.string(),
|
||||
created_at: z.string(),
|
||||
});
|
||||
|
||||
const caseData = z.object({
|
||||
case_number: z.number(),
|
||||
user_id: z.string(),
|
||||
user_name: z.string(),
|
||||
mod_id: z.nullable(z.string()),
|
||||
mod_name: z.nullable(z.string()),
|
||||
type: z.number(),
|
||||
created_at: z.string(),
|
||||
is_hidden: z.boolean(),
|
||||
pp_id: z.nullable(z.string()),
|
||||
pp_name: z.nullable(z.string()),
|
||||
|
||||
notes: z.array(caseNoteData),
|
||||
});
|
||||
|
||||
const importExportData = z.object({
|
||||
cases: z.array(caseData),
|
||||
});
|
||||
type TImportExportData = z.infer<typeof importExportData>;
|
||||
|
||||
export function initGuildsImportExportAPI(guildRouter: express.Router) {
|
||||
const importExportRouter = express.Router();
|
||||
|
||||
importExportRouter.get(
|
||||
"/:guildId/pre-import",
|
||||
requireGuildPermission(ApiPermissions.ManageAccess),
|
||||
async (req: Request, res: Response) => {
|
||||
const guildCases = GuildCases.getGuildInstance(req.params.guildId);
|
||||
const minNum = await guildCases.getMinCaseNumber();
|
||||
const maxNum = await guildCases.getMaxCaseNumber();
|
||||
|
||||
return {
|
||||
minCaseNumber: minNum,
|
||||
maxCaseNumber: maxNum,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
importExportRouter.post(
|
||||
"/:guildId/import",
|
||||
requireGuildPermission(ApiPermissions.ManageAccess),
|
||||
async (req: Request, res: Response) => {
|
||||
let data: TImportExportData;
|
||||
try {
|
||||
data = importExportData.parse(req.body.data);
|
||||
} catch (err) {
|
||||
return clientError(res, "Invalid import data format");
|
||||
return;
|
||||
}
|
||||
|
||||
let caseHandlingMode: CaseHandlingMode;
|
||||
try {
|
||||
caseHandlingMode = caseHandlingModeSchema.parse(req.body.caseHandlingMode);
|
||||
} catch (err) {
|
||||
return clientError(res, "Invalid case handling mode");
|
||||
return;
|
||||
}
|
||||
|
||||
const guildCases = GuildCases.getGuildInstance(req.params.guildId);
|
||||
|
||||
// Prepare cases
|
||||
if (caseHandlingMode === "replace") {
|
||||
// Replace existing cases
|
||||
await guildCases.deleteAllCases();
|
||||
} else if (caseHandlingMode === "bumpExistingCases") {
|
||||
// Bump existing numbers
|
||||
const maxNumberInData = data.cases.reduce((max, theCase) => Math.max(max, theCase.case_number), 0);
|
||||
await guildCases.bumpCaseNumbers(maxNumberInData);
|
||||
} else if (caseHandlingMode === "bumpImportedCases") {
|
||||
const maxExistingNumber = await guildCases.getMaxCaseNumber();
|
||||
for (const theCase of data.cases) {
|
||||
theCase.case_number += maxExistingNumber;
|
||||
}
|
||||
}
|
||||
|
||||
// Import cases
|
||||
for (const theCase of data.cases) {
|
||||
const insertData: any = {
|
||||
...theCase,
|
||||
is_hidden: theCase.is_hidden ? 1 : 0,
|
||||
guild_id: req.params.guildId,
|
||||
notes: undefined,
|
||||
};
|
||||
|
||||
const caseInsertData = await guildCases.createInternal(insertData);
|
||||
for (const note of theCase.notes) {
|
||||
await guildCases.createNote(caseInsertData.identifiers[0].id, note);
|
||||
}
|
||||
}
|
||||
|
||||
ok(res);
|
||||
},
|
||||
);
|
||||
|
||||
const exportBatchSize = 500;
|
||||
importExportRouter.post(
|
||||
"/:guildId/export",
|
||||
requireGuildPermission(ApiPermissions.ManageAccess),
|
||||
async (req: Request, res: Response) => {
|
||||
const guildCases = GuildCases.getGuildInstance(req.params.guildId);
|
||||
|
||||
const data: TImportExportData = {
|
||||
cases: [],
|
||||
};
|
||||
|
||||
let n = 0;
|
||||
let cases: Case[];
|
||||
do {
|
||||
cases = await guildCases.getExportCases(n, exportBatchSize);
|
||||
n += cases.length;
|
||||
|
||||
for (const theCase of cases) {
|
||||
data.cases.push({
|
||||
case_number: theCase.case_number,
|
||||
user_id: theCase.user_id,
|
||||
user_name: theCase.user_name,
|
||||
mod_id: theCase.mod_id,
|
||||
mod_name: theCase.mod_name,
|
||||
type: theCase.type,
|
||||
created_at: theCase.created_at,
|
||||
is_hidden: theCase.is_hidden,
|
||||
pp_id: theCase.pp_id,
|
||||
pp_name: theCase.pp_name,
|
||||
|
||||
notes: theCase.notes.map((note) => ({
|
||||
mod_id: note.mod_id,
|
||||
mod_name: note.mod_name,
|
||||
body: note.body,
|
||||
created_at: note.created_at,
|
||||
})),
|
||||
});
|
||||
}
|
||||
} while (cases.length === exportBatchSize);
|
||||
|
||||
res.json(data);
|
||||
},
|
||||
);
|
||||
|
||||
guildRouter.use("/", importExportRouter);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue