Skip to content

Commit

Permalink
feat: Automate importing and exporting
Browse files Browse the repository at this point in the history
  • Loading branch information
thebongy committed May 30, 2018
1 parent 625553e commit 03681b8
Show file tree
Hide file tree
Showing 12 changed files with 2,424 additions and 2,210 deletions.
4,433 changes: 2,252 additions & 2,181 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
"nedb": "^1.8.0",
"nunjucks": "^3.1.2",
"opn": "^5.3.0",
"request": "^2.87.0",
"request-promise": "^4.2.2",
"rimraf": "^2.6.2",
"shortid": "^2.2.8"
},
Expand All @@ -33,6 +35,7 @@
"@types/nunjucks": "^3.0.0",
"@types/opn": "^5.1.0",
"@types/puppeteer": "^1.2.1",
"@types/request-promise": "^4.1.41",
"@types/rimraf": "^2.0.2",
"@types/shortid": "0.0.29",
"adm-zip": "^0.4.11",
Expand Down
37 changes: 37 additions & 0 deletions src/client/views/election-select.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{% extends "index.html" %}

{% block mainContent %}
<div class="election-import-container col-lg-10 mx-auto">
<div class="election-import-container card">
<div class="card-header bg-dark">
<h3>Select elections to import from edison-central:</h3>
</div>
<div class="card-body">
<form id="poll-select-form" action="{{ formURL }}" data-method="GET">
<input type="hidden" name="serverURL" value="{{ serverURL }}">
<div class="form-group">
{% for election in elections -%}
<div class="form-check">
<input name= "electionID" class="form-check-input" type="radio" value="{{ election.id }}" id="check{{ election.id }}">
<label class="form-check-label" for="check{{ poll.id }}">{{ election.name }}</label>
</div>
{%- endfor %}
</div>
<button id="exportPollsBtn" type="submit" class="btn btn-success">
<i class="{{ submitBtnIcon }}"></i>
{{ submitBtnText }}
</button>
</form>
</div>
</div>
</div>
{%- endblock %}

{% block stylesheets %}
<link rel="stylesheet" href="/assets/styles/forms.css">
<link rel="stylesheet" href="/assets/styles/forms/poll-select.css">
{%- endblock %}

{% block scripts %}
<script src="/assets/scripts/poll-select-form.bundle.js"></script>
{%- endblock %}
11 changes: 10 additions & 1 deletion src/client/views/import.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ <h3>Import data</h3>
<p class="text-danger">
A file with name {{ alreadyImported }} has already been imported. Please import
data to <span class="text-bold"> start a new voting session only.</span> Any non exported
data will be lost! We recommend you <a href="/export">export</a> data first.
data will be lost! We recommend you <a href="/external/export">export</a> data first.
</p>
{% endif %}
<form class="import-form" action="/import" id="import-form" data-method="POST" enctype="multipart/form-data" data-redirect="/selectPolls">
Expand All @@ -26,6 +26,15 @@ <h3>Import data</h3>
</button>
</div>
</form>
<br>
<h3>OR</h3>
<form action="/external/import/select" class="form">
<label for="ip-input">Enter IP address of edison-central</label>
<input class="form-control" id="ip-input" name="serverIP" type="text">
<button type="submit" class="btn btn-dark">
Import
</button>
</form>
</div>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/client/views/login.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
</head>
<body>
<form action="/users/login" method="POST" class="form-signin">
<h1 class="h2 mb-2 font-weight-normal">Edison Central</h1>
<h1 class="h2 mb-2 font-weight-normal">Edison Booth</h1>
<p>Enter your Password</p>
<div id="errorDisplay" class="alert alert-danger" role="alert">
<h6 id="errorTitle" class="alert-heading"></h6>
Expand Down
2 changes: 1 addition & 1 deletion src/client/views/navbar.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
{% for link, name, faIcon in [
["/", "Home", "fas fa-home"],
["/vote", "Vote", "fas fa-users"],
["/export", "Export", "fas fa-download"]
["/external/export", "Export", "fas fa-download"]
] %}
<li class="nav-item">
<a href="{{ link }}" class="nav-link {% if currentURL === link %}active{% endif %}">
Expand Down
2 changes: 2 additions & 0 deletions src/server/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import morgan = require("morgan");
import nunjucks = require("nunjucks");

