diff options
Diffstat (limited to 'src/server/ApiManagers')
-rw-r--r-- | src/server/ApiManagers/ApiManager.ts | 10 | ||||
-rw-r--r-- | src/server/ApiManagers/ExportManager.ts | 133 | ||||
-rw-r--r-- | src/server/ApiManagers/SearchManager.ts | 10 | ||||
-rw-r--r-- | src/server/ApiManagers/UserManager.ts | 10 | ||||
-rw-r--r-- | src/server/ApiManagers/UtilManager.ts | 12 |
5 files changed, 157 insertions, 18 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 }) => { |