aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSam Wilkins <samwilkins333@gmail.com>2019-10-26 18:28:38 -0400
committerSam Wilkins <samwilkins333@gmail.com>2019-10-26 18:28:38 -0400
commite6bd33867cc7f7185575666255369f55cacb9856 (patch)
treed4efbe0668d759400f44f7083562cf362706935c
parentfcf67616b9fd6f98d631f6c8eab31a19a2a2e86d (diff)
restructured route registration and added preliminary comments for exporter
-rw-r--r--src/server/ApiManagers/ApiManager.ts10
-rw-r--r--src/server/ApiManagers/ExportManager.ts133
-rw-r--r--src/server/ApiManagers/SearchManager.ts10
-rw-r--r--src/server/ApiManagers/UserManager.ts10
-rw-r--r--src/server/ApiManagers/UtilManager.ts12
-rw-r--r--src/server/RouteManager.ts15
-rw-r--r--src/server/Websocket/Websocket.ts2
-rw-r--r--src/server/index.ts96
8 files changed, 179 insertions, 109 deletions
diff --git a/src/server/ApiManagers/ApiManager.ts b/src/server/ApiManagers/ApiManager.ts
index 264c78a17..9fd726060 100644
--- a/src/server/ApiManagers/ApiManager.ts
+++ b/src/server/ApiManagers/ApiManager.ts
@@ -1,7 +1,11 @@
-import RouteManager from "../RouteManager";
+import RouteManager, { RouteInitializer } from "../RouteManager";
-export default abstract class ApiManager {
+export type Registration = (initializer: RouteInitializer) => void;
- public abstract register(router: RouteManager): void;
+export default abstract class ApiManager {
+ protected abstract initialize(register: Registration): void;
+ public register(router: RouteManager) {
+ this.initialize(router.addSupervisedRoute);
+ }
} \ No newline at end of file
diff --git a/src/server/ApiManagers/ExportManager.ts b/src/server/ApiManagers/ExportManager.ts
new file mode 100644
index 000000000..261acbbe0
--- /dev/null
+++ b/src/server/ApiManagers/ExportManager.ts
@@ -0,0 +1,133 @@
+import ApiManager, { Registration } from "./ApiManager";
+import RouteManager, { Method } from "../RouteManager";
+import RouteSubscriber from "../RouteSubscriber";
+import { RouteStore } from "../RouteStore";
+import * as Archiver from 'archiver';
+import * as express from 'express';
+import { Database } from "../database";
+import * as path from "path";
+import { DashUploadUtils } from "../DashUploadUtils";
+
+export type Hierarchy = { [id: string]: string | Hierarchy };
+export type ZipMutator = (file: Archiver.Archiver) => void | Promise<void>;
+export interface DocumentElements {
+ data: string | any[];
+ title: string;
+}
+
+export default class ExportManager extends ApiManager {
+
+ protected initialize(register: Registration): void {
+
+ register({
+ method: Method.GET,
+ subscription: new RouteSubscriber(RouteStore.imageHierarchyExport).add('docId'),
+ onValidation: async ({ req, res }) => {
+ const id = req.params.docId;
+ const hierarchy: Hierarchy = {};
+ await buildHierarchyRecursive(id, hierarchy);
+ BuildAndDispatchZip(res, zip => writeHierarchyRecursive(zip, hierarchy));
+ }
+ });
+ }
+
+}
+
+/**
+ * This utility function factors out the process
+ * of creating a zip file and sending it back to the client
+ * by piping it into a response.
+ *
+ * Learn more about piping and readable / writable streams here!
+ * https://www.freecodecamp.org/news/node-js-streams-everything-you-need-to-know-c9141306be93/
+ *
+ * @param res the writable stream response object that will transfer the generated zip file
+ * @param mutator the callback function used to actually modify and insert information into the zip instance
+ */
+export async function BuildAndDispatchZip(res: express.Response, mutator: ZipMutator): Promise<void> {
+ const zip = Archiver('zip');
+ zip.pipe(res);
+ await mutator(zip);
+ zip.finalize();
+}
+
+/**
+ * This function starts with a single document id as a seed,
+ * typically that of a collection, and then descends the entire tree
+ * of image or collection documents that are reachable from that seed.
+ * @param seedId the id of the root of the subtree we're trying to capture, interesting only if it's a collection
+ * @param hierarchy the data structure we're going to use to record the nesting of the collections and images as we descend
+ */
+
+/*
+Below is an example of the JSON hierarchy built from two images contained inside a collection titled 'a nested collection',
+following the general recursive structure shown immediately below
+{
+ "parent folder name":{
+ "first child's fild name":"first child's url"
+ ...
+ "nth child's fild name":"nth child's url"
+ }
+}
+{
+ "a nested collection (865c4734-c036-4d67-a588-c71bb43d1440)":{
+ "an image of a cat (ace99ffd-8ed8-4026-a5d5-a353fff57bdd).jpg":"https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg",
+ "1*SGJw31T5Q9Zfsk24l2yirg.gif (9321cc9b-9b3e-4cb6-b99c-b7e667340f05).gif":"https://cdn-media-1.freecodecamp.org/images/1*SGJw31T5Q9Zfsk24l2yirg.gif"
+ }
+}
+*/
+
+async function buildHierarchyRecursive(seedId: string, hierarchy: Hierarchy): Promise<void> {
+ const { title, data } = await getData(seedId);
+ const label = `${title} (${seedId})`;
+ // is the document a collection?
+ if (Array.isArray(data)) {
+ // recurse over all documents in the collection.
+ const local: Hierarchy = {}; // create a child hierarchy for this level, which will get passed in as the parent of the recursive call
+ hierarchy[label] = local; // store it at the index in the parent, so we'll end up with a map of maps of maps
+ await Promise.all(data.map(proxy => buildHierarchyRecursive(proxy.fieldId, local)));
+ } else {
+ // now, data can only be a string, namely the url of the image
+ const filename = label + path.extname(data); // this is the file name under which the output image will be stored
+ hierarchy[filename] = data;
+ }
+}
+
+async function getData(seedId: string): Promise<DocumentElements> {
+ return new Promise<DocumentElements>((resolve, reject) => {
+ Database.Instance.getDocument(seedId, async (result: any) => {
+ const { data, proto, title } = result.fields;
+ if (data) {
+ if (data.url) {
+ resolve({ data: data.url, title });
+ } else if (data.fields) {
+ resolve({ data: data.fields, title });
+ } else {
+ reject();
+ }
+ }
+ if (proto) {
+ getData(proto.fieldId).then(resolve, reject);
+ }
+ });
+ });
+}
+
+async function writeHierarchyRecursive(file: Archiver.Archiver, hierarchy: Hierarchy, prefix = "Dash Export"): Promise<void> {
+ for (const key of Object.keys(hierarchy)) {
+ const result = hierarchy[key];
+ if (typeof result === "string") {
+ let path: string;
+ let matches: RegExpExecArray | null;
+ if ((matches = /\:1050\/files\/(upload\_[\da-z]{32}.*)/g.exec(result)) !== null) {
+ path = `${__dirname}/public/files/${matches[1]}`;
+ } else {
+ const information = await DashUploadUtils.UploadImage(result);
+ path = information.mediaPaths[0];
+ }
+ file.file(path, { name: key, prefix });
+ } else {
+ await writeHierarchyRecursive(file, result, `${prefix}/${key}`);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/server/ApiManagers/SearchManager.ts b/src/server/ApiManagers/SearchManager.ts
index 15b87204c..1c4b805e5 100644
--- a/src/server/ApiManagers/SearchManager.ts
+++ b/src/server/ApiManagers/SearchManager.ts
@@ -1,5 +1,5 @@
-import ApiManager from "./ApiManager";
-import RouteManager, { Method } from "../RouteManager";
+import ApiManager, { Registration } from "./ApiManager";
+import { Method } from "../RouteManager";
import { Search } from "../Search";
var findInFiles = require('find-in-files');
import * as path from 'path';
@@ -7,9 +7,9 @@ import { uploadDirectory } from "..";
export default class SearchManager extends ApiManager {
- public register(router: RouteManager): void {
+ protected initialize(register: Registration): void {
- router.addSupervisedRoute({
+ register({
method: Method.GET,
subscription: "/textsearch",
onValidation: async ({ req, res }) => {
@@ -29,7 +29,7 @@ export default class SearchManager extends ApiManager {
}
});
- router.addSupervisedRoute({
+ register({
method: Method.GET,
subscription: "/search",
onValidation: async ({ req, res }) => {
diff --git a/src/server/ApiManagers/UserManager.ts b/src/server/ApiManagers/UserManager.ts
index bb8837dc6..dd1e50133 100644
--- a/src/server/ApiManagers/UserManager.ts
+++ b/src/server/ApiManagers/UserManager.ts
@@ -1,11 +1,12 @@
-import ApiManager from "./ApiManager";
-import RouteManager, { Method } from "../RouteManager";
+import ApiManager, { Registration } from "./ApiManager";
+import { Method } from "../RouteManager";
import { WebSocket } from "../Websocket/Websocket";
export default class UserManager extends ApiManager {
- public register(router: RouteManager): void {
- router.addSupervisedRoute({
+ protected initialize(register: Registration): void {
+
+ register({
method: Method.GET,
subscription: "/whosOnline",
onValidation: ({ res }) => {
@@ -22,6 +23,7 @@ export default class UserManager extends ApiManager {
res.send(users);
}
});
+
}
private msToTime(duration: number) {
diff --git a/src/server/ApiManagers/UtilManager.ts b/src/server/ApiManagers/UtilManager.ts
index 79b904e8a..a3f802b20 100644
--- a/src/server/ApiManagers/UtilManager.ts
+++ b/src/server/ApiManagers/UtilManager.ts
@@ -1,13 +1,13 @@
-import ApiManager from "./ApiManager";
-import RouteManager, { Method } from "../RouteManager";
+import ApiManager, { Registration } from "./ApiManager";
+import { Method } from "../RouteManager";
import { exec } from 'child_process';
import { command_line } from "../ActionUtilities";
export default class UtilManager extends ApiManager {
- public register(router: RouteManager): void {
+ protected initialize(register: Registration): void {
- router.addSupervisedRoute({
+ register({
method: Method.GET,
subscription: "/pull",
onValidation: ({ res }) => {
@@ -21,7 +21,7 @@ export default class UtilManager extends ApiManager {
}
});
- router.addSupervisedRoute({
+ register({
method: Method.GET,
subscription: "/buxton",
onValidation: ({ res }) => {
@@ -35,7 +35,7 @@ export default class UtilManager extends ApiManager {
},
});
- router.addSupervisedRoute({
+ register({
method: Method.GET,
subscription: "/version",
onValidation: ({ res }) => {
diff --git a/src/server/RouteManager.ts b/src/server/RouteManager.ts
index b3864e89c..ef083a88a 100644
--- a/src/server/RouteManager.ts
+++ b/src/server/RouteManager.ts
@@ -2,7 +2,6 @@ import RouteSubscriber from "./RouteSubscriber";
import { RouteStore } from "./RouteStore";
import { DashUserModel } from "./authentication/models/user_model";
import * as express from 'express';
-import { Opt } from "../new_fields/Doc";
export enum Method {
GET,
@@ -41,15 +40,10 @@ export default class RouteManager {
}
/**
- * Please invoke this function when adding a new route to Dash's server.
- * It ensures that any requests leading to or containing user-sensitive information
- * does not execute unless Passport authentication detects a user logged in.
- * @param method whether or not the request is a GET or a POST
- * @param handler the action to invoke, recieving a DashUserModel and, as expected, the Express.Request and Express.Response
- * @param onRejection an optional callback invoked on return if no user is found to be logged in
- * @param subscribers the forward slash prepended path names (reference and add to RouteStore.ts) that will all invoke the given @param handler
+ *
+ * @param initializer
*/
- addSupervisedRoute(initializer: RouteInitializer) {
+ addSupervisedRoute = (initializer: RouteInitializer): void => {
const { method, subscription, onValidation, onUnauthenticated, onError } = initializer;
const isRelease = this._isRelease;
let supervised = async (req: express.Request, res: express.Response) => {
@@ -72,6 +66,9 @@ export default class RouteManager {
req.session!.target = target;
if (onUnauthenticated) {
await tryExecute(onUnauthenticated, core);
+ if (!res.headersSent) {
+ res.redirect(RouteStore.login);
+ }
} else {
res.redirect(RouteStore.login);
}
diff --git a/src/server/Websocket/Websocket.ts b/src/server/Websocket/Websocket.ts
index 2461dd8d5..cd2813d99 100644
--- a/src/server/Websocket/Websocket.ts
+++ b/src/server/Websocket/Websocket.ts
@@ -4,7 +4,7 @@ import { Client } from "../Client";
import { Socket } from "socket.io";
import { Database } from "../database";
import { Search } from "../Search";
-import io from 'socket.io';
+import * as io from 'socket.io';
import YoutubeApi from "../apis/youtube/youtubeApiSample";
import { youtubeApiKey } from "..";
diff --git a/src/server/index.ts b/src/server/index.ts
index 93f4238bc..384800f23 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -33,12 +33,11 @@ import UtilManager from './ApiManagers/UtilManager';
import SearchManager from './ApiManagers/SearchManager';
import UserManager from './ApiManagers/UserManager';
import { WebSocket } from './Websocket/Websocket';
+import ExportManager from './ApiManagers/ExportManager';
+import ApiManager from './ApiManagers/ApiManager';
export let youtubeApiKey: string;
-export type Hierarchy = { [id: string]: string | Hierarchy };
-export type ZipMutator = (file: Archiver.Archiver) => void | Promise<void>;
-
export interface NewMediaItem {
description: string;
simpleMediaItem: {
@@ -72,9 +71,13 @@ async function PreliminaryFunctions() {
}
function routeSetter(router: RouteManager) {
- new UtilManager().register(router);
- new SearchManager().register(router);
- new UserManager().register(router);
+ const managers: ApiManager[] = [
+ new UtilManager(),
+ new SearchManager(),
+ new UserManager(),
+ new ExportManager()
+ ];
+ managers.forEach(manager => manager.register(router));
WebSocket.initialize(serverPort, router.isRelease);
@@ -154,77 +157,6 @@ function routeSetter(router: RouteManager) {
router.addSupervisedRoute({
method: Method.GET,
- subscription: new RouteSubscriber(RouteStore.imageHierarchyExport).add('docId'),
- onValidation: async ({ req, res }) => {
- const id = req.params.docId;
- const hierarchy: Hierarchy = {};
- await targetedVisitorRecursive(id, hierarchy);
- BuildAndDispatchZip(res, async zip => {
- await hierarchyTraverserRecursive(zip, hierarchy);
- });
- }
- });
-
- const BuildAndDispatchZip = async (res: Response, mutator: ZipMutator): Promise<void> => {
- const zip = Archiver('zip');
- zip.pipe(res);
- await mutator(zip);
- return zip.finalize();
- };
-
- const targetedVisitorRecursive = async (seedId: string, hierarchy: Hierarchy): Promise<void> => {
- const local: Hierarchy = {};
- const { title, data } = await getData(seedId);
- const label = `${title} (${seedId})`;
- if (Array.isArray(data)) {
- hierarchy[label] = local;
- await Promise.all(data.map(proxy => targetedVisitorRecursive(proxy.fieldId, local)));
- } else {
- hierarchy[label + path.extname(data)] = data;
- }
- };
-
- const getData = async (seedId: string): Promise<{ data: string | any[], title: string }> => {
- return new Promise<{ data: string | any[], title: string }>((resolve, reject) => {
- Database.Instance.getDocument(seedId, async (result: any) => {
- const { data, proto, title } = result.fields;
- if (data) {
- if (data.url) {
- resolve({ data: data.url, title });
- } else if (data.fields) {
- resolve({ data: data.fields, title });
- } else {
- reject();
- }
- }
- if (proto) {
- getData(proto.fieldId).then(resolve, reject);
- }
- });
- });
- };
-
- const hierarchyTraverserRecursive = async (file: Archiver.Archiver, hierarchy: Hierarchy, prefix = "Dash Export"): Promise<void> => {
- for (const key of Object.keys(hierarchy)) {
- const result = hierarchy[key];
- if (typeof result === "string") {
- let path: string;
- let matches: RegExpExecArray | null;
- if ((matches = /\:1050\/files\/(upload\_[\da-z]{32}.*)/g.exec(result)) !== null) {
- path = `${__dirname}/public/files/${matches[1]}`;
- } else {
- const information = await DashUploadUtils.UploadImage(result);
- path = information.mediaPaths[0];
- }
- file.file(path, { name: key, prefix });
- } else {
- await hierarchyTraverserRecursive(file, result, `${prefix}/${key}`);
- }
- }
- };
-
- router.addSupervisedRoute({
- method: Method.GET,
subscription: new RouteSubscriber("/downloadId").add("docId"),
onValidation: async ({ req, res }) => {
res.set('Content-disposition', `attachment;`);
@@ -600,22 +532,24 @@ function routeSetter(router: RouteManager) {
router.addSupervisedRoute({
method: Method.GET,
subscription: RouteStore.delete,
- onValidation: ({ res, isRelease }) => {
+ onValidation: async ({ res, isRelease }) => {
if (isRelease) {
return _permission_denied(res, deletionPermissionError);
}
- WebSocket.deleteFields().then(() => res.redirect(RouteStore.home));
+ await WebSocket.deleteFields();
+ res.redirect(RouteStore.home);
}
});
router.addSupervisedRoute({
method: Method.GET,
subscription: RouteStore.deleteAll,
- onValidation: ({ res, isRelease }) => {
+ onValidation: async ({ res, isRelease }) => {
if (isRelease) {
return _permission_denied(res, deletionPermissionError);
}
- WebSocket.deleteAll().then(() => res.redirect(RouteStore.home));
+ await WebSocket.deleteAll();
+ res.redirect(RouteStore.home);
}
});