import { config } from "../config";
import { router as externalRouter } from "./routes/externalRouter";
import { router as mainRouter } from "./routes/mainRouter";
import { router as userRouter } from "./routes/userRouter";

Expand Down Expand Up @@ -39,6 +40,7 @@ app.use("/images", express.static(config.database.images));

app.use("/", mainRouter);
app.use("/users", userRouter);
app.use("/external", externalRouter);

export function runServer(callBack: () => void): void {
app.listen(config.port, callBack);
Expand Down
12 changes: 12 additions & 0 deletions src/server/model/elections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { promisify } from "util";
import { config } from "../../config";
import { Candidate, Election, Image, Poll } from "../../shared/models";
import { dbfind, dbInsert, dbRemove, dbUpdate } from "../utils/database";
import { unzip } from "../utils/zipAndUnzip";

const copyFilePromise = promisify(copyFile);
const mkdirPromise = promisify(mkdir);
Expand All @@ -27,6 +28,17 @@ export function zipElection(
zipper.writeZip(destination);
}

export async function unzipElection(zipPath: string) {
await Promise.all([
promisify(rimraf)(config.database.images),
promisify(rimraf)(path.join(config.database.dir, "*.db"))
]);

unzip(zipPath, config.database.dir);

await unlinkPromise(zipPath);

}
class ElectionsDatastore {
public db: Datastore;
private currentFile: string;
Expand Down
1 change: 0 additions & 1 deletion src/server/model/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,5 @@ export interface UserData {
* @returns {database.UserData}
*/
export async function getUserData(): Promise<UserData> {
console.log(config.database.users);
return await getData(config.database.users);
}
93 changes: 93 additions & 0 deletions src/server/routes/externalRouter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import express = require("express");
import fs = require("fs");
import ip = require("ip");
import Datastore = require("nedb");
import path = require("path");
import request = require("request");
import rp = require("request-promise");
import shortid = require("shortid");

import { config } from "../../config";
import { db, unzipElection } from "../model/elections";
import { asyncMiddleware } from "../utils/asyncMiddleware";
import { dbRemove } from "../utils/database";
import { ERRORS, JSONResponse } from "../utils/JSONResponse";
import { zipFile } from "../utils/zipAndUnzip";

export const router = express.Router();

router.get("/import/select", asyncMiddleware(async (req, res) => {
const serverIP = req.query.serverIP;
const URL = "http://" + serverIP + ":3000";

let resp;
try {
resp = await rp(URL + "/identity");
} catch (err) {
console.log(err);
return JSONResponse.Error(res, ERRORS.ResourceError.NotFound);
}
let identity: any;
try {
identity = JSON.parse(resp);
} catch (err) {
return JSONResponse.Error(res, ERRORS.ResourceError.NotFound);
}
if (identity.name === "edison-central") {
const electionData = JSON.parse(await rp(URL + "/external/getElections"));
res.render("election-select.html", {
appName: config.appName,
lanIP: ip.address(),
serverURL: URL,
pageTitle: "Select Elections",
currentURL: req.url,
formURL: "/external/import/download",
elections: electionData,
submitBtnText: "Begin voting",
submitBtnIcon: "fas fa-angle-double-right"
});

} else {
return JSONResponse.Error(res, ERRORS.ResourceError.NotFound);
}
}));

router.get("/import/download", asyncMiddleware(async (req, res) => {
const electionID = req.query.electionID;
const URL = req.query.serverURL;
const downloadPath = path.join(
config.database.temp,
shortid.generate() + ".zip");
const r = request(
URL + `/external/elections/${electionID}/export?all=true`);
r
.on("error", () => {
JSONResponse.Error(res, ERRORS.ResourceError.NotFound);
})
.pipe(fs.createWriteStream(downloadPath))
.on("finish", () => {
unzipElection(downloadPath).then(() => res.redirect("/vote"));
});
}));

router.get("/export", asyncMiddleware(async (_REQ, res) => {
const oldDB = db.loadDB();
if (oldDB === undefined) {
return JSONResponse.Error(res, ERRORS.ResourceError.NotFound);
}
const dbCopy = path.join(config.database.temp, shortid.generate() + ".db");
fs.copyFileSync(oldDB, dbCopy);
const newDB = new Datastore({
filename: dbCopy,
autoload: true
});

// Don't include unselected candidates
await dbRemove(newDB, { show: false }, { multi: true});

const zipPath = zipFile(dbCopy, config.database.temp);
res.download(zipPath, () => {
fs.unlinkSync(zipPath);
fs.unlinkSync(dbCopy);
});
}));
29 changes: 8 additions & 21 deletions src/server/routes/mainRouter.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
import express = require("express");
import fs = require("fs");
import ip = require("ip");
import multer = require("multer");
import path = require("path");
import rimraf = require("rimraf");
import shortid = require("shortid");

import { promisify } from "util";

import { config } from "../../config";
import { Candidate } from "../../shared/models";
import { db } from "../model/elections";
import { unzip, zipFile } from "../utils/zipAndUnzip";
import { db, unzipElection } from "../model/elections";

import { asyncMiddleware } from "../utils/asyncMiddleware";
import { ERRORS, JSONResponse } from "../utils/JSONResponse";

export const router = express.Router();

router.use((_REQ, res, next) => {
res.setHeader("Cache-Control", "no-cache,no-store,max-age=0," +
"must-revalidate");
next();
});

function checkIfImported(
_1: express.Request,
res: express.Response,
Expand Down Expand Up @@ -64,15 +65,8 @@ router.get("/", lockMiddleware, (req, res) => {

router.post("/import", upload.single("importedData"),
asyncMiddleware(async (req, res) => {
await Promise.all([
promisify(rimraf)(config.database.images),
promisify(rimraf)(path.join(config.database.dir, "*.db"))
]);

unzip(req.file.path, config.database.dir);

await unzipElection(req.file.path);
JSONResponse.Data(res, {});
await promisify(fs.unlink)(req.file.path);
})
);

Expand Down Expand Up @@ -109,13 +103,6 @@ router.post("/vote", checkIfImported, asyncMiddleware(async (req, res) => {

}));

router.get("/export", checkIfImported, asyncMiddleware(async (_REQ, res) => {
const zipPath = zipFile(db.loadDB(), config.database.temp);
res.download(zipPath, () => {
fs.unlinkSync(zipPath);
});
}));

router.get(
"/selectPolls",
checkIfImported,
Expand Down
9 changes: 5 additions & 4 deletions src/server/utils/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,12 @@ export function dbInsert<T>(datastore: Datastore, doc: T): Promise<T> {
});
}

export function dbRemove(datastore: Datastore, query: any) {
export function dbRemove(datastore: Datastore, query: any, options?: any) {
if (options === undefined) {
options = {};
}
return new Promise((resolve, reject) => {
datastore.remove(query, (err: any, numRemoved: number) => {
datastore.remove(query, options, (err: any, numRemoved: number) => {
if (err) {
reject(err);
} else {
Expand Down Expand Up @@ -74,8 +77,6 @@ export async function getData(
let data;
try {
data = JSON.parse(await fileHandler.readFile(dataPath, cryptKey));
console.log("HERE");
console.log(data);
} catch (error) {
if (error.code === "ENOENT") {
return {};
Expand Down

0 comments on commit 03681b8

Please sign in to comment.