aboutsummaryrefslogtreecommitdiff
path: root/src/server/ApiManagers
diff options
context:
space:
mode:
Diffstat (limited to 'src/server/ApiManagers')
-rw-r--r--src/server/ApiManagers/ApiManager.ts4
-rw-r--r--src/server/ApiManagers/DataVizManager.ts15
-rw-r--r--src/server/ApiManagers/DeleteManager.ts14
-rw-r--r--src/server/ApiManagers/DownloadManager.ts385
-rw-r--r--src/server/ApiManagers/GeneralGoogleManager.ts39
-rw-r--r--src/server/ApiManagers/MongoStore.js413
-rw-r--r--src/server/ApiManagers/SearchManager.ts44
-rw-r--r--src/server/ApiManagers/SessionManager.ts75
-rw-r--r--src/server/ApiManagers/UploadManager.ts346
-rw-r--r--src/server/ApiManagers/UserManager.ts60
-rw-r--r--src/server/ApiManagers/UtilManager.ts31
11 files changed, 876 insertions, 550 deletions
diff --git a/src/server/ApiManagers/ApiManager.ts b/src/server/ApiManagers/ApiManager.ts
index 27e9de065..f55495b2e 100644
--- a/src/server/ApiManagers/ApiManager.ts
+++ b/src/server/ApiManagers/ApiManager.ts
@@ -1,4 +1,4 @@
-import { RouteInitializer } from "../RouteManager";
+import { RouteInitializer } from '../RouteManager';
export type Registration = (initializer: RouteInitializer) => void;
@@ -8,4 +8,4 @@ export default abstract class ApiManager {
public register(register: Registration) {
this.initialize(register);
}
-} \ No newline at end of file
+}
diff --git a/src/server/ApiManagers/DataVizManager.ts b/src/server/ApiManagers/DataVizManager.ts
index 0d43130d1..88f22992d 100644
--- a/src/server/ApiManagers/DataVizManager.ts
+++ b/src/server/ApiManagers/DataVizManager.ts
@@ -1,14 +1,14 @@
-import { csvParser, csvToString } from "../DataVizUtils";
-import { Method, _success } from "../RouteManager";
-import ApiManager, { Registration } from "./ApiManager";
-import { Directory, serverPathToFile } from "./UploadManager";
import * as path from 'path';
+import { csvParser, csvToString } from '../DataVizUtils';
+import { Method, _success } from '../RouteManager';
+import { Directory, serverPathToFile } from '../SocketData';
+import ApiManager, { Registration } from './ApiManager';
export default class DataVizManager extends ApiManager {
protected initialize(register: Registration): void {
register({
method: Method.GET,
- subscription: "/csvData",
+ subscription: '/csvData',
secureHandler: async ({ req, res }) => {
const uri = req.query.uri as string;
@@ -19,8 +19,7 @@ export default class DataVizManager extends ApiManager {
_success(res, parsedCsv);
resolve();
});
- }
+ },
});
}
-
-} \ No newline at end of file
+}
diff --git a/src/server/ApiManagers/DeleteManager.ts b/src/server/ApiManagers/DeleteManager.ts
index c6c4ca464..9ad334c1b 100644
--- a/src/server/ApiManagers/DeleteManager.ts
+++ b/src/server/ApiManagers/DeleteManager.ts
@@ -1,12 +1,12 @@
-import ApiManager, { Registration } from './ApiManager';
-import { Method, _permission_denied } from '../RouteManager';
-import { WebSocket } from '../websocket';
-import { Database } from '../database';
+import { mkdirSync } from 'fs';
import { rimraf } from 'rimraf';
-import { filesDirectory } from '..';
+import { filesDirectory } from '../SocketData';
import { DashUploadUtils } from '../DashUploadUtils';
-import { mkdirSync } from 'fs';
+import { Method } from '../RouteManager';
import RouteSubscriber from '../RouteSubscriber';
+import { Database } from '../database';
+import { WebSocket } from '../websocket';
+import ApiManager, { Registration } from './ApiManager';
export default class DeleteManager extends ApiManager {
protected initialize(register: Registration): void {
@@ -24,9 +24,11 @@ export default class DeleteManager extends ApiManager {
switch (target) {
case 'all':
all = true;
+ // eslint-disable-next-line no-fallthrough
case 'database':
await WebSocket.doDelete(false);
if (!all) break;
+ // eslint-disable-next-line no-fallthrough
case 'files':
rimraf.sync(filesDirectory);
mkdirSync(filesDirectory);
diff --git a/src/server/ApiManagers/DownloadManager.ts b/src/server/ApiManagers/DownloadManager.ts
index 2175b6db6..5ee21fb44 100644
--- a/src/server/ApiManagers/DownloadManager.ts
+++ b/src/server/ApiManagers/DownloadManager.ts
@@ -1,13 +1,13 @@
-import ApiManager, { Registration } from "./ApiManager";
-import { Method } from "../RouteManager";
-import RouteSubscriber from "../RouteSubscriber";
import * as Archiver from 'archiver';
import * as express from 'express';
-import { Database } from "../database";
-import * as path from "path";
-import { DashUploadUtils, SizeSuffix } from "../DashUploadUtils";
-import { publicDirectory } from "..";
-import { serverPathToFile, Directory } from "./UploadManager";
+import * as path from 'path';
+import { URL } from 'url';
+import { DashUploadUtils, SizeSuffix } from '../DashUploadUtils';
+import { Method } from '../RouteManager';
+import RouteSubscriber from '../RouteSubscriber';
+import { Directory, publicDirectory, serverPathToFile } from '../SocketData';
+import { Database } from '../database';
+import ApiManager, { Registration } from './ApiManager';
export type Hierarchy = { [id: string]: string | Hierarchy };
export type ZipMutator = (file: Archiver.Archiver) => void | Promise<void>;
@@ -16,147 +16,45 @@ export interface DocumentElements {
title: string;
}
-export default class DownloadManager extends ApiManager {
-
- protected initialize(register: Registration): void {
-
- /**
- * Let's say someone's using Dash to organize images in collections.
- * This lets them export the hierarchy they've built to their
- * own file system in a useful format.
- *
- * This handler starts with a single document id (interesting only
- * if it's that of a collection). It traverses the database, captures
- * the nesting of only nested images or collections, writes
- * that to a zip file and returns it to the client for download.
- */
- register({
- method: Method.GET,
- subscription: new RouteSubscriber("imageHierarchyExport").add('docId'),
- secureHandler: async ({ req, res }) => {
- const id = req.params.docId;
- const hierarchy: Hierarchy = {};
- await buildHierarchyRecursive(id, hierarchy);
- return BuildAndDispatchZip(res, zip => writeHierarchyRecursive(zip, hierarchy));
- }
- });
-
- register({
- method: Method.GET,
- subscription: new RouteSubscriber("downloadId").add("docId"),
- secureHandler: async ({ req, res }) => {
- return BuildAndDispatchZip(res, async zip => {
- const { id, docs, files } = await getDocs(req.params.docId);
- const docString = JSON.stringify({ id, docs });
- zip.append(docString, { name: "doc.json" });
- files.forEach(val => {
- zip.file(publicDirectory + val, { name: val.substring(1) });
- });
- });
- }
- });
-
- register({
- method: Method.GET,
- subscription: new RouteSubscriber("serializeDoc").add("docId"),
- secureHandler: async ({ req, res }) => {
- const { docs, files } = await getDocs(req.params.docId);
- res.send({ docs, files: Array.from(files) });
- }
- });
-
- }
-
-}
-
-async function getDocs(id: string) {
- const files = new Set<string>();
- const docs: { [id: string]: any } = {};
- const fn = (doc: any): string[] => {
- const id = doc.id;
- if (typeof id === "string" && id.endsWith("Proto")) {
- //Skip protos
- return [];
- }
- const ids: string[] = [];
- for (const key in doc.fields) {
- if (!doc.fields.hasOwnProperty(key)) { continue; }
- const field = doc.fields[key];
- if (field === undefined || field === null) { continue; }
-
- if (field.__type === "proxy" || field.__type === "prefetch_proxy") {
- ids.push(field.fieldId);
- } else if (field.__type === "script" || field.__type === "computed") {
- field.captures && ids.push(field.captures.fieldId);
- } else if (field.__type === "list") {
- ids.push(...fn(field));
- } else if (typeof field === "string") {
- const re = /"(?:dataD|d)ocumentId"\s*:\s*"([\w\-]*)"/g;
- let match: string[] | null;
- while ((match = re.exec(field)) !== null) {
- ids.push(match[1]);
- }
- } else if (field.__type === "RichTextField") {
- const re = /"href"\s*:\s*"(.*?)"/g;
- let match: string[] | null;
- while ((match = re.exec(field.Data)) !== null) {
- const urlString = match[1];
- const split = new URL(urlString).pathname.split("doc/");
- if (split.length > 1) {
- ids.push(split[split.length - 1]);
- }
- }
- const re2 = /"src"\s*:\s*"(.*?)"/g;
- while ((match = re2.exec(field.Data)) !== null) {
- const urlString = match[1];
- const pathname = new URL(urlString).pathname;
- files.add(pathname);
- }
- } else if (["audio", "image", "video", "pdf", "web", "map"].includes(field.__type)) {
- const url = new URL(field.url);
- const pathname = url.pathname;
- files.add(pathname);
- }
- }
-
- if (doc.id) {
- docs[doc.id] = doc;
- }
- return ids;
- };
- await Database.Instance.visit([id], fn);
- return { id, docs, files };
-}
-
/**
- * 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
+ * This is a very specific utility method to help traverse the database
+ * to parse data and titles out of images and collections alone.
+ *
+ * We don't know if the document id given to is corresponds to a view document or a data
+ * document. If it's a data document, the response from the database will have
+ * a data field. If not, call recursively on the proto, and resolve with *its* data
+ *
+ * @param targetId the id of the Dash document whose data is being requests
+ * @returns the data of the document, as well as its title
*/
-export async function BuildAndDispatchZip(res: express.Response, mutator: ZipMutator): Promise<void> {
- res.set('Content-disposition', `attachment;`);
- res.set('Content-Type', "application/zip");
- const zip = Archiver('zip');
- zip.pipe(res);
- await mutator(zip);
- return zip.finalize();
+async function getData(targetId: string): Promise<DocumentElements> {
+ return new Promise<DocumentElements>((resolve, reject) => {
+ Database.Instance.getDocument(targetId, 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();
+ }
+ } else if (proto) {
+ getData(proto.fieldId).then(resolve, reject);
+ } else {
+ reject();
+ }
+ });
+ });
}
/**
* 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.
+ * 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
{
@@ -190,74 +88,175 @@ async function buildHierarchyRecursive(seedId: string, hierarchy: Hierarchy): Pr
}
/**
- * This is a very specific utility method to help traverse the database
- * to parse data and titles out of images and collections alone.
- *
- * We don't know if the document id given to is corresponds to a view document or a data
- * document. If it's a data document, the response from the database will have
- * a data field. If not, call recursively on the proto, and resolve with *its* data
- *
- * @param targetId the id of the Dash document whose data is being requests
- * @returns the data of the document, as well as its title
+ * 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
*/
-async function getData(targetId: string): Promise<DocumentElements> {
- return new Promise<DocumentElements>((resolve, reject) => {
- Database.Instance.getDocument(targetId, 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();
- }
- } else if (proto) {
- getData(proto.fieldId).then(resolve, reject);
- } else {
- reject();
- }
- });
- });
+export async function BuildAndDispatchZip(res: express.Response, mutator: ZipMutator): Promise<void> {
+ res.set('Content-disposition', `attachment;`);
+ res.set('Content-Type', 'application/zip');
+ const zip = Archiver('zip');
+ zip.pipe(res);
+ await mutator(zip);
+ return zip.finalize();
}
/**
- *
+ *
* @param file the zip file to which we write the files
* @param hierarchy the data structure from which we read, defining the nesting of the documents in the zip
* @param prefix lets us create nested folders in the zip file by continually appending to the end
* of the prefix with each layer of recursion.
- *
+ *
* Function Call #1 => "Dash Export"
* Function Call #2 => "Dash Export/a nested collection"
* Function Call #3 => "Dash Export/a nested collection/lowest level collection"
* ...
*/
-async function writeHierarchyRecursive(file: Archiver.Archiver, hierarchy: Hierarchy, prefix = "Dash Export"): Promise<void> {
- for (const documentTitle of Object.keys(hierarchy)) {
- const result = hierarchy[documentTitle];
- // base case or leaf node, we've hit a url (image)
- if (typeof result === "string") {
- let path: string;
- let matches: RegExpExecArray | null;
- if ((matches = /\:\d+\/files\/images\/(upload\_[\da-z]{32}.*)/g.exec(result)) !== null) {
- // image already exists on our server
- path = serverPathToFile(Directory.images, matches[1]);
+async function writeHierarchyRecursive(file: Archiver.Archiver, hierarchy: Hierarchy, prefix = 'Dash Export'): Promise<void> {
+ // eslint-disable-next-line no-restricted-syntax
+ for (const documentTitle in hierarchy) {
+ if (Object.prototype.hasOwnProperty.call(hierarchy, documentTitle)) {
+ const result = hierarchy[documentTitle];
+ // base case or leaf node, we've hit a url (image)
+ if (typeof result === 'string') {
+ let fPath: string;
+ const matches = /:\d+\/files\/images\/(upload_[\da-z]{32}.*)/g.exec(result);
+ if (matches !== null) {
+ // image already exists on our server
+ fPath = serverPathToFile(Directory.images, matches[1]);
+ } else {
+ // the image doesn't already exist on our server (may have been dragged
+ // and dropped in the browser and thus hosted remotely) so we upload it
+ // to our server and point the zip file to it, so it can bundle up the bytes
+ // eslint-disable-next-line no-await-in-loop
+ const information = await DashUploadUtils.UploadImage(result);
+ fPath = information instanceof Error ? '' : information.accessPaths[SizeSuffix.Original].server;
+ }
+ // write the file specified by the path to the directory in the
+ // zip file given by the prefix.
+ if (fPath) {
+ file.file(fPath, { name: documentTitle, prefix });
+ }
} else {
- // the image doesn't already exist on our server (may have been dragged
- // and dropped in the browser and thus hosted remotely) so we upload it
- // to our server and point the zip file to it, so it can bundle up the bytes
- const information = await DashUploadUtils.UploadImage(result);
- path = information instanceof Error ? "" : information.accessPaths[SizeSuffix.Original].server;
+ // we've hit a collection, so we have to recurse
+ // eslint-disable-next-line no-await-in-loop
+ await writeHierarchyRecursive(file, result, `${prefix}/${documentTitle}`);
}
- // write the file specified by the path to the directory in the
- // zip file given by the prefix.
- if (path) {
- file.file(path, { name: documentTitle, prefix });
+ }
+ }
+}
+
+async function getDocs(docId: string) {
+ const files = new Set<string>();
+ const docs: { [id: string]: any } = {};
+ const fn = (doc: any): string[] => {
+ const { id } = doc;
+ if (typeof id === 'string' && id.endsWith('Proto')) {
+ // Skip protos
+ return [];
+ }
+ const ids: string[] = [];
+ // eslint-disable-next-line no-restricted-syntax
+ for (const key in doc.fields) {
+ // eslint-disable-next-line no-continue
+ if (!Object.prototype.hasOwnProperty.call(doc.fields, key)) continue;
+
+ const field = doc.fields[key];
+ // eslint-disable-next-line no-continue
+ if (field === undefined || field === null) continue;
+
+ if (field.__type === 'proxy' || field.__type === 'prefetch_proxy') {
+ ids.push(field.fieldId);
+ } else if (field.__type === 'script' || field.__type === 'computed') {
+ field.captures && ids.push(field.captures.fieldId);
+ } else if (field.__type === 'list') {
+ ids.push(...fn(field));
+ } else if (typeof field === 'string') {
+ const re = /"(?:dataD|d)ocumentId"\s*:\s*"([\w-]*)"/g;
+ for (let match = re.exec(field); match !== null; match = re.exec(field)) {
+ ids.push(match[1]);
+ }
+ } else if (field.__type === 'RichTextField') {
+ const re = /"href"\s*:\s*"(.*?)"/g;
+ for (let match = re.exec(field.data); match !== null; match = re.exec(field.Data)) {
+ const urlString = match[1];
+ const split = new URL(urlString).pathname.split('doc/');
+ if (split.length > 1) {
+ ids.push(split[split.length - 1]);
+ }
+ }
+ const re2 = /"src"\s*:\s*"(.*?)"/g;
+ for (let match = re2.exec(field.Data); match !== null; match = re2.exec(field.Data)) {
+ const urlString = match[1];
+ const { pathname } = new URL(urlString);
+ files.add(pathname);
+ }
+ } else if (['audio', 'image', 'video', 'pdf', 'web', 'map'].includes(field.__type)) {
+ const { pathname } = new URL(field.url);
+ files.add(pathname);
}
- } else {
- // we've hit a collection, so we have to recurse
- await writeHierarchyRecursive(file, result, `${prefix}/${documentTitle}`);
}
+
+ if (doc.id) {
+ docs[doc.id] = doc;
+ }
+ return ids;
+ };
+ await Database.Instance.visit([docId], fn);
+ return { id: docId, docs, files };
+}
+
+export default class DownloadManager extends ApiManager {
+ protected initialize(register: Registration): void {
+ /**
+ * Let's say someone's using Dash to organize images in collections.
+ * This lets them export the hierarchy they've built to their
+ * own file system in a useful format.
+ *
+ * This handler starts with a single document id (interesting only
+ * if it's that of a collection). It traverses the database, captures
+ * the nesting of only nested images or collections, writes
+ * that to a zip file and returns it to the client for download.
+ */
+ register({
+ method: Method.GET,
+ subscription: new RouteSubscriber('imageHierarchyExport').add('docId'),
+ secureHandler: async ({ req, res }) => {
+ const id = req.params.docId;
+ const hierarchy: Hierarchy = {};
+ await buildHierarchyRecursive(id, hierarchy);
+ return BuildAndDispatchZip(res, zip => writeHierarchyRecursive(zip, hierarchy));
+ },
+ });
+
+ register({
+ method: Method.GET,
+ subscription: new RouteSubscriber('downloadId').add('docId'),
+ secureHandler: async ({ req, res }) =>
+ BuildAndDispatchZip(res, async zip => {
+ const { id, docs, files } = await getDocs(req.params.docId);
+ const docString = JSON.stringify({ id, docs });
+ zip.append(docString, { name: 'doc.json' });
+ files.forEach(val => {
+ zip.file(publicDirectory + val, { name: val.substring(1) });
+ });
+ }),
+ });
+
+ register({
+ method: Method.GET,
+ subscription: new RouteSubscriber('serializeDoc').add('docId'),
+ secureHandler: async ({ req, res }) => {
+ const { docs, files } = await getDocs(req.params.docId);
+ res.send({ docs, files: Array.from(files) });
+ },
+ });
}
-} \ No newline at end of file
+}
diff --git a/src/server/ApiManagers/GeneralGoogleManager.ts b/src/server/ApiManagers/GeneralGoogleManager.ts
index f94b77cac..12913b1ef 100644
--- a/src/server/ApiManagers/GeneralGoogleManager.ts
+++ b/src/server/ApiManagers/GeneralGoogleManager.ts
@@ -1,51 +1,49 @@
-import ApiManager, { Registration } from "./ApiManager";
-import { Method, _permission_denied } from "../RouteManager";
-import { GoogleApiServerUtils } from "../apis/google/GoogleApiServerUtils";
-import RouteSubscriber from "../RouteSubscriber";
-import { Database } from "../database";
+import ApiManager, { Registration } from './ApiManager';
+import { Method } from '../RouteManager';
+import { GoogleApiServerUtils } from '../apis/google/GoogleApiServerUtils';
+import RouteSubscriber from '../RouteSubscriber';
+import { Database } from '../database';
const EndpointHandlerMap = new Map<GoogleApiServerUtils.Action, GoogleApiServerUtils.ApiRouter>([
- ["create", (api, params) => api.create(params)],
- ["retrieve", (api, params) => api.get(params)],
- ["update", (api, params) => api.batchUpdate(params)],
+ ['create', (api, params) => api.create(params)],
+ ['retrieve', (api, params) => api.get(params)],
+ ['update', (api, params) => api.batchUpdate(params)],
]);
export default class GeneralGoogleManager extends ApiManager {
-
protected initialize(register: Registration): void {
-
register({
method: Method.GET,
- subscription: "/readGoogleAccessToken",
+ subscription: '/readGoogleAccessToken',
secureHandler: async ({ user, res }) => {
- const { credentials } = (await GoogleApiServerUtils.retrieveCredentials(user.id));
+ const { credentials } = await GoogleApiServerUtils.retrieveCredentials(user.id);
if (!credentials?.access_token) {
return res.send(GoogleApiServerUtils.generateAuthenticationUrl());
}
return res.send(credentials);
- }
+ },
});
register({
method: Method.POST,
- subscription: "/writeGoogleAccessToken",
+ subscription: '/writeGoogleAccessToken',
secureHandler: async ({ user, req, res }) => {
res.send(await GoogleApiServerUtils.processNewUser(user.id, req.body.authenticationCode));
- }
+ },
});
register({
method: Method.GET,
- subscription: "/revokeGoogleAccessToken",
+ subscription: '/revokeGoogleAccessToken',
secureHandler: async ({ user, res }) => {
await Database.Auxiliary.GoogleAccessToken.Revoke(user.id);
res.send();
- }
+ },
});
register({
method: Method.POST,
- subscription: new RouteSubscriber("googleDocs").add("sector", "action"),
+ subscription: new RouteSubscriber('googleDocs').add('sector', 'action'),
secureHandler: async ({ req, res, user }) => {
const sector: GoogleApiServerUtils.Service = req.params.sector as GoogleApiServerUtils.Service;
const action: GoogleApiServerUtils.Action = req.params.action as GoogleApiServerUtils.Action;
@@ -61,8 +59,7 @@ export default class GeneralGoogleManager extends ApiManager {
return;
}
res.send(undefined);
- }
+ },
});
-
}
-} \ No newline at end of file
+}
diff --git a/src/server/ApiManagers/MongoStore.js b/src/server/ApiManagers/MongoStore.js
new file mode 100644
index 000000000..5d91c2805
--- /dev/null
+++ b/src/server/ApiManagers/MongoStore.js
@@ -0,0 +1,413 @@
+const __createBinding =
+ (this && this.__createBinding) ||
+ (Object.create
+ ? function (o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ let desc = Object.getOwnPropertyDescriptor(m, k);
+ if (!desc || ('get' in desc ? !m.__esModule : desc.writable || desc.configurable)) {
+ desc = {
+ enumerable: true,
+ get: function () {
+ return m[k];
+ },
+ };
+ }
+ Object.defineProperty(o, k2, desc);
+ }
+ : function (o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ o[k2] = m[k];
+ });
+const __setModuleDefault =
+ (this && this.__setModuleDefault) ||
+ (Object.create
+ ? function (o, v) {
+ Object.defineProperty(o, 'default', { enumerable: true, value: v });
+ }
+ : function (o, v) {
+ o.default = v;
+ });
+const __importStar =
+ (this && this.__importStar) ||
+ function (mod) {
+ if (mod && mod.__esModule) return mod;
+ const result = {};
+ if (mod != null) for (const k in mod) if (k !== 'default' && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+ __setModuleDefault(result, mod);
+ return result;
+ };
+const __importDefault =
+ (this && this.__importDefault) ||
+ function (mod) {
+ return mod && mod.__esModule ? mod : { default: mod };
+ };
+Object.defineProperty(exports, '__esModule', { value: true });
+const console_1 = require('console');
+const util_1 = __importDefault(require('util'));
+const session = __importStar(require('express-session'));
+const mongodb_1 = require('mongodb');
+const debug_1 = __importDefault(require('debug'));
+const debug = (0, debug_1.default)('connect-mongo');
+// eslint-disable-next-line @typescript-eslint/no-empty-function
+const noop = () => {};
+const unit = a => a;
+function defaultSerializeFunction(session) {
+ // Copy each property of the session to a new object
+ const obj = {};
+ let prop;
+ for (prop in session) {
+ if (prop === 'cookie') {
+ // Convert the cookie instance to an object, if possible
+ // This gets rid of the duplicate object under session.cookie.data property
+ // @ts-ignore FIXME:
+ obj.cookie = session.cookie.toJSON
+ ? // @ts-ignore FIXME:
+ session.cookie.toJSON()
+ : session.cookie;
+ } else {
+ // @ts-ignore FIXME:
+ obj[prop] = session[prop];
+ }
+ }
+ return obj;
+}
+function computeTransformFunctions(options) {
+ if (options.serialize || options.unserialize) {
+ return {
+ serialize: options.serialize || defaultSerializeFunction,
+ unserialize: options.unserialize || unit,
+ };
+ }
+ if (options.stringify === false) {
+ return {
+ serialize: defaultSerializeFunction,
+ unserialize: unit,
+ };
+ }
+ // Default case
+ return {
+ serialize: JSON.stringify,
+ unserialize: JSON.parse,
+ };
+}
+class MongoStore extends session.Store {
+ constructor({ collectionName = 'sessions', ttl = 1209600, mongoOptions = {}, autoRemove = 'native', autoRemoveInterval = 10, touchAfter = 0, stringify = true, crypto, ...required }) {
+ super();
+ this.crypto = null;
+ debug('create MongoStore instance');
+ const options = {
+ collectionName,
+ ttl,
+ mongoOptions,
+ autoRemove,
+ autoRemoveInterval,
+ touchAfter,
+ stringify,
+ crypto: {
+ ...{
+ secret: false,
+ algorithm: 'aes-256-gcm',
+ hashing: 'sha512',
+ encodeas: 'base64',
+ key_size: 32,
+ iv_size: 16,
+ at_size: 16,
+ },
+ ...crypto,
+ },
+ ...required,
+ };
+ // Check params
+ (0, console_1.assert)(options.mongoUrl || options.clientPromise || options.client, 'You must provide either mongoUrl|clientPromise|client in options');
+ (0, console_1.assert)(options.createAutoRemoveIdx === null || options.createAutoRemoveIdx === undefined, 'options.createAutoRemoveIdx has been reverted to autoRemove and autoRemoveInterval');
+ (0, console_1.assert)(!options.autoRemoveInterval || options.autoRemoveInterval <= 71582, /* (Math.pow(2, 32) - 1) / (1000 * 60) */ 'autoRemoveInterval is too large. options.autoRemoveInterval is in minutes but not seconds nor mills');
+ this.transformFunctions = computeTransformFunctions(options);
+ let _clientP;
+ if (options.mongoUrl) {
+ _clientP = mongodb_1.MongoClient.connect(options.mongoUrl, options.mongoOptions);
+ } else if (options.clientPromise) {
+ _clientP = options.clientPromise;
+ } else if (options.client) {
+ _clientP = Promise.resolve(options.client);
+ } else {
+ throw new Error('Cannot init client. Please provide correct options');
+ }
+ (0, console_1.assert)(!!_clientP, 'Client is null|undefined');
+ this.clientP = _clientP;
+ this.options = options;
+ this.collectionP = _clientP.then(async con => {
+ const collection = con.db(options.dbName).collection(options.collectionName);
+ await this.setAutoRemove(collection);
+ return collection;
+ });
+ if (options.crypto.secret) {
+ this.crypto = require('kruptein')(options.crypto);
+ }
+ }
+ static create(options) {
+ return new MongoStore(options);
+ }
+ setAutoRemove(collection) {
+ const removeQuery = () => ({
+ expires: {
+ $lt: new Date(),
+ },
+ });
+ switch (this.options.autoRemove) {
+ case 'native':
+ debug('Creating MongoDB TTL index');
+ return collection.createIndex(
+ { expires: 1 },
+ {
+ background: true,
+ expireAfterSeconds: 0,
+ }
+ );
+ case 'interval':
+ debug('create Timer to remove expired sessions');
+ this.timer = setInterval(
+ () =>
+ collection.deleteMany(removeQuery(), {
+ writeConcern: {
+ w: 0,
+ j: false,
+ },
+ }),
+ this.options.autoRemoveInterval * 1000 * 60
+ );
+ this.timer.unref();
+ return Promise.resolve();
+ case 'disabled':
+ default:
+ return Promise.resolve();
+ }
+ }
+ computeStorageId(sessionId) {
+ if (this.options.transformId && typeof this.options.transformId === 'function') {
+ return this.options.transformId(sessionId);
+ }
+ return sessionId;
+ }
+ /**
+ * promisify and bind the `this.crypto.get` function.
+ * Please check !!this.crypto === true before using this getter!
+ */
+ get cryptoGet() {
+ if (!this.crypto) {
+ throw new Error('Check this.crypto before calling this.cryptoGet!');
+ }
+ return util_1.default.promisify(this.crypto.get).bind(this.crypto);
+ }
+ /**
+ * Decrypt given session data
+ * @param session session data to be decrypt. Mutate the input session.
+ */
+ async decryptSession(session) {
+ if (this.crypto && session) {
+ const plaintext = await this.cryptoGet(this.options.crypto.secret, session.session).catch(err => {
+ throw new Error(err);
+ });
+ // @ts-ignore
+ session.session = JSON.parse(plaintext);
+ }
+ }
+ /**
+ * Get a session from the store given a session ID (sid)
+ * @param sid session ID
+ */
+ get(sid, callback) {
+ (async () => {
+ try {
+ debug(`MongoStore#get=${sid}`);
+ const collection = await this.collectionP;
+ const session = await collection.findOne({
+ _id: this.computeStorageId(sid),
+ $or: [{ expires: { $exists: false } }, { expires: { $gt: new Date() } }],
+ });
+ if (this.crypto && session) {
+ await this.decryptSession(session).catch(err => callback(err));
+ }
+ const s = session && this.transformFunctions.unserialize(session.session);
+ if (this.options.touchAfter > 0 && (session === null || session === void 0 ? void 0 : session.lastModified)) {
+ s.lastModified = session.lastModified;
+ }
+ this.emit('get', sid);
+ callback(null, s === undefined ? null : s);
+ } catch (error) {
+ callback(error);
+ }
+ })();
+ }
+ /**
+ * Upsert a session into the store given a session ID (sid) and session (session) object.
+ * @param sid session ID
+ * @param session session object
+ */
+ set(sid, session, callback = noop) {
+ (async () => {
+ let _a;
+ try {
+ debug(`MongoStore#set=${sid}`);
+ // Removing the lastModified prop from the session object before update
+ // @ts-ignore
+ if (this.options.touchAfter > 0 && (session === null || session === void 0 ? void 0 : session.lastModified)) {
+ // @ts-ignore
+ delete session.lastModified;
+ }
+ const s = {
+ _id: this.computeStorageId(sid),
+ session: this.transformFunctions.serialize(session),
+ };
+ // Expire handling
+ if ((_a = session === null || session === void 0 ? void 0 : session.cookie) === null || _a === void 0 ? void 0 : _a.expires) {
+ s.expires = new Date(session.cookie.expires);
+ } else {
+ // If there's no expiration date specified, it is
+ // browser-session cookie or there is no cookie at all,
+ // as per the connect docs.
+ //
+ // So we set the expiration to two-weeks from now
+ // - as is common practice in the industry (e.g Django) -
+ // or the default specified in the options.
+ s.expires = new Date(Date.now() + this.options.ttl * 1000);
+ }
+ // Last modify handling
+ if (this.options.touchAfter > 0) {
+ s.lastModified = new Date();
+ }
+ if (this.crypto) {
+ const cryptoSet = util_1.default.promisify(this.crypto.set).bind(this.crypto);
+ const data = await cryptoSet(this.options.crypto.secret, s.session).catch(err => {
+ throw new Error(err);
+ });
+ s.session = data;
+ }
+ const collection = await this.collectionP;
+ const rawResp = await collection.updateOne(
+ { _id: s._id },
+ { $set: s },
+ {
+ upsert: true,
+ writeConcern: this.options.writeOperationOptions,
+ }
+ );
+ if (rawResp.upsertedCount > 0) {
+ this.emit('create', sid);
+ } else {
+ this.emit('update', sid);
+ }
+ this.emit('set', sid);
+ } catch (error) {
+ return callback(error);
+ }
+ return callback(null);
+ })();
+ }
+ touch(sid, session, callback = noop) {
+ (async () => {
+ let _a;
+ try {
+ debug(`MongoStore#touch=${sid}`);
+ const updateFields = {};
+ const touchAfter = this.options.touchAfter * 1000;
+ const lastModified = session.lastModified ? session.lastModified.getTime() : 0;
+ const currentDate = new Date();
+ // If the given options has a touchAfter property, check if the
+ // current timestamp - lastModified timestamp is bigger than
+ // the specified, if it's not, don't touch the session
+ if (touchAfter > 0 && lastModified > 0) {
+ const timeElapsed = currentDate.getTime() - lastModified;
+ if (timeElapsed < touchAfter) {
+ debug(`Skip touching session=${sid}`);
+ return callback(null);
+ }
+ updateFields.lastModified = currentDate;
+ }
+ if ((_a = session === null || session === void 0 ? void 0 : session.cookie) === null || _a === void 0 ? void 0 : _a.expires) {
+ updateFields.expires = new Date(session.cookie.expires);
+ } else {
+ updateFields.expires = new Date(Date.now() + this.options.ttl * 1000);
+ }
+ const collection = await this.collectionP;
+ const rawResp = await collection.updateOne({ _id: this.computeStorageId(sid) }, { $set: updateFields }, { writeConcern: this.options.writeOperationOptions });
+ if (rawResp.matchedCount === 0) {
+ return callback(new Error('Unable to find the session to touch'));
+ } else {
+ this.emit('touch', sid, session);
+ return callback(null);
+ }
+ } catch (error) {
+ return callback(error);
+ }
+ })();
+ }
+ /**
+ * Get all sessions in the store as an array
+ */
+ all(callback) {
+ (async () => {
+ try {
+ debug('MongoStore#all()');
+ const collection = await this.collectionP;
+ const sessions = collection.find({
+ $or: [{ expires: { $exists: false } }, { expires: { $gt: new Date() } }],
+ });
+ const results = [];
+ for await (const session of sessions) {
+ if (this.crypto && session) {
+ await this.decryptSession(session);
+ }
+ results.push(this.transformFunctions.unserialize(session.session));
+ }
+ this.emit('all', results);
+ callback(null, results);
+ } catch (error) {
+ callback(error);
+ }
+ })();
+ }
+ /**
+ * Destroy/delete a session from the store given a session ID (sid)
+ * @param sid session ID
+ */
+ destroy(sid, callback = noop) {
+ debug(`MongoStore#destroy=${sid}`);
+ this.collectionP
+ .then(colleciton => colleciton.deleteOne({ _id: this.computeStorageId(sid) }, { writeConcern: this.options.writeOperationOptions }))
+ .then(() => {
+ this.emit('destroy', sid);
+ callback(null);
+ })
+ .catch(err => callback(err));
+ }
+ /**
+ * Get the count of all sessions in the store
+ */
+ length(callback) {
+ debug('MongoStore#length()');
+ this.collectionP
+ .then(collection => collection.countDocuments())
+ .then(c => callback(null, c))
+ // @ts-ignore
+ .catch(err => callback(err));
+ }
+ /**
+ * Delete all sessions from the store.
+ */
+ clear(callback = noop) {
+ debug('MongoStore#clear()');
+ this.collectionP
+ .then(collection => collection.drop())
+ .then(() => callback(null))
+ .catch(err => callback(err));
+ }
+ /**
+ * Close database connection
+ */
+ close() {
+ debug('MongoStore#close()');
+ return this.clientP.then(c => c.close());
+ }
+}
+exports.default = MongoStore;
+//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiTW9uZ29TdG9yZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9saWIvTW9uZ29TdG9yZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBQUEscUNBQWdDO0FBQ2hDLGdEQUF1QjtBQUN2Qix5REFBMEM7QUFDMUMscUNBS2dCO0FBQ2hCLGtEQUF5QjtBQUd6QixNQUFNLEtBQUssR0FBRyxJQUFBLGVBQUssRUFBQyxlQUFlLENBQUMsQ0FBQTtBQWdFcEMsZ0VBQWdFO0FBQ2hFLE1BQU0sSUFBSSxHQUFHLEdBQUcsRUFBRSxHQUFFLENBQUMsQ0FBQTtBQUNyQixNQUFNLElBQUksR0FBbUIsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQTtBQUVyQyxTQUFTLHdCQUF3QixDQUMvQixPQUE0QjtJQUU1QixvREFBb0Q7SUFDcEQsTUFBTSxHQUFHLEdBQUcsRUFBRSxDQUFBO0lBQ2QsSUFBSSxJQUFJLENBQUE7SUFDUixLQUFLLElBQUksSUFBSSxPQUFPLEVBQUU7UUFDcEIsSUFBSSxJQUFJLEtBQUssUUFBUSxFQUFFO1lBQ3JCLHdEQUF3RDtZQUN4RCwyRUFBMkU7WUFDM0Usb0JBQW9CO1lBQ3BCLEdBQUcsQ0FBQyxNQUFNLEdBQUcsT0FBTyxDQUFDLE1BQU0sQ0FBQyxNQUFNO2dCQUNoQyxDQUFDLENBQUMsb0JBQW9CO29CQUNwQixPQUFPLENBQUMsTUFBTSxDQUFDLE1BQU0sRUFBRTtnQkFDekIsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUE7U0FDbkI7YUFBTTtZQUNMLG9CQUFvQjtZQUNwQixHQUFHLENBQUMsSUFBSSxDQUFDLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFBO1NBQzFCO0tBQ0Y7SUFFRCxPQUFPLEdBQTBCLENBQUE7QUFDbkMsQ0FBQztBQUVELFNBQVMseUJBQXlCLENBQUMsT0FBbUM7SUFDcEUsSUFBSSxPQUFPLENBQUMsU0FBUyxJ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
diff --git a/src/server/ApiManagers/SearchManager.ts b/src/server/ApiManagers/SearchManager.ts
index 92c10975f..684b49eaf 100644
--- a/src/server/ApiManagers/SearchManager.ts
+++ b/src/server/ApiManagers/SearchManager.ts
@@ -1,6 +1,7 @@
+/* eslint-disable no-use-before-define */
import { exec } from 'child_process';
import { cyan, green, red, yellow } from 'colors';
-import { log_execution } from '../ActionUtilities';
+import { logExecution } from '../ActionUtilities';
import { Method } from '../RouteManager';
import RouteSubscriber from '../RouteSubscriber';
import { Search } from '../Search';
@@ -17,8 +18,10 @@ export class SearchManager extends ApiManager {
switch (action) {
case 'start':
case 'stop':
- const status = req.params.action === 'start';
- SolrManager.SetRunning(status);
+ {
+ const status = req.params.action === 'start';
+ SolrManager.SetRunning(status);
+ }
break;
case 'update':
await SolrManager.update();
@@ -35,7 +38,9 @@ export class SearchManager extends ApiManager {
subscription: '/dashsearch',
secureHandler: async ({ req, res }) => {
const solrQuery: any = {};
- ['q', 'fq', 'start', 'rows', 'sort', 'hl.maxAnalyzedChars', 'hl', 'hl.fl'].forEach(key => (solrQuery[key] = req.query[key]));
+ ['q', 'fq', 'start', 'rows', 'sort', 'hl.maxAnalyzedChars', 'hl', 'hl.fl'].forEach(key => {
+ solrQuery[key] = req.query[key];
+ });
if (solrQuery.q === undefined) {
res.send([]);
return;
@@ -66,13 +71,13 @@ export namespace SolrManager {
export async function update() {
console.log(green('Beginning update...'));
- await log_execution<void>({
+ await logExecution<void>({
startMessage: 'Clearing existing Solr information...',
endMessage: 'Solr information successfully cleared',
action: Search.clear,
color: cyan,
});
- const cursor = await log_execution({
+ const cursor = await logExecution({
startMessage: 'Connecting to and querying for all documents from database...',
endMessage: ({ result, error }) => {
const success = error === null && result !== undefined;
@@ -95,30 +100,30 @@ export namespace SolrManager {
if (doc.__type !== 'Doc') {
return;
}
- const fields = doc.fields;
+ const { fields } = doc;
if (!fields) {
return;
}
- const update: any = { id: doc._id };
+ const update2: any = { id: doc._id };
let dynfield = false;
- for (const key in fields) {
+ fields.forEach((key: any) => {
const value = fields[key];
const term = ToSearchTerm(value);
if (term !== undefined) {
- const { suffix, value } = term;
+ const { suffix, value: tvalue } = term;
if (key.endsWith('modificationDate')) {
- update['modificationDate' + suffix] = value;
+ update2['modificationDate' + suffix] = tvalue;
}
- update[key + suffix] = value;
+ update2[key + suffix] = value;
dynfield = true;
}
- }
+ });
if (dynfield) {
- updates.push(update);
+ updates.push(update2);
}
}
await cursor?.forEach(updateDoc);
- const result = await log_execution({
+ const result = await logExecution({
startMessage: `Dispatching updates for ${updates.length} documents`,
endMessage: 'Dispatched updates complete',
action: () => Search.updateDocuments(updates),
@@ -156,6 +161,7 @@ export namespace SolrManager {
'_l',
list => {
const results = [];
+ // eslint-disable-next-line no-restricted-syntax
for (const value of list.fields) {
const term = ToSearchTerm(value);
if (term) {
@@ -167,14 +173,15 @@ export namespace SolrManager {
],
};
- function ToSearchTerm(val: any): { suffix: string; value: any } | undefined {
+ function ToSearchTerm(valIn: any): { suffix: string; value: any } | undefined {
+ let val = valIn;
if (val === null || val === undefined) {
- return;
+ return undefined;
}
const type = val.__type || typeof val;
let suffix = suffixMap[type];
if (!suffix) {
- return;
+ return undefined;
}
if (Array.isArray(suffix)) {
@@ -184,6 +191,7 @@ export namespace SolrManager {
} else {
val = val[accessor];
}
+ // eslint-disable-next-line prefer-destructuring
suffix = suffix[0];
}
diff --git a/src/server/ApiManagers/SessionManager.ts b/src/server/ApiManagers/SessionManager.ts
index e37f8c6db..bebe50a62 100644
--- a/src/server/ApiManagers/SessionManager.ts
+++ b/src/server/ApiManagers/SessionManager.ts
@@ -1,67 +1,64 @@
-import ApiManager, { Registration } from "./ApiManager";
-import { Method, _permission_denied, AuthorizedCore, SecureHandler } from "../RouteManager";
-import RouteSubscriber from "../RouteSubscriber";
-import { sessionAgent } from "..";
-import { DashSessionAgent } from "../DashSession/DashSessionAgent";
+import ApiManager, { Registration } from './ApiManager';
+import { Method, _permissionDenied, AuthorizedCore, SecureHandler } from '../RouteManager';
+import RouteSubscriber from '../RouteSubscriber';
+import { sessionAgent } from '..';
+import { DashSessionAgent } from '../DashSession/DashSessionAgent';
-const permissionError = "You are not authorized!";
+const permissionError = 'You are not authorized!';
export default class SessionManager extends ApiManager {
+ private secureSubscriber = (root: string, ...params: string[]) => new RouteSubscriber(root).add('session_key', ...params);
- private secureSubscriber = (root: string, ...params: string[]) => new RouteSubscriber(root).add("session_key", ...params);
-
- private authorizedAction = (handler: SecureHandler) => {
- return (core: AuthorizedCore) => {
- const { req: { params }, res } = core;
- if (!process.env.MONITORED) {
- return res.send("This command only makes sense in the context of a monitored session.");
- }
- if (params.session_key !== process.env.session_key) {
- return _permission_denied(res, permissionError);
- }
- return handler(core);
- };
- }
+ private authorizedAction = (handler: SecureHandler) => (core: AuthorizedCore) => {
+ const {
+ req: { params },
+ res,
+ } = core;
+ if (!process.env.MONITORED) {
+ return res.send('This command only makes sense in the context of a monitored session.');
+ }
+ if (params.session_key !== process.env.session_key) {
+ return _permissionDenied(res, permissionError);
+ }
+ return handler(core);
+ };
protected initialize(register: Registration): void {
-
register({
method: Method.GET,
- subscription: this.secureSubscriber("debug", "to?"),
+ subscription: this.secureSubscriber('debug', 'to?'),
secureHandler: this.authorizedAction(async ({ req: { params }, res }) => {
const to = params.to || DashSessionAgent.notificationRecipient;
- const { error } = await sessionAgent.serverWorker.emit("debug", { to });
+ const { error } = await sessionAgent.serverWorker.emit('debug', { to });
res.send(error ? error.message : `Your request was successful: the server captured and compressed (but did not save) a new back up. It was sent to ${to}.`);
- })
+ }),
});
register({
method: Method.GET,
- subscription: this.secureSubscriber("backup"),
+ subscription: this.secureSubscriber('backup'),
secureHandler: this.authorizedAction(async ({ res }) => {
- const { error } = await sessionAgent.serverWorker.emit("backup");
- res.send(error ? error.message : "Your request was successful: the server successfully created a new back up.");
- })
+ const { error } = await sessionAgent.serverWorker.emit('backup');
+ res.send(error ? error.message : 'Your request was successful: the server successfully created a new back up.');
+ }),
});
register({
method: Method.GET,
- subscription: this.secureSubscriber("kill"),
+ subscription: this.secureSubscriber('kill'),
secureHandler: this.authorizedAction(({ res }) => {
- res.send("Your request was successful: the server and its session have been killed.");
- sessionAgent.killSession("an authorized user has manually ended the server session via the /kill route");
- })
+ res.send('Your request was successful: the server and its session have been killed.');
+ sessionAgent.killSession('an authorized user has manually ended the server session via the /kill route');
+ }),
});
register({
method: Method.GET,
- subscription: this.secureSubscriber("deleteSession"),
+ subscription: this.secureSubscriber('deleteSession'),
secureHandler: this.authorizedAction(async ({ res }) => {
- const { error } = await sessionAgent.serverWorker.emit("delete");
- res.send(error ? error.message : "Your request was successful: the server successfully deleted the database. Return to /home.");
- })
+ const { error } = await sessionAgent.serverWorker.emit('delete');
+ res.send(error ? error.message : 'Your request was successful: the server successfully deleted the database. Return to /home.');
+ }),
});
-
}
-
-} \ No newline at end of file
+}
diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts
index 2306b6589..4cb3d8baf 100644
--- a/src/server/ApiManagers/UploadManager.ts
+++ b/src/server/ApiManagers/UploadManager.ts
@@ -1,50 +1,27 @@
+import * as AdmZip from 'adm-zip';
import * as formidable from 'formidable';
-import { createReadStream, createWriteStream, unlink, writeFile } from 'fs';
-import * as path from 'path';
+import * as fs from 'fs';
+import { createReadStream, createWriteStream, unlink } from 'fs';
+import * as imageDataUri from 'image-data-uri';
import Jimp from 'jimp';
-import { filesDirectory, publicDirectory } from '..';
+import * as path from 'path';
+import * as uuid from 'uuid';
import { retrocycle } from '../../decycler/decycler';
+import { DashVersion } from '../../fields/DocSymbols';
import { DashUploadUtils, InjectSize, SizeSuffix } from '../DashUploadUtils';
-import { Database } from '../database';
import { Method, _success } from '../RouteManager';
-import RouteSubscriber from '../RouteSubscriber';
import { AcceptableMedia, Upload } from '../SharedMediaTypes';
+import { clientPathToFile, Directory, pathToDirectory, publicDirectory, serverPathToFile } from '../SocketData';
+import { Database } from '../database';
import ApiManager, { Registration } from './ApiManager';
import { SolrManager } from './SearchManager';
-import * as uuid from 'uuid';
-import { DashVersion } from '../../fields/DocSymbols';
-import * as AdmZip from 'adm-zip';
-import * as imageDataUri from 'image-data-uri';
-import * as fs from 'fs';
-
-export enum Directory {
- parsed_files = 'parsed_files',
- images = 'images',
- videos = 'videos',
- pdfs = 'pdfs',
- text = 'text',
- audio = 'audio',
- csv = 'csv',
-}
-
-export function serverPathToFile(directory: Directory, filename: string) {
- return path.normalize(`${filesDirectory}/${directory}/${filename}`);
-}
-
-export function pathToDirectory(directory: Directory) {
- return path.normalize(`${filesDirectory}/${directory}`);
-}
-
-export function clientPathToFile(directory: Directory, filename: string) {
- return `/files/${directory}/${filename}`;
-}
export default class UploadManager extends ApiManager {
protected initialize(register: Registration): void {
register({
method: Method.POST,
subscription: '/ping',
- secureHandler: async ({ req, res }) => {
+ secureHandler: async ({ /* req, */ res }) => {
_success(res, { message: DashVersion, date: new Date() });
},
});
@@ -78,31 +55,33 @@ export default class UploadManager extends ApiManager {
form.on('progress', e => fileguids.split(';').map(guid => DashUploadUtils.uploadProgress.set(guid, `read:(${Math.round((100 * +e) / +filesize)}%) ${e} of ${filesize}`)));
return new Promise<void>(resolve => {
form.parse(req, async (_err, _fields, files) => {
- const results: Upload.FileResponse[] = [];
if (_err?.message) {
- results.push({
- source: {
- filepath: '',
- originalFilename: 'none',
- newFilename: 'none',
- mimetype: 'text',
- size: 0,
- hashAlgorithm: 'md5',
- toJSON: () => ({ name: 'none', size: 0, length: 0, mtime: new Date(), filepath: '', originalFilename: 'none', newFilename: 'none', mimetype: 'text' }),
+ _success(res, [
+ {
+ source: {
+ filepath: '',
+ originalFilename: 'none',
+ newFilename: 'none',
+ mimetype: 'text',
+ size: 0,
+ hashAlgorithm: 'md5',
+ toJSON: () => ({ name: 'none', size: 0, length: 0, mtime: new Date(), filepath: '', originalFilename: 'none', newFilename: 'none', mimetype: 'text' }),
+ },
+ result: { name: 'failed upload', message: `${_err.message}` },
},
- result: { name: 'failed upload', message: `${_err.message}` },
- });
- }
- fileguids.split(';').map(guid => DashUploadUtils.uploadProgress.set(guid, `resampling images`));
+ ]);
+ } else {
+ fileguids.split(';').map(guid => DashUploadUtils.uploadProgress.set(guid, `resampling images`));
+ const results = (
+ await Promise.all(
+ Array.from(Object.keys(files)).map(
+ async key => (!files[key] ? undefined : DashUploadUtils.upload(files[key]![0] /* , key */)) // key is the guid used by the client to track upload progress.
+ )
+ )
+ ).filter(result => result && !(result.result instanceof Error));
- for (const key in files) {
- const f = files[key];
- if (f) {
- const result = await DashUploadUtils.upload(f[0], key); // key is the guid used by the client to track upload progress.
- result && !(result.result instanceof Error) && results.push(result);
- }
+ _success(res, results);
}
- _success(res, results);
resolve();
});
});
@@ -113,17 +92,14 @@ export default class UploadManager extends ApiManager {
method: Method.POST,
subscription: '/uploadYoutubeVideo',
secureHandler: async ({ req, res }) => {
- //req.readableBuffer.head.data
- return new Promise<void>(async resolve => {
- req.addListener('data', async args => {
- const payload = String.fromCharCode.apply(String, args);
- const { videoId, overwriteId } = JSON.parse(payload);
- const results: Upload.FileResponse[] = [];
- const result = await DashUploadUtils.uploadYoutube(videoId, overwriteId ?? videoId);
- result && results.push(result);
- _success(res, results);
- resolve();
- });
+ // req.readableBuffer.head.data
+ req.addListener('data', async args => {
+ const payload = String.fromCharCode(...args); // .apply(String, args);
+ const { videoId, overwriteId } = JSON.parse(payload);
+ const results: Upload.FileResponse[] = [];
+ const result = await DashUploadUtils.uploadYoutube(videoId, overwriteId ?? videoId);
+ result && results.push(result);
+ _success(res, results);
});
},
});
@@ -132,49 +108,10 @@ export default class UploadManager extends ApiManager {
method: Method.POST,
subscription: '/queryYoutubeProgress',
secureHandler: async ({ req, res }) => {
- return new Promise<void>(async resolve => {
- req.addListener('data', args => {
- const payload = String.fromCharCode.apply(String, args);
- const videoId = JSON.parse(payload).videoId;
- _success(res, { progress: DashUploadUtils.QueryYoutubeProgress(videoId, req.user) });
- resolve();
- });
- });
- },
- });
-
- register({
- method: Method.POST,
- subscription: new RouteSubscriber('youtubeScreenshot'),
- secureHandler: async ({ req, res }) => {
- const { id, timecode } = req.body;
- const convert = (raw: string) => {
- const number = Math.floor(Number(raw));
- const seconds = number % 60;
- const minutes = (number - seconds) / 60;
- return `${minutes}m${seconds}s`;
- };
- const suffix = timecode ? `&t=${convert(timecode)}` : ``;
- const targetUrl = `https://www.youtube.com/watch?v=${id}${suffix}`;
- const buffer = await captureYoutubeScreenshot(targetUrl);
- if (!buffer) {
- return res.send();
- }
- const resolvedName = `youtube_capture_${id}_${suffix}.png`;
- const resolvedPath = serverPathToFile(Directory.images, resolvedName);
- return new Promise<void>(resolve => {
- writeFile(resolvedPath, buffer, async error => {
- if (error) {
- return res.send();
- }
- await DashUploadUtils.outputResizedImages(resolvedPath, resolvedName, pathToDirectory(Directory.images));
- res.send({
- accessPaths: {
- agnostic: DashUploadUtils.getAccessPaths(Directory.images, resolvedName),
- },
- } as Upload.FileInformation);
- resolve();
- });
+ req.addListener('data', args => {
+ const payload = String.fromCharCode(...args); // .apply(String, args);
+ const { videoId } = JSON.parse(payload);
+ _success(res, { progress: DashUploadUtils.QueryYoutubeProgress(videoId) });
});
},
});
@@ -186,7 +123,8 @@ export default class UploadManager extends ApiManager {
const { sources } = req.body;
if (Array.isArray(sources)) {
const results = await Promise.all(sources.map(source => DashUploadUtils.UploadImage(source)));
- return res.send(results);
+ res.send(results);
+ return;
}
res.send();
},
@@ -203,20 +141,22 @@ export default class UploadManager extends ApiManager {
const getId = (id: string): string => {
if (!remap || id.endsWith('Proto')) return id;
if (id in ids) return ids[id];
- return (ids[id] = uuid.v4());
+ ids[id] = uuid.v4();
+ return ids[id];
};
- const mapFn = (doc: any) => {
+ const mapFn = (docIn: any) => {
+ const doc = docIn;
if (doc.id) {
doc.id = getId(doc.id);
}
+ // eslint-disable-next-line no-restricted-syntax
for (const key in doc.fields) {
- if (!doc.fields.hasOwnProperty(key)) {
- continue;
- }
+ // eslint-disable-next-line no-continue
+ if (!Object.prototype.hasOwnProperty.call(doc.fields, key)) continue;
+
const field = doc.fields[key];
- if (field === undefined || field === null) {
- continue;
- }
+ // eslint-disable-next-line no-continue
+ if (field === undefined || field === null) continue;
if (field.__type === 'Doc') {
mapFn(field);
@@ -229,78 +169,80 @@ export default class UploadManager extends ApiManager {
} else if (field.__type === 'list') {
mapFn(field);
} else if (typeof field === 'string') {
- const re = /("(?:dataD|d)ocumentId"\s*:\s*")([\w\-]*)"/g;
- doc.fields[key] = (field as any).replace(re, (match: any, p1: string, p2: string) => {
- return `${p1}${getId(p2)}"`;
- });
+ const re = /("(?:dataD|d)ocumentId"\s*:\s*")([\w-]*)"/g;
+ doc.fields[key] = (field as any).replace(re, (match: any, p1: string, p2: string) => `${p1}${getId(p2)}"`);
} else if (field.__type === 'RichTextField') {
const re = /("href"\s*:\s*")(.*?)"/g;
- field.Data = field.Data.replace(re, (match: any, p1: string, p2: string) => {
- return `${p1}${getId(p2)}"`;
- });
+ field.Data = field.Data.replace(re, (match: any, p1: string, p2: string) => `${p1}${getId(p2)}"`);
}
}
};
return new Promise<void>(resolve => {
form.parse(req, async (_err, fields, files) => {
- remap = Object.keys(fields).some(key => key === 'remap' && !fields.remap?.includes('false')); //.remap !== 'false'; // bcz: looking to see if the field 'remap' is set to 'false'
+ remap = Object.keys(fields).some(key => key === 'remap' && !fields.remap?.includes('false')); // .remap !== 'false'; // bcz: looking to see if the field 'remap' is set to 'false'
let id: string = '';
let docids: string[] = [];
let linkids: string[] = [];
try {
- for (const name in files) {
- const f = files[name];
- if (!f) continue;
- const path_2 = f[0]; // what about the rest of the array? are we guaranteed only one value is set?
- const zip = new AdmZip(path_2.filepath);
- zip.getEntries().forEach((entry: any) => {
- let entryName = entry.entryName.replace(/%%%/g, '/');
- if (!entryName.startsWith('files/')) {
- return;
- }
- const extension = path.extname(entryName);
- const pathname = publicDirectory + '/' + entry.entryName;
- const targetname = publicDirectory + '/' + entryName;
- try {
- zip.extractEntryTo(entry.entryName, publicDirectory, true, false);
- createReadStream(pathname).pipe(createWriteStream(targetname));
- Jimp.read(pathname).then(img => {
- DashUploadUtils.imageResampleSizes(extension).forEach(({ width, suffix }) => {
- const outputPath = InjectSize(targetname, suffix);
- if (!width) createReadStream(pathname).pipe(createWriteStream(outputPath));
- else img = img.resize(width, Jimp.AUTO).write(outputPath);
+ // eslint-disable-next-line no-restricted-syntax
+ for (const name in Object.keys(files)) {
+ if (Object.prototype.hasOwnProperty.call(files, name)) {
+ const f = files[name];
+ // eslint-disable-next-line no-continue
+ if (!f) continue;
+ const path2 = f[0]; // what about the rest of the array? are we guaranteed only one value is set?
+ const zip = new AdmZip(path2.filepath);
+ zip.getEntries().forEach((entry: any) => {
+ const entryName = entry.entryName.replace(/%%%/g, '/');
+ if (!entryName.startsWith('files/')) {
+ return;
+ }
+ const extension = path.extname(entryName);
+ const pathname = publicDirectory + '/' + entry.entryName;
+ const targetname = publicDirectory + '/' + entryName;
+ try {
+ zip.extractEntryTo(entry.entryName, publicDirectory, true, false);
+ createReadStream(pathname).pipe(createWriteStream(targetname));
+ Jimp.read(pathname).then(imgIn => {
+ let img = imgIn;
+ DashUploadUtils.imageResampleSizes(extension).forEach(({ width, suffix }) => {
+ const outputPath = InjectSize(targetname, suffix);
+ if (!width) createReadStream(pathname).pipe(createWriteStream(outputPath));
+ else img = img.resize(width, Jimp.AUTO).write(outputPath);
+ });
+ unlink(pathname, () => {});
});
- unlink(pathname, () => {});
- });
- } catch (e) {
- console.log(e);
- }
- });
- const json = zip.getEntry('docs.json');
- if (json) {
- try {
- const data = JSON.parse(json.getData().toString('utf8'), retrocycle());
- const { docs, links } = data;
- id = getId(data.id);
- const rdocs = Object.keys(docs).map(key => docs[key]);
- const ldocs = Object.keys(links).map(key => links[key]);
- [...rdocs, ...ldocs].forEach(mapFn);
- docids = rdocs.map(doc => doc.id);
- linkids = ldocs.map(link => link.id);
- await Promise.all(
- [...rdocs, ...ldocs].map(
- doc =>
- new Promise<void>(res => {
- // overwrite mongo doc with json doc contents
- Database.Instance.replace(doc.id, doc, (err, r) => res(err && console.log(err)), true);
- })
- )
- );
- } catch (e) {
- console.log(e);
+ } catch (e) {
+ console.log(e);
+ }
+ });
+ const json = zip.getEntry('docs.json');
+ if (json) {
+ try {
+ const data = JSON.parse(json.getData().toString('utf8'), retrocycle());
+ const { docs, links } = data;
+ id = getId(data.id);
+ const rdocs = Object.keys(docs).map(key => docs[key]);
+ const ldocs = Object.keys(links).map(key => links[key]);
+ [...rdocs, ...ldocs].forEach(mapFn);
+ docids = rdocs.map(doc => doc.id);
+ linkids = ldocs.map(link => link.id);
+ // eslint-disable-next-line no-await-in-loop
+ await Promise.all(
+ [...rdocs, ...ldocs].map(
+ doc =>
+ new Promise<void>(dbRes => {
+ // overwrite mongo doc with json doc contents
+ Database.Instance.replace(doc.id, doc, err => dbRes(err && console.log(err)), true);
+ })
+ )
+ );
+ } catch (e) {
+ console.log(e);
+ }
}
+ unlink(path2.filepath, () => {});
}
- unlink(path_2.filepath, () => {});
}
SolrManager.update();
res.send(JSON.stringify({ id, docids, linkids } || 'error'));
@@ -319,9 +261,8 @@ export default class UploadManager extends ApiManager {
secureHandler: async ({ req, res }) => {
const { source } = req.body;
if (typeof source === 'string') {
- return res.send(await DashUploadUtils.InspectImage(source));
- }
- res.send({});
+ res.send(await DashUploadUtils.InspectImage(source));
+ } else res.send({});
},
});
@@ -329,7 +270,7 @@ export default class UploadManager extends ApiManager {
method: Method.POST,
subscription: '/uploadURI',
secureHandler: ({ req, res }) => {
- const uri: any = req.body.uri;
+ const { uri } = req.body;
const filename = req.body.name;
const origSuffix = req.body.nosuffix ? SizeSuffix.None : SizeSuffix.Original;
const deleteFiles = req.body.replaceRootFilename;
@@ -338,23 +279,24 @@ export default class UploadManager extends ApiManager {
return;
}
if (deleteFiles) {
- const path = serverPathToFile(Directory.images, '');
+ const serverPath = serverPathToFile(Directory.images, '');
const regex = new RegExp(`${deleteFiles}.*`);
- fs.readdirSync(path)
+ fs.readdirSync(serverPath)
.filter((f: any) => regex.test(f))
- .map((f: any) => fs.unlinkSync(path + f));
+ .map((f: any) => fs.unlinkSync(serverPath + f));
}
- return imageDataUri.outputFile(uri, serverPathToFile(Directory.images, InjectSize(filename, origSuffix))).then((savedName: string) => {
+ imageDataUri.outputFile(uri, serverPathToFile(Directory.images, InjectSize(filename, origSuffix))).then((savedName: string) => {
const ext = path.extname(savedName).toLowerCase();
if (AcceptableMedia.imageFormats.includes(ext)) {
- Jimp.read(savedName).then(img =>
+ Jimp.read(savedName).then(imgIn => {
+ let img = imgIn;
(!origSuffix ? [{ width: 400, suffix: SizeSuffix.Medium }] : Object.values(DashUploadUtils.Sizes)) //
.forEach(({ width, suffix }) => {
const outputPath = serverPathToFile(Directory.images, InjectSize(filename, suffix) + ext);
if (!width) createReadStream(savedName).pipe(createWriteStream(outputPath));
else img = img.resize(width, Jimp.AUTO).write(outputPath);
- })
- );
+ });
+ });
}
res.send(clientPathToFile(Directory.images, filename + ext));
});
@@ -362,35 +304,3 @@ export default class UploadManager extends ApiManager {
});
}
}
-function delay(ms: number) {
- return new Promise(resolve => setTimeout(resolve, ms));
-}
-/**
- * On success, returns a buffer containing the bytes of a screenshot
- * of the video (optionally, at a timecode) specified by @param targetUrl.
- *
- * On failure, returns undefined.
- */
-async function captureYoutubeScreenshot(targetUrl: string) {
- // const browser = await launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'] });
- // const page = await browser.newPage();
- // // await page.setViewport({ width: 1920, height: 1080 });
-
- // // await page.goto(targetUrl, { waitUntil: 'domcontentloaded' as any });
-
- // const videoPlayer = await page.$('.html5-video-player');
- // videoPlayer && await page.focus("video");
- // await delay(7000);
- // const ad = await page.$('.ytp-ad-skip-button-text');
- // await ad?.click();
- // await videoPlayer?.click();
- // await delay(1000);
- // // hide youtube player controls.
- // await page.evaluate(() => (document.querySelector('.ytp-chrome-bottom') as HTMLElement).style.display = 'none');
-
- // const buffer = await videoPlayer?.screenshot({ encoding: "binary" });
- // await browser.close();
-
- // return buffer;
- return null;
-}
diff --git a/src/server/ApiManagers/UserManager.ts b/src/server/ApiManagers/UserManager.ts
index 0431b9bcf..b587340e2 100644
--- a/src/server/ApiManagers/UserManager.ts
+++ b/src/server/ApiManagers/UserManager.ts
@@ -1,16 +1,14 @@
-import ApiManager, { Registration } from './ApiManager';
-import { Method } from '../RouteManager';
-import { Database } from '../database';
-import { msToTime } from '../ActionUtilities';
import * as bcrypt from 'bcrypt-nodejs';
+import { check, validationResult } from 'express-validator';
+import { Utils } from '../../Utils';
import { Opt } from '../../fields/Doc';
-import { WebSocket } from '../websocket';
-import { resolvedPorts } from '../server_Initialization';
import { DashVersion } from '../../fields/DocSymbols';
-import { Utils } from '../../Utils';
-import { check, validationResult } from 'express-validator';
+import { msToTime } from '../ActionUtilities';
+import { Method } from '../RouteManager';
+import { resolvedPorts, socketMap, timeMap } from '../SocketData';
+import { Database } from '../database';
+import ApiManager, { Registration } from './ApiManager';
-export const timeMap: { [id: string]: number } = {};
interface ActivityUnit {
user: string;
duration: number;
@@ -32,9 +30,10 @@ export default class UserManager extends ApiManager {
method: Method.POST,
subscription: '/setCacheDocumentIds',
secureHandler: async ({ user, req, res }) => {
+ const userModel = user;
const result: any = {};
- user.cacheDocumentIds = req.body.cacheDocumentIds;
- user.save().then(undefined, err => {
+ userModel.cacheDocumentIds = req.body.cacheDocumentIds;
+ userModel.save().then(undefined, (err: any) => {
if (err) {
result.error = [{ msg: 'Error while caching documents' }];
}
@@ -90,17 +89,19 @@ export default class UserManager extends ApiManager {
method: Method.POST,
subscription: '/internalResetPassword',
secureHandler: async ({ user, req, res }) => {
+ const userModel = user;
const result: any = {};
- const { curr_pass, new_pass, new_confirm } = req.body;
+ // eslint-disable-next-line camelcase
+ const { curr_pass, new_pass } = req.body;
// perhaps should assert whether curr password is entered correctly
const validated = await new Promise<Opt<boolean>>(resolve => {
- bcrypt.compare(curr_pass, user.password, (err, passwords_match) => {
- if (err || !passwords_match) {
+ bcrypt.compare(curr_pass, userModel.password, (err, passwordsMatch) => {
+ if (err || !passwordsMatch) {
result.error = [{ msg: 'Incorrect current password' }];
res.send(result);
resolve(undefined);
} else {
- resolve(passwords_match);
+ resolve(passwordsMatch);
}
});
});
@@ -111,10 +112,11 @@ export default class UserManager extends ApiManager {
check('new_pass', 'Password must be at least 4 characters long')
.run(req)
- .then(chcekcres => console.log(chcekcres)); //.len({ min: 4 });
+ .then(chcekcres => console.log(chcekcres)); // .len({ min: 4 });
check('new_confirm', 'Passwords do not match')
.run(req)
- .then(theres => console.log(theres)); //.equals(new_pass);
+ .then(theres => console.log(theres)); // .equals(new_pass);
+ // eslint-disable-next-line camelcase
if (curr_pass === new_pass) {
result.error = [{ msg: 'Current and new password are the same' }];
}
@@ -125,12 +127,13 @@ export default class UserManager extends ApiManager {
// will only change password if there are no errors.
if (!result.error) {
- user.password = new_pass;
- user.passwordResetToken = undefined;
- user.passwordResetExpires = undefined;
+ // eslint-disable-next-line camelcase
+ userModel.password = new_pass;
+ userModel.passwordResetToken = undefined;
+ userModel.passwordResetExpires = undefined;
}
- user.save().then(undefined, err => {
+ userModel.save().then(undefined, err => {
if (err) {
result.error = [{ msg: 'Error while saving new password' }];
}
@@ -149,13 +152,16 @@ export default class UserManager extends ApiManager {
const activeTimes: ActivityUnit[] = [];
const inactiveTimes: ActivityUnit[] = [];
+ // eslint-disable-next-line no-restricted-syntax
for (const user in timeMap) {
- const time = timeMap[user];
- const socketPair = Array.from(WebSocket.socketMap).find(pair => pair[1] === user);
- if (socketPair && !socketPair[0].disconnected) {
- const duration = now - time;
- const target = duration / 1000 < 60 * 5 ? activeTimes : inactiveTimes;
- target.push({ user, duration });
+ if (Object.prototype.hasOwnProperty.call(timeMap, user)) {
+ const time = timeMap[user];
+ const socketPair = Array.from(socketMap).find(pair => pair[1] === user);
+ if (socketPair && !socketPair[0].disconnected) {
+ const duration = now - time;
+ const target = duration / 1000 < 60 * 5 ? activeTimes : inactiveTimes;
+ target.push({ user, duration });
+ }
}
}
diff --git a/src/server/ApiManagers/UtilManager.ts b/src/server/ApiManagers/UtilManager.ts
index e657866ce..8ad421a30 100644
--- a/src/server/ApiManagers/UtilManager.ts
+++ b/src/server/ApiManagers/UtilManager.ts
@@ -1,6 +1,7 @@
-import ApiManager, { Registration } from "./ApiManager";
-import { Method } from "../RouteManager";
import { exec } from 'child_process';
+import ApiManager, { Registration } from './ApiManager';
+import { Method } from '../RouteManager';
+
// import { IBM_Recommender } from "../../client/apis/IBM_Recommender";
// import { Recommender } from "../Recommender";
@@ -8,9 +9,7 @@ import { exec } from 'child_process';
// recommender.testModel();
export default class UtilManager extends ApiManager {
-
protected initialize(register: Registration): void {
-
// register({
// method: Method.POST,
// subscription: "/IBMAnalysis",
@@ -33,26 +32,25 @@ export default class UtilManager extends ApiManager {
register({
method: Method.GET,
- subscription: "/pull",
- secureHandler: async ({ res }) => {
- return new Promise<void>(resolve => {
+ subscription: '/pull',
+ secureHandler: async ({ res }) =>
+ new Promise<void>(resolve => {
exec('"C:\\Program Files\\Git\\git-bash.exe" -c "git pull"', err => {
if (err) {
res.send(err.message);
return;
}
- res.redirect("/");
+ res.redirect('/');
resolve();
});
- });
- }
+ }),
});
register({
method: Method.GET,
- subscription: "/version",
- secureHandler: ({ res }) => {
- return new Promise<void>(resolve => {
+ subscription: '/version',
+ secureHandler: ({ res }) =>
+ new Promise<void>(resolve => {
exec('"C:\\Program Files\\Git\\bin\\git.exe" rev-parse HEAD', (err, stdout) => {
if (err) {
res.send(err.message);
@@ -61,10 +59,7 @@ export default class UtilManager extends ApiManager {
res.send(stdout);
});
resolve();
- });
- }
+ }),
});
-
}
-
-} \ No newline at end of file
+}