mirror of
https://github.com/zkldi/Tachi.git
synced 2026-10-12 17:28:31 +03:00
??
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
import t from "tap";
|
||||
import mockApi from "../../test-utils/mock-api";
|
||||
import { TestingIIDXEamusementCSV26 } from "../../test-utils/test-data";
|
||||
|
||||
t.test("POST /internal-api/import/file", async (t) => {
|
||||
let res = await mockApi
|
||||
.post("/internal-api/import/file")
|
||||
.attach("file", TestingIIDXEamusementCSV26, "my_csv.csv");
|
||||
|
||||
t.end();
|
||||
});
|
||||
@@ -1,133 +1,133 @@
|
||||
import { Router, NextFunction, Request, Response } from "express";
|
||||
import { FileUploadImportTypes } from "kamaitachi-common";
|
||||
import multer, { MulterError } from "multer";
|
||||
import Prudence from "prudence";
|
||||
import { Logger } from "winston";
|
||||
import { GetUserWithID } from "../../core/user-core";
|
||||
import CreateLogCtx from "../../logger";
|
||||
import prValidate from "../../middleware/prudence-validate";
|
||||
import { RequireLoggedIn } from "../../middleware/require-logged-in";
|
||||
import ScoreImportFatalError from "../../score-import/framework/core/score-import-error";
|
||||
import ScoreImportMain from "../../score-import/framework/score-import-main";
|
||||
import ParseEamusementCSV from "../../score-import/import-types/csv-eamusement-iidx/parser";
|
||||
import serverConfig from "../../server-config";
|
||||
|
||||
const logger = CreateLogCtx("import.ts");
|
||||
|
||||
const router = Router({ mergeParams: true });
|
||||
|
||||
// multer config
|
||||
const upload = multer({ limits: { fileSize: 1024 * 1024 * 16 } }); // basically 16mb
|
||||
const uploadMW = upload.single("scoreData");
|
||||
|
||||
const parseMultipartScoredata = (req: Request, res: Response, next: NextFunction) => {
|
||||
uploadMW(req, res, (err: unknown) => {
|
||||
if (err instanceof MulterError) {
|
||||
logger.error(`Multer Error.`, { err });
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
description: "File provided was too large, corrupt, or simply broken.",
|
||||
});
|
||||
} else if (err) {
|
||||
logger.error(`Unknown file import error: ${err}`, { err });
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
description: `An internal server error has occured.`,
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Import scores from a file. Expects the post request to be multipart, and to provide a scoreData file.
|
||||
* @name /internal-api/import/file
|
||||
*/
|
||||
router.post(
|
||||
"/file",
|
||||
RequireLoggedIn,
|
||||
parseMultipartScoredata,
|
||||
prValidate({
|
||||
importType: Prudence.isIn(serverConfig.supportedFileUploads),
|
||||
}),
|
||||
async (req, res) => {
|
||||
if (!req.file) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
description: `No file provided.`,
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
let importType = req.body.importType as FileUploadImportTypes;
|
||||
|
||||
const inputParser = async (logger: Logger) =>
|
||||
await ResolveFileUploadData(importType, req.file, req.body, logger);
|
||||
|
||||
const userDoc = await GetUserWithID(req.session.ktchi!.userID);
|
||||
|
||||
if (!userDoc) {
|
||||
logger.severe(
|
||||
`User ${req.session.ktchi!.userID} does not have an associated user document.`
|
||||
);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
description: "An internal error has occured.",
|
||||
});
|
||||
}
|
||||
|
||||
let importDocument = await ScoreImportMain(userDoc, importType, inputParser);
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
description: "Import successful.",
|
||||
body: {
|
||||
import: importDocument,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
if (err instanceof ScoreImportFatalError) {
|
||||
return res.status(err.statusCode).json({
|
||||
success: false,
|
||||
description: err.message,
|
||||
});
|
||||
} else {
|
||||
logger.error(err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
description: `An internal service error has occured.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Resolves the data from a file upload into an iterable,
|
||||
* The appropriate processing function to map that iterable over,
|
||||
* and and any context the processing may need (such as playtype)
|
||||
*
|
||||
* This also performs validation on the type of file uploaded.
|
||||
* @param importType - The type of import request this was.
|
||||
* @param fileData - The data sent by the user.
|
||||
* @param body - Other data passed by the user in the request body.
|
||||
*/
|
||||
export function ResolveFileUploadData(
|
||||
importType: FileUploadImportTypes,
|
||||
fileData: Express.Multer.File,
|
||||
body: Record<string, unknown>,
|
||||
logger: Logger
|
||||
) {
|
||||
switch (importType) {
|
||||
case "csv:eamusement-iidx":
|
||||
return ParseEamusementCSV(fileData, body, logger);
|
||||
default:
|
||||
logger.error(
|
||||
`importType ${importType} made it into ResolveFileUploadData, but should have been rejected by Prudence.`
|
||||
);
|
||||
throw new ScoreImportFatalError(400, `Invalid importType of ${importType}.`);
|
||||
}
|
||||
}
|
||||
|
||||
export default router;
|
||||
import { Router, NextFunction, Request, Response } from "express";
|
||||
import { FileUploadImportTypes } from "kamaitachi-common";
|
||||
import multer, { MulterError } from "multer";
|
||||
import Prudence from "prudence";
|
||||
import { Logger } from "winston";
|
||||
import { GetUserWithID } from "../../core/user-core";
|
||||
import CreateLogCtx from "../../logger";
|
||||
import prValidate from "../../middleware/prudence-validate";
|
||||
import { RequireLoggedIn } from "../../middleware/require-logged-in";
|
||||
import ScoreImportFatalError from "../../score-import/framework/core/score-import-error";
|
||||
import ScoreImportMain from "../../score-import/framework/score-import-main";
|
||||
import ParseEamusementCSV from "../../score-import/import-types/csv-eamusement-iidx/parser";
|
||||
import serverConfig from "../../server-config";
|
||||
|
||||
const logger = CreateLogCtx("import.ts");
|
||||
|
||||
const router = Router({ mergeParams: true });
|
||||
|
||||
// multer config
|
||||
const upload = multer({ limits: { fileSize: 1024 * 1024 * 16 } }); // basically 16mb
|
||||
const uploadMW = upload.single("scoreData");
|
||||
|
||||
const parseMultipartScoredata = (req: Request, res: Response, next: NextFunction) => {
|
||||
uploadMW(req, res, (err: unknown) => {
|
||||
if (err instanceof MulterError) {
|
||||
logger.error(`Multer Error.`, { err });
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
description: "File provided was too large, corrupt, or simply broken.",
|
||||
});
|
||||
} else if (err) {
|
||||
logger.error(`Unknown file import error: ${err}`, { err });
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
description: `An internal server error has occured.`,
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Import scores from a file. Expects the post request to be multipart, and to provide a scoreData file.
|
||||
* @name /internal-api/import/file
|
||||
*/
|
||||
router.post(
|
||||
"/file",
|
||||
RequireLoggedIn,
|
||||
parseMultipartScoredata,
|
||||
prValidate({
|
||||
importType: Prudence.isIn(serverConfig.supportedFileUploads),
|
||||
}),
|
||||
async (req, res) => {
|
||||
if (!req.file) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
description: `No file provided.`,
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
let importType = req.body.importType as FileUploadImportTypes;
|
||||
|
||||
const inputParser = async (logger: Logger) =>
|
||||
await ResolveFileUploadData(importType, req.file, req.body, logger);
|
||||
|
||||
const userDoc = await GetUserWithID(req.session.ktchi!.userID);
|
||||
|
||||
if (!userDoc) {
|
||||
logger.severe(
|
||||
`User ${req.session.ktchi!.userID} does not have an associated user document.`
|
||||
);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
description: "An internal error has occured.",
|
||||
});
|
||||
}
|
||||
|
||||
let importDocument = await ScoreImportMain(userDoc, importType, inputParser);
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
description: "Import successful.",
|
||||
body: {
|
||||
import: importDocument,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
if (err instanceof ScoreImportFatalError) {
|
||||
return res.status(err.statusCode).json({
|
||||
success: false,
|
||||
description: err.message,
|
||||
});
|
||||
} else {
|
||||
logger.error(err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
description: `An internal service error has occured.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Resolves the data from a file upload into an iterable,
|
||||
* The appropriate processing function to map that iterable over,
|
||||
* and and any context the processing may need (such as playtype)
|
||||
*
|
||||
* This also performs validation on the type of file uploaded.
|
||||
* @param importType - The type of import request this was.
|
||||
* @param fileData - The data sent by the user.
|
||||
* @param body - Other data passed by the user in the request body.
|
||||
*/
|
||||
export function ResolveFileUploadData(
|
||||
importType: FileUploadImportTypes,
|
||||
fileData: Express.Multer.File,
|
||||
body: Record<string, unknown>,
|
||||
logger: Logger
|
||||
) {
|
||||
switch (importType) {
|
||||
case "csv:eamusement-iidx":
|
||||
return ParseEamusementCSV(fileData, body, logger);
|
||||
default:
|
||||
logger.error(
|
||||
`importType ${importType} made it into ResolveFileUploadData, but should have been rejected by Prudence.`
|
||||
);
|
||||
throw new ScoreImportFatalError(400, `Invalid importType of ${importType}.`);
|
||||
}
|
||||
}
|
||||
|
||||
export default router;
|
||||
|
||||
Reference in New Issue
Block a user