Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 52 additions & 10 deletions beat-editor/backend/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ const cors = require("cors");
const fs = require("fs");
const path = require("path");
const bodyParser = require("body-parser");
const multer = require("multer");
const upload = multer({ dest: "temp/" });
const app = express();

const validateEditsJson = require("./helper/validateEditsJson");

// Middleware to parse JSON bodies
app.use(bodyParser.json());

Expand All @@ -18,13 +22,13 @@ app.get("/fetch-file", async (req, res) => {
try {
const dataFiles = await fs.promises.readdir(dataDir);
const fileDirJsonFiles = dataFiles.filter((file) =>
file.endsWith("_edit.json")
file.endsWith("_edit.json"),
);

// Extract the name of the _edit.json file
const jsonFileName = fileDirJsonFiles[0].substring(
0,
fileDirJsonFiles[0].length - 10
fileDirJsonFiles[0].length - 10,
);

// Skip the /saved folder if it doesn't exist
Expand All @@ -33,9 +37,7 @@ app.get("/fetch-file", async (req, res) => {
: [];
// Grab the _edited.json file that matches the _edit.json file
const savedDirJsonFiles = fs.existsSync(savedDir)
? savedFiles.filter((file) =>
file.endsWith(`${jsonFileName}_edited.json`)
)
? savedFiles.filter((file) => file.endsWith(`_edited.json`))
: [];

if (dataFiles.length === 0) {
Expand All @@ -50,7 +52,7 @@ app.get("/fetch-file", async (req, res) => {
fileName: file.replace("_edit.json", ""),
data: JSON.parse(fileContent),
};
})
}),
);

// If there's a '_edited.json' file, take the data in there too to replot
Expand All @@ -64,16 +66,16 @@ app.get("/fetch-file", async (req, res) => {
fileName: file,
data: JSON.parse(fileContent),
};
})
}),
)
: null;

// Extract unique segment options
const segmentOptions = [
...new Set(
allFileData.flatMap((file) =>
file.data.map((o) => o.Segment.toString())
)
file.data.map((o) => o.Segment.toString()),
),
),
];

Expand All @@ -97,7 +99,7 @@ app.post("/saved", async (req, res) => {
__dirname,
"..",
"saved",
`${fileName}_edited.json`
`${fileName}_edited.json`,
);

try {
Expand Down Expand Up @@ -141,6 +143,46 @@ app.post("/saved", async (req, res) => {
}
});

app.post("/import-edits", upload.single("file"), async (req, res) => {
try {
const savedDir = path.join(__dirname, "..", "saved");

if (!fs.existsSync(savedDir)) {
fs.mkdirSync(savedDir);
}

const tempPath = req.file.path;
const rawData = await fs.promises.readFile(tempPath, "utf-8");
let parsedData;

try {
parsedData = JSON.parse(rawData);
} catch {
await fs.promises.unlink(tempPath);
return res.status(400).json({ message: "Invalid JSON file." });
}

const validationResult = validateEditsJson(parsedData);
if (!validationResult.ok) {
await fs.promises.unlink(tempPath);
return res.status(400).json({
message: "Invalid JSON structure.",
error: validationResult.error,
});
}

const targetPath = path.join(savedDir, req.file.originalname);

await fs.promises.rename(tempPath, targetPath);

res.status(200).json({ message: "File imported successfully." });
} catch (err) {
res
.status(500)
.json({ message: "Error importing file.", error: err.message });
}
});

// Start the server
const port = process.env.PORT || 3001;
app.listen(port, () => {
Expand Down
48 changes: 48 additions & 0 deletions beat-editor/backend/helper/validateEditsJson.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
const EDIT_TYPES = ["ADD", "DELETE", "UNUSABLE"];

/**
* validateEditsJson
*
* Validates the structure of the imported _edits.json file.
*
* @param {Array} data - The parsed JSON data to validate.
* @returns {Object} An object with 'ok' boolean and 'error' message if not valid.
*/

function validateEditsJson(data) {
if (!Array.isArray(data)) {
return { ok: false, error: "Expected an array" };
}

for (const [key, value] of data.entries()) {
if (!value || typeof value !== "object") {
return { ok: false, error: `Item ${key} is not an object` };
}

const { editType, segment, x, y } = value;

if (!EDIT_TYPES.includes(editType))
return { ok: false, error: `Item ${key} has an invalid editType` };
if (typeof segment !== "string")
return { ok: false, error: `Item ${key} has an invalid segment` };
if (typeof x !== "number" || typeof y !== "number")
return { ok: false, error: `Item ${key} has invalid x or y` };

if (editType === "UNUSABLE") {
if (
!Number.isFinite(value.from) ||
!Number.isFinite(value.to) ||
typeof value.color !== "string"
) {
return {
ok: false,
error: `Item ${key} has invalid from or to for UNUSABLE editType`,
};
}
}
}

return { ok: true };
}

module.exports = validateEditsJson;
121 changes: 120 additions & 1 deletion beat-editor/backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion beat-editor/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"license": "ISC",
"dependencies": {
"cors": "^2.8.5",
"express": "^4.19.2"
"express": "^4.19.2",
"multer": "^2.0.2"
}
}
Loading