feat: add file download support

feat: backend: new api to download file such as planet

/PROJECT/frontend/down_folder/
http://locahost:4000/downfile/:downfilename

feat: frontend: add file download component for ease use

Signed-off-by: Syrone Wong <wong.syrone@gmail.com>
This commit is contained in:
Syrone Wong 2022-08-07 20:41:31 +08:00
commit 17bf258ea1
7 changed files with 147 additions and 0 deletions

View file

@ -12,6 +12,7 @@ const authRoutes = require("./routes/auth");
const networkRoutes = require("./routes/network");
const memberRoutes = require("./routes/member");
const controllerRoutes = require("./routes/controller");
const downFileRoutes = require("./routes/downfile");
const app = express();
@ -64,6 +65,7 @@ routerController.use("", controllerRoutes);
app.use("/auth", authRoutes);
app.use("/api", routerAPI); // offical SaaS API compatible
app.use("/controller", routerController); // other controller-specific routes
app.use("/downfile", downFileRoutes); // file download routes
// error handlers
app.get("*", async function (req, res) {

View file

@ -0,0 +1,21 @@
const express = require("express");
const path = require("path");
const router = express.Router();
const auth = require("../services/auth");
router.get("/:downfilename", async function (req, res) {
const ret = await auth.isUserLoggedIn(req);
if (ret) {
const filename = req.params.downfilename;
res.sendFile(
path.join(__dirname, "..", "..", "frontend", "down_folder", filename)
);
} else {
res
.status(401)
.json({ error: "401 Not authorized, must Login to download" });
}
});
module.exports = router;

View file

@ -35,3 +35,16 @@ async function isAuthorized(req, res, next) {
}
}
}
exports.isUserLoggedIn = isUserLoggedIn;
async function isUserLoggedIn(req) {
if (process.env.ZU_DISABLE_AUTH === "true") {
// assuming logged in
return true;
}
if (!req.token) {
return false;
}
const user = await db.get("users").find({ token: req.token }).value();
return !!user ? true : false;
}

View file

View file

@ -5,6 +5,7 @@ import { Divider, Button, Grid, Typography, Box } from "@material-ui/core";
import useStyles from "./HomeLoggedIn.styles";
import NetworkButton from "./components/NetworkButton";
import DownloadFile from "./components/DownloadFile";
import API from "utils/API";
import { generateNetworkConfig } from "utils/NetworkConfig";
@ -40,6 +41,8 @@ function HomeLoggedIn() {
>
Create A Network
</Button>
<Divider orientation="vertical" />
<DownloadFile />
<Divider />
<Grid container spacing={3} className={classes.container}>
<Grid item xs={6}>

View file

@ -0,0 +1,107 @@
import { useState } from "react";
import { useLocalStorage } from "react-use";
import { TextField, Button, Snackbar } from "@material-ui/core";
import axios from "axios";
function DownloadFile() {
const [snackbarOpen, setSnackbarOpen] = useState(false);
const [filename, setFilename] = useState("planet");
const [errormsg, setErrormsg] = useState("");
const [token] = useLocalStorage("token", null);
const downloadFile = async (fileName) => {
if (!!!token) {
await handleDownFileErr(`Invalid token`);
return;
}
if (typeof filename === "undefined" || filename.length === 0) {
await handleDownFileErr(`Invalid file name [${filename}]`);
return;
}
let ret;
ret = await axios
.create({
baseURL: `/downfile/`,
responseType: "arraybuffer",
withCredentials: "true",
headers:
localStorage.getItem("disableAuth") === "true"
? {}
: {
Authorization: `Bearer ${token}`,
},
})
.get(`${fileName}`)
.then((resp) => {
const blobUrl = window.URL.createObjectURL(
new Blob([resp.data], { type: "application/octet-stream" })
);
const tmpLink = document.createElement("a");
tmpLink.style.display = "none";
tmpLink.href = blobUrl;
tmpLink.setAttribute("download", `${fileName}`);
if (typeof tmpLink.download === "undefined") {
tmpLink.setAttribute("target", "_blank");
}
document.body.appendChild(tmpLink);
tmpLink.click();
document.body.removeChild(tmpLink);
window.URL.revokeObjectURL(blobUrl);
})
.catch(async (e) => {
let errmsg = `downfile(${fileName}): ${e}: ${ret}`;
await handleDownFileErr(errmsg);
});
};
const handleDownFileBtnClick = async () => {
await downloadFile(filename);
};
const handleDownFileErr = async (errormsg) => {
setErrormsg(errormsg);
setSnackbarOpen(true);
let sleep = function (ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
};
await sleep(3 * 1000);
setSnackbarOpen(false);
setErrormsg("");
};
return (
<>
<TextField
value={filename}
onChange={(e) => {
setFilename(e.target.value);
}}
margin="dense"
label="File name to download"
type="text"
/>
<Button
variant="contained"
color="primary"
onClick={handleDownFileBtnClick}
>
Download file
</Button>
<Snackbar
open={snackbarOpen}
anchorOrigin={{
vertical: "top",
horizontal: "center",
}}
message={errormsg}
/>
</>
);
}
export default DownloadFile;

View file

@ -0,0 +1 @@
export { default } from "./DownloadFile";