From ee4910b1a90284c71ebdaa5fbd9243148ae113f6 Mon Sep 17 00:00:00 2001 From: Mohammad Amoush Date: Sat, 9 Nov 2019 16:29:20 -0500 Subject: initial --- src/server/ApiManagers/GeneralGoogleManager.ts | 48 ++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 src/server/ApiManagers/GeneralGoogleManager.ts (limited to 'src/server/ApiManagers/GeneralGoogleManager.ts') diff --git a/src/server/ApiManagers/GeneralGoogleManager.ts b/src/server/ApiManagers/GeneralGoogleManager.ts new file mode 100644 index 000000000..cb37b0dce --- /dev/null +++ b/src/server/ApiManagers/GeneralGoogleManager.ts @@ -0,0 +1,48 @@ +import ApiManager, { Registration } from "./ApiManager"; +import { Method, _permission_denied } from "../RouteManager"; +import { uploadDirectory } from ".."; +import { path } from "animejs"; +import { RouteStore } from "../RouteStore"; +import { GoogleApiServerUtils } from "../apis/google/GoogleApiServerUtils"; +import { Database } from "../database"; + +const deletionPermissionError = "Cannot perform specialized delete outside of the development environment!"; + +export default class GeneralGoogleManager extends ApiManager { + + protected initialize(register: Registration): void { + + register({ + method: Method.GET, + subscription: RouteStore.readGoogleAccessToken, + onValidation: async ({ user, res }) => { + const userId = user.id; + const token = await GoogleApiServerUtils.retrieveAccessToken(userId); + if (!token) { + return res.send(GoogleApiServerUtils.generateAuthenticationUrl()); + } + return res.send(token); + } + }); + + register({ + method: Method.POST, + subscription: RouteStore.writeGoogleAccessToken, + onValidation: async ({ user, req, res }) => { + res.send(await GoogleApiServerUtils.processNewUser(user.id, req.body.authenticationCode)); + } + }); + + register({ + method: Method.GET, + subscription: "/deleteWithGoogleCredentials", + onValidation: async ({ res, isRelease }) => { + if (isRelease) { + return _permission_denied(res, deletionPermissionError); + } + await Database.Auxiliary.GoogleAuthenticationToken.DeleteAll(); + res.redirect(RouteStore.delete); + } + }); + } +} \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 06a9b3477dfef93af3c2715f5512d0d883191b58 Mon Sep 17 00:00:00 2001 From: Mohammad Amoush Date: Tue, 12 Nov 2019 17:26:58 -0500 Subject: fixed everything except for async --- src/server/ApiManagers/GeneralGoogleManager.ts | 47 ++++++++++++++++++++--- src/server/ApiManagers/GooglePhotosManager.ts | 27 +++++++------ src/server/apis/google/GooglePhotosUploadUtils.ts | 2 +- src/server/index.ts | 9 +++-- 4 files changed, 62 insertions(+), 23 deletions(-) (limited to 'src/server/ApiManagers/GeneralGoogleManager.ts') diff --git a/src/server/ApiManagers/GeneralGoogleManager.ts b/src/server/ApiManagers/GeneralGoogleManager.ts index cb37b0dce..89efebf78 100644 --- a/src/server/ApiManagers/GeneralGoogleManager.ts +++ b/src/server/ApiManagers/GeneralGoogleManager.ts @@ -1,12 +1,20 @@ import ApiManager, { Registration } from "./ApiManager"; import { Method, _permission_denied } from "../RouteManager"; -import { uploadDirectory } from ".."; -import { path } from "animejs"; -import { RouteStore } from "../RouteStore"; import { GoogleApiServerUtils } from "../apis/google/GoogleApiServerUtils"; import { Database } from "../database"; +import RouteSubscriber from "../RouteSubscriber"; const deletionPermissionError = "Cannot perform specialized delete outside of the development environment!"; +const ServicesApiKeyMap = new Map([ + ["face", process.env.FACE], + ["vision", process.env.VISION], + ["handwriting", process.env.HANDWRITING] +]); +const EndpointHandlerMap = new Map([ + ["create", (api, params) => api.create(params)], + ["retrieve", (api, params) => api.get(params)], + ["update", (api, params) => api.batchUpdate(params)], +]); export default class GeneralGoogleManager extends ApiManager { @@ -14,7 +22,7 @@ export default class GeneralGoogleManager extends ApiManager { register({ method: Method.GET, - subscription: RouteStore.readGoogleAccessToken, + subscription: "/readGoogleAccessToken", onValidation: async ({ user, res }) => { const userId = user.id; const token = await GoogleApiServerUtils.retrieveAccessToken(userId); @@ -27,7 +35,7 @@ export default class GeneralGoogleManager extends ApiManager { register({ method: Method.POST, - subscription: RouteStore.writeGoogleAccessToken, + subscription: "/writeGoogleAccessToken", onValidation: async ({ user, req, res }) => { res.send(await GoogleApiServerUtils.processNewUser(user.id, req.body.authenticationCode)); } @@ -41,7 +49,34 @@ export default class GeneralGoogleManager extends ApiManager { return _permission_denied(res, deletionPermissionError); } await Database.Auxiliary.GoogleAuthenticationToken.DeleteAll(); - res.redirect(RouteStore.delete); + res.redirect("/delete"); + } + }); + + register({ + method: Method.GET, + subscription: new RouteSubscriber("/cognitiveServices").add('requestedservice'), + onValidation: ({ req, res }) => { + let service = req.params.requestedservice; + res.send(ServicesApiKeyMap.get(service)); + } + }); + + register({ + method: Method.POST, + subscription: new RouteSubscriber("/googleDocs").add("sector", "action"), + onValidation: async ({ req, res, user }) => { + let sector: GoogleApiServerUtils.Service = req.params.sector as GoogleApiServerUtils.Service; + let action: GoogleApiServerUtils.Action = req.params.action as GoogleApiServerUtils.Action; + const endpoint = await GoogleApiServerUtils.GetEndpoint(GoogleApiServerUtils.Service[sector], user.id); + let handler = EndpointHandlerMap.get(action); + if (endpoint && handler) { + handler(endpoint, req.body) + .then(response => res.send(response.data)) + .catch(exception => res.send(exception)); + return; + } + res.send(undefined); } }); } diff --git a/src/server/ApiManagers/GooglePhotosManager.ts b/src/server/ApiManagers/GooglePhotosManager.ts index b5e9caa38..1f6051c28 100644 --- a/src/server/ApiManagers/GooglePhotosManager.ts +++ b/src/server/ApiManagers/GooglePhotosManager.ts @@ -1,16 +1,12 @@ import ApiManager, { Registration } from "./ApiManager"; import { Method, _error, _success, _invalid } from "../RouteManager"; -import { uploadDirectory, NewMediaItem } from ".."; -import { path } from "animejs"; -import { RouteStore } from "../RouteStore"; +import * as path from "path"; import { GoogleApiServerUtils } from "../apis/google/GoogleApiServerUtils"; import { BatchedArray, TimeUnit } from "array-batcher"; import { GooglePhotosUploadUtils } from "../apis/google/GooglePhotosUploadUtils"; -import { MediaItem } from "../apis/google/SharedTypes"; import { Opt } from "../../new_fields/Doc"; import { DashUploadUtils } from "../DashUploadUtils"; import { Database } from "../database"; -import { prefix } from "@fortawesome/free-solid-svg-icons"; const authenticationError = "Unable to authenticate Google credentials before uploading to Google Photos!"; const mediaError = "Unable to convert all uploaded bytes to media items!"; @@ -23,6 +19,17 @@ interface GooglePhotosUploadFailure { url: string; reason: string; } +interface MediaItem { + baseUrl: string; + filename: string; +} +interface NewMediaItem { + description: string; + simpleMediaItem: { + uploadToken: string; + }; +} +const prefix = "google_photos_"; export default class GooglePhotosManager extends ApiManager { @@ -30,20 +37,18 @@ export default class GooglePhotosManager extends ApiManager { register({ method: Method.POST, - subscription: RouteStore.googlePhotosMediaUpload, + subscription: "/googlePhotosMediaUpload", onValidation: async ({ user, req, res }) => { const { media } = req.body; - const token = await GoogleApiServerUtils.retrieveAccessToken(user.id); if (!token) { return _error(res, authenticationError); } - let failed: GooglePhotosUploadFailure[] = []; const batched = BatchedArray.from(media, { batchSize: 25 }); const newMediaItems = await batched.batchedMapPatientInterval( { magnitude: 100, unit: TimeUnit.Milliseconds }, - async (batch, collector, { completedBatches }) => { + async (batch: any, collector: any, { completedBatches }: any) => { for (let index = 0; index < batch.length; index++) { const { url, description } = batch[index]; const fail = (reason: string) => failed.push({ reason, batch: completedBatches + 1, index, url }); @@ -59,13 +64,11 @@ export default class GooglePhotosManager extends ApiManager { } } ); - const failedCount = failed.length; if (failedCount) { console.error(`Unable to upload ${failedCount} image${failedCount === 1 ? "" : "s"} to Google's servers`); console.log(failed.map(({ reason, batch, index, url }) => `@${batch}.${index}: ${url} failed:\n${reason}`).join('\n\n')); } - return GooglePhotosUploadUtils.CreateMediaItems(token, newMediaItems, req.body.album).then( results => _success(res, { results, failed }), error => _error(res, mediaError, error) @@ -75,7 +78,7 @@ export default class GooglePhotosManager extends ApiManager { register({ method: Method.POST, - subscription: RouteStore.googlePhotosMediaDownload, + subscription: "/googlePhotosMediaDownload", onValidation: async ({ req, res }) => { const contents: { mediaItems: MediaItem[] } = req.body; let failed = 0; diff --git a/src/server/apis/google/GooglePhotosUploadUtils.ts b/src/server/apis/google/GooglePhotosUploadUtils.ts index 0abed3f1d..27532d7f0 100644 --- a/src/server/apis/google/GooglePhotosUploadUtils.ts +++ b/src/server/apis/google/GooglePhotosUploadUtils.ts @@ -122,7 +122,7 @@ export namespace GooglePhotosUploadUtils { // ...so we execute them in delayed batches and await the entire execution return batched.batchedMapPatientInterval( { magnitude: 100, unit: TimeUnit.Milliseconds }, - async (batch: NewMediaItem[], collector) => { + async (batch: NewMediaItem[], collector: any) => { const parameters = { method: 'POST', headers: headers('json', bearerToken), diff --git a/src/server/index.ts b/src/server/index.ts index 59752d6de..773b84403 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -4,10 +4,7 @@ import * as mobileDetect from 'mobile-detect'; import * as path from 'path'; import { Database } from './database'; const serverPort = 4321; -import { GooglePhotosUploadUtils } from './apis/google/GooglePhotosUploadUtils'; -import { Opt } from '../new_fields/Doc'; import { DashUploadUtils } from './DashUploadUtils'; -import { BatchedArray, TimeUnit } from 'array-batcher'; import RouteSubscriber from './RouteSubscriber'; import initializeServer from './Initialization'; import RouteManager, { Method, _success, _permission_denied, _error, _invalid, OnUnauthenticated } from './RouteManager'; @@ -21,6 +18,8 @@ import { GoogleCredentialsLoader } from './credentials/CredentialsLoader'; import DeleteManager from "./ApiManagers/DeleteManager"; import PDFManager from "./ApiManagers/PDFManager"; import UploadManager from "./ApiManagers/UploadManager"; +import GeneralGoogleManager from "./ApiManagers/GeneralGoogleManager"; +import GooglePhotosManager from "./ApiManagers/GooglePhotosManager"; export const publicDirectory = __dirname + "/public"; export const filesDirectory = publicDirectory + "/files/"; @@ -64,7 +63,9 @@ function routeSetter(router: RouteManager) { new SearchManager(), new PDFManager(), new DeleteManager(), - new UtilManager() + new UtilManager(), + new GeneralGoogleManager(), + new GooglePhotosManager(), ].forEach(manager => manager.register(router)); // initialize the web socket (bidirectional communication: if a user changes -- cgit v1.2.3-70-g09d2 From 00633c834c725bab78cef5bd7b9c4ff2b1449ccf Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Tue, 19 Nov 2019 11:18:08 -0500 Subject: api key route switched to environment, added client side util --- src/Utils.ts | 7 +++++- src/client/cognitive_services/CognitiveServices.ts | 28 ++++++++++------------ src/server/ApiManagers/GeneralGoogleManager.ts | 16 ++----------- src/server/ApiManagers/GooglePhotosManager.ts | 1 + 4 files changed, 22 insertions(+), 30 deletions(-) (limited to 'src/server/ApiManagers/GeneralGoogleManager.ts') diff --git a/src/Utils.ts b/src/Utils.ts index abff2eaba..91fa459c6 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -48,6 +48,11 @@ export namespace Utils { return prepend("/corsProxy/") + encodeURIComponent(url); } + export async function getApiKey(target: string): Promise { + const response = await fetch(prepend(`environment/${target.toUpperCase()}`)); + return response.text(); + } + export function CopyText(text: string) { var textArea = document.createElement("textarea"); textArea.value = text; @@ -174,7 +179,7 @@ export namespace Utils { } let idString = (message.id || "").padStart(36, ' '); prefix = prefix.padEnd(16, ' '); - console.log(`${prefix}: ${idString}, ${receiving ? 'receiving' : 'sending'} ${messageName} with data ${JSON.stringify(message)}`); + console.log(`${prefix}: ${idString}, ${receiving ? 'receiving' : 'sending'} ${messageName} with data ${JSON.stringify(message)} `); } function loggingCallback(prefix: string, func: (args: any) => any, messageName: string) { diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts index e74aef998..5a7f5e991 100644 --- a/src/client/cognitive_services/CognitiveServices.ts +++ b/src/client/cognitive_services/CognitiveServices.ts @@ -38,21 +38,19 @@ export enum Confidence { export namespace CognitiveServices { const ExecuteQuery = async (service: Service, manager: APIManager, data: D): Promise => { - return fetch(Utils.prepend(`environment/${service}`)).then(async response => { - let apiKey = await response.text(); - if (!apiKey) { - console.log(`No API key found for ${service}: ensure index.ts has access to a .env file in your root directory`); - return undefined; - } - - let results: any; - try { - results = await manager.requester(apiKey, manager.converter(data), service).then(json => JSON.parse(json)); - } catch { - results = undefined; - } - return results; - }); + const apiKey = await Utils.getApiKey(service); + if (!apiKey) { + console.log(`No API key found for ${service}: ensure index.ts has access to a .env file in your root directory.`); + return undefined; + } + + let results: any; + try { + results = await manager.requester(apiKey, manager.converter(data), service).then(json => JSON.parse(json)); + } catch { + results = undefined; + } + return results; }; export namespace Image { diff --git a/src/server/ApiManagers/GeneralGoogleManager.ts b/src/server/ApiManagers/GeneralGoogleManager.ts index 89efebf78..171912185 100644 --- a/src/server/ApiManagers/GeneralGoogleManager.ts +++ b/src/server/ApiManagers/GeneralGoogleManager.ts @@ -5,11 +5,7 @@ import { Database } from "../database"; import RouteSubscriber from "../RouteSubscriber"; const deletionPermissionError = "Cannot perform specialized delete outside of the development environment!"; -const ServicesApiKeyMap = new Map([ - ["face", process.env.FACE], - ["vision", process.env.VISION], - ["handwriting", process.env.HANDWRITING] -]); + const EndpointHandlerMap = new Map([ ["create", (api, params) => api.create(params)], ["retrieve", (api, params) => api.get(params)], @@ -53,15 +49,6 @@ export default class GeneralGoogleManager extends ApiManager { } }); - register({ - method: Method.GET, - subscription: new RouteSubscriber("/cognitiveServices").add('requestedservice'), - onValidation: ({ req, res }) => { - let service = req.params.requestedservice; - res.send(ServicesApiKeyMap.get(service)); - } - }); - register({ method: Method.POST, subscription: new RouteSubscriber("/googleDocs").add("sector", "action"), @@ -79,5 +66,6 @@ export default class GeneralGoogleManager extends ApiManager { res.send(undefined); } }); + } } \ No newline at end of file diff --git a/src/server/ApiManagers/GooglePhotosManager.ts b/src/server/ApiManagers/GooglePhotosManager.ts index 1f6051c28..1138dede1 100644 --- a/src/server/ApiManagers/GooglePhotosManager.ts +++ b/src/server/ApiManagers/GooglePhotosManager.ts @@ -107,5 +107,6 @@ export default class GooglePhotosManager extends ApiManager { _invalid(res, requestError); } }); + } } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 77ee66de66a411f79bbbc036d379d09be38d172f Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Mon, 2 Dec 2019 12:12:58 -0500 Subject: further cleanup --- src/Utils.ts | 2 - src/client/util/ClientDiagnostics.ts | 18 +-- .../util/Import & Export/DirectoryImportBox.tsx | 3 +- src/client/views/MainView.tsx | 2 +- src/client/views/collections/CollectionSubView.tsx | 6 +- src/server/ApiManagers/DeleteManager.ts | 1 - src/server/ApiManagers/GeneralGoogleManager.ts | 15 +-- src/server/ApiManagers/PDFManager.ts | 134 ++++++++++----------- src/server/ApiManagers/UploadManager.ts | 43 ++++--- src/server/DashUploadUtils.ts | 18 ++- src/server/RouteManager.ts | 14 +++ .../authentication/models/current_user_utils.ts | 3 +- src/server/credentials/test.json | 14 --- 13 files changed, 125 insertions(+), 148 deletions(-) delete mode 100644 src/server/credentials/test.json (limited to 'src/server/ApiManagers/GeneralGoogleManager.ts') diff --git a/src/Utils.ts b/src/Utils.ts index b60e9e023..2543743a4 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -2,8 +2,6 @@ import v4 = require('uuid/v4'); import v5 = require("uuid/v5"); import { Socket } from 'socket.io'; import { Message } from './server/Message'; -import { EventEmitter } from 'events'; -import { ConsoleColors } from './server/ActionUtilities'; export namespace Utils { diff --git a/src/client/util/ClientDiagnostics.ts b/src/client/util/ClientDiagnostics.ts index 7eef935fd..0a213aa1c 100644 --- a/src/client/util/ClientDiagnostics.ts +++ b/src/client/util/ClientDiagnostics.ts @@ -12,18 +12,22 @@ export namespace ClientDiagnostics { serverPolls--; }, 1000 * 15); - let executed = false; - const handle = async () => { + let solrHandle: NodeJS.Timeout | undefined; + const handler = async () => { const response = await fetch("/solrHeartbeat"); if (!(await response.json()).running) { - !executed && alert("Looks like SOLR is not running on your machine."); - executed = true; - clearInterval(solrHandle); + if (!executed) { + alert("Looks like SOLR is not running on your machine."); + executed = true; + solrHandle && clearInterval(solrHandle); + } } }; - await handle(); - const solrHandle = setInterval(handle, 1000 * 15); + await handler(); + if (!executed) { + solrHandle = setInterval(handler, 1000 * 15); + } } diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx index b5e806a97..104d9e099 100644 --- a/src/client/util/Import & Export/DirectoryImportBox.tsx +++ b/src/client/util/Import & Export/DirectoryImportBox.tsx @@ -106,7 +106,8 @@ export default class DirectoryImportBox extends React.Component runInAction(() => this.phase = `Internal: uploading ${this.quota - this.completed} files to Dash...`); - const uploads = await BatchedArray.from(validated, { batchSize: 15 }).batchedMapAsync(async (batch, collector) => { + const batched = BatchedArray.from(validated, { batchSize: 15 }); + const uploads = await batched.batchedMapAsync(async (batch, collector) => { const formData = new FormData(); batch.forEach(file => { diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 5231075a1..85dfd8be2 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -4,7 +4,7 @@ import { faMusic, faObjectGroup, faPause, faMousePointer, faPenNib, faFileAudio, faPen, faEraser, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faTv, faUndoAlt, faHighlighter, faMicrophone, faCompressArrowsAlt } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, configure, observable, reaction, runInAction, autorun } from 'mobx'; +import { action, computed, configure, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import "normalize.css"; import * as React from 'react'; diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index a1bd1527e..a1ae77fef 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -6,7 +6,7 @@ import { Id } from "../../../new_fields/FieldSymbols"; import { List } from "../../../new_fields/List"; import { listSpec } from "../../../new_fields/Schema"; import { ScriptField } from "../../../new_fields/ScriptField"; -import { Cast, StrCast } from "../../../new_fields/Types"; +import { Cast } from "../../../new_fields/Types"; import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; import { Utils } from "../../../Utils"; import { DocServer } from "../../DocServer"; @@ -279,9 +279,9 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { formData.append('file', file); let dropFileName = file ? file.name : "-empty-"; promises.push(Networking.PostFormDataToServer("/upload", formData).then(results => { - results.map(action((file: any) => { + results.map(action(({ clientAccessPath }: any) => { let full = { ...options, nativeWidth: type.indexOf("video") !== -1 ? 600 : 300, width: 300, title: dropFileName }; - let pathname = Utils.prepend(file.clientAccessPath); + let pathname = Utils.prepend(clientAccessPath); Docs.Get.DocumentFromType(type, pathname, full).then(doc => { doc && (Doc.GetProto(doc).fileUpload = path.basename(pathname).replace("upload_", "").replace(/\.[a-z0-9]*$/, "")); doc && this.props.addDocument(doc); diff --git a/src/server/ApiManagers/DeleteManager.ts b/src/server/ApiManagers/DeleteManager.ts index f58a28ce5..71818c673 100644 --- a/src/server/ApiManagers/DeleteManager.ts +++ b/src/server/ApiManagers/DeleteManager.ts @@ -56,7 +56,6 @@ export default class DeleteManager extends ApiManager { } }); - } } diff --git a/src/server/ApiManagers/GeneralGoogleManager.ts b/src/server/ApiManagers/GeneralGoogleManager.ts index 171912185..629684e0c 100644 --- a/src/server/ApiManagers/GeneralGoogleManager.ts +++ b/src/server/ApiManagers/GeneralGoogleManager.ts @@ -20,8 +20,7 @@ export default class GeneralGoogleManager extends ApiManager { method: Method.GET, subscription: "/readGoogleAccessToken", onValidation: async ({ user, res }) => { - const userId = user.id; - const token = await GoogleApiServerUtils.retrieveAccessToken(userId); + const token = await GoogleApiServerUtils.retrieveAccessToken(user.id); if (!token) { return res.send(GoogleApiServerUtils.generateAuthenticationUrl()); } @@ -37,18 +36,6 @@ export default class GeneralGoogleManager extends ApiManager { } }); - register({ - method: Method.GET, - subscription: "/deleteWithGoogleCredentials", - onValidation: async ({ res, isRelease }) => { - if (isRelease) { - return _permission_denied(res, deletionPermissionError); - } - await Database.Auxiliary.GoogleAuthenticationToken.DeleteAll(); - res.redirect("/delete"); - } - }); - register({ method: Method.POST, subscription: new RouteSubscriber("/googleDocs").add("sector", "action"), diff --git a/src/server/ApiManagers/PDFManager.ts b/src/server/ApiManagers/PDFManager.ts index 4bd750aaf..151b48dd9 100644 --- a/src/server/ApiManagers/PDFManager.ts +++ b/src/server/ApiManagers/PDFManager.ts @@ -4,10 +4,11 @@ import RouteSubscriber from "../RouteSubscriber"; import { exists, createReadStream, createWriteStream } from "fs"; import * as Pdfjs from 'pdfjs-dist'; import { createCanvas } from "canvas"; -const probe = require("probe-image-size"); +const imageSize = require("probe-image-size"); import * as express from "express"; import * as path from "path"; import { Directory, serverPathToFile, clientPathToFile } from "./UploadManager"; +import { ConsoleColors } from "../ActionUtilities"; export default class PDFManager extends ApiManager { @@ -16,84 +17,77 @@ export default class PDFManager extends ApiManager { register({ method: Method.GET, subscription: new RouteSubscriber("thumbnail").add("filename"), - onValidation: ({ req, res }) => { - let filename = req.params.filename; - let noExt = filename.substring(0, filename.length - ".png".length); - let pagenumber = parseInt(noExt.split('-')[1]); - return new Promise(resolve => { - const path = serverPathToFile(Directory.pdf_thumbnails, filename); - exists(path, (exists: boolean) => { - console.log(`${path} ${exists ? "exists" : "does not exist"}`); - if (exists) { - let input = createReadStream(path); - probe(input, (err: any, { width, height }: any) => { - if (err) { - console.log(err); - console.log(`error on ${filename}`); - return; - } - res.send({ - path: clientPathToFile(Directory.pdf_thumbnails, filename), - width, - height - }); - }); - } - else { - const name = filename.substring(0, filename.length - noExt.split('-')[1].length - ".PNG".length - 1) + ".pdf"; - LoadPage(serverPathToFile(Directory.pdfs, name), pagenumber, res); - } - resolve(); - }); - }); - } + onValidation: ({ req, res }) => getOrCreateThumbnail(req.params.filename, res) }); - function LoadPage(file: string, pageNumber: number, res: express.Response) { - console.log(file); - Pdfjs.getDocument(file).promise - .then((pdf: Pdfjs.PDFDocumentProxy) => { - let factory = new NodeCanvasFactory(); - console.log(pageNumber); - pdf.getPage(pageNumber).then((page: Pdfjs.PDFPageProxy) => { - console.log("reading " + page); - let viewport = page.getViewport(1 as any); - let canvasAndContext = factory.create(viewport.width, viewport.height); - let renderContext = { - canvasContext: canvasAndContext.context, - canvasFactory: factory, - viewport - }; - console.log("read " + pageNumber); + } - page.render(renderContext).promise - .then(() => { - console.log("saving " + pageNumber); - let stream = canvasAndContext.canvas.createPNGStream(); - let filenames = path.basename(file).split("."); - const pngFile = serverPathToFile(Directory.pdf_thumbnails, `${filenames[0]}-${pageNumber}.png`); - let out = createWriteStream(pngFile); - stream.pipe(out); - out.on("finish", () => { - console.log(`Success! Saved to ${pngFile}`); - res.send({ - path: pngFile, - width: viewport.width, - height: viewport.height - }); - }); - }, (reason: string) => { - console.error(reason + ` ${pageNumber}`); - }); +} + +function getOrCreateThumbnail(thumbnailName: string, res: express.Response) { + const noExtension = thumbnailName.substring(0, thumbnailName.length - ".png".length); + const pageString = noExtension.split('-')[1]; + const pageNumber = parseInt(pageString); + return new Promise(resolve => { + const path = serverPathToFile(Directory.pdf_thumbnails, thumbnailName); + exists(path, (exists: boolean) => { + if (exists) { + let existingThumbnail = createReadStream(path); + imageSize(existingThumbnail, (err: any, { width, height }: any) => { + if (err) { + console.log(ConsoleColors.Red, `In PDF thumbnail response, unable to determine dimensions of ${thumbnailName}:`); + console.log(err); + return; + } + res.send({ + path: clientPathToFile(Directory.pdf_thumbnails, thumbnailName), + width, + height }); }); - } - - } + } else { + const offset = thumbnailName.length - pageString.length - 5; + const name = thumbnailName.substring(0, offset) + ".pdf"; + const path = serverPathToFile(Directory.pdfs, name); + CreateThumbnail(path, pageNumber, res); + } + resolve(); + }); + }); +} +async function CreateThumbnail(file: string, pageNumber: number, res: express.Response) { + const documentProxy = await Pdfjs.getDocument(file).promise; + const factory = new NodeCanvasFactory(); + const page = await documentProxy.getPage(pageNumber); + const viewport = page.getViewport(1 as any); + const { canvas, context } = factory.create(viewport.width, viewport.height); + const renderContext = { + canvasContext: context, + canvasFactory: factory, + viewport + }; + await page.render(renderContext).promise; + const pngStream = canvas.createPNGStream(); + const filenames = path.basename(file).split("."); + const pngFile = serverPathToFile(Directory.pdf_thumbnails, `${filenames[0]}-${pageNumber}.png`); + const out = createWriteStream(pngFile); + pngStream.pipe(out); + out.on("finish", () => { + res.send({ + path: pngFile, + width: viewport.width, + height: viewport.height + }); + }); + out.on("error", error => { + console.log(ConsoleColors.Red, `In PDF thumbnail creation, encountered the following error when piping ${pngFile}:`); + console.log(error); + }); } class NodeCanvasFactory { + create = (width: number, height: number) => { var canvas = createCanvas(width, height); var context = canvas.getContext('2d'); diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts index 2f76871a6..80ae0ad61 100644 --- a/src/server/ApiManagers/UploadManager.ts +++ b/src/server/ApiManagers/UploadManager.ts @@ -38,6 +38,27 @@ export default class UploadManager extends ApiManager { protected initialize(register: Registration): void { + register({ + method: Method.POST, + subscription: "/upload", + onValidation: async ({ req, res }) => { + let form = new formidable.IncomingForm(); + form.uploadDir = pathToDirectory(Directory.parsed_files); + form.keepExtensions = true; + return new Promise(resolve => { + form.parse(req, async (_err, _fields, files) => { + let results: any[] = []; + for (const key in files) { + const result = await DashUploadUtils.upload(files[key]); + result && results.push(result); + } + _success(res, results); + resolve(); + }); + }); + } + }); + register({ method: Method.POST, subscription: "/uploadDoc", @@ -142,28 +163,6 @@ export default class UploadManager extends ApiManager { } }); - - register({ - method: Method.POST, - subscription: "/upload", - onValidation: async ({ req, res }) => { - let form = new formidable.IncomingForm(); - form.uploadDir = pathToDirectory(Directory.parsed_files); - form.keepExtensions = true; - return new Promise(resolve => { - form.parse(req, async (_err, _fields, files) => { - let results: any[] = []; - for (const key in files) { - const result = await DashUploadUtils.upload(files[key]); - result && results.push(result); - } - _success(res, results); - resolve(); - }); - }); - } - }); - register({ method: Method.POST, subscription: "/inspectImage", diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts index c831eb072..9ccc72e35 100644 --- a/src/server/DashUploadUtils.ts +++ b/src/server/DashUploadUtils.ts @@ -85,7 +85,8 @@ export namespace DashUploadUtils { return UploadPdf(path); } } - console.log(ConsoleColors.Red, `Ignoring unsupported file ${name} with upload type (${type}).`); + + console.log(ConsoleColors.Red, `Ignoring unsupported file (${name}) with upload type (${type}).`); return { clientAccessPath: undefined }; } @@ -169,17 +170,12 @@ export namespace DashUploadUtils { if (isLocal) { return results; } - const metadata = (await new Promise((resolve, reject) => { - request.head(source, async (error, res) => { - if (error) { - return reject(error); - } - resolve(res); - }); - })).headers; + const { headers } = (await new Promise((resolve, reject) => { + request.head(source, (error, res) => error ? reject(error) : resolve(res)); + })); return { - contentSize: parseInt(metadata[size]), - contentType: metadata[type], + contentSize: parseInt(headers[size]), + contentType: headers[type], ...results }; }; diff --git a/src/server/RouteManager.ts b/src/server/RouteManager.ts index 3a20d5af5..7c49485f1 100644 --- a/src/server/RouteManager.ts +++ b/src/server/RouteManager.ts @@ -26,6 +26,8 @@ export interface RouteInitializer { onError?: OnError; } +const registered = new Map>(); + export default class RouteManager { private server: express.Express; private _isRelease: boolean; @@ -89,6 +91,18 @@ export default class RouteManager { } else { route = subscriber.build; } + const existing = registered.get(route); + if (existing) { + if (existing.has(method)) { + console.log(ConsoleColors.Red, `\nDuplicate registration error: already registered ${route} with Method[${method}]`); + console.log('Please remove duplicate registrations before continuing...\n'); + process.exit(0); + } + } else { + const specific = new Set(); + specific.add(method); + registered.set(route, specific); + } switch (method) { case Method.GET: this.server.get(route, supervised); diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 052aa54a6..ac4462f78 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -1,4 +1,4 @@ -import { action, computed, observable, reaction, runInAction } from "mobx"; +import { action, computed, observable, reaction } from "mobx"; import * as rp from 'request-promise'; import { DocServer } from "../../../client/DocServer"; import { Docs } from "../../../client/documents/Documents"; @@ -11,7 +11,6 @@ import { listSpec } from "../../../new_fields/Schema"; import { ScriptField, ComputedField } from "../../../new_fields/ScriptField"; import { Cast, PromiseValue } from "../../../new_fields/Types"; import { Utils } from "../../../Utils"; -import { ButtonBox } from "../../../client/views/nodes/ButtonBox"; import { nullAudio } from "../../../new_fields/URLField"; import { DragManager } from "../../../client/util/DragManager"; import { InkingControl } from "../../../client/views/InkingControl"; diff --git a/src/server/credentials/test.json b/src/server/credentials/test.json deleted file mode 100644 index 0a032cc2d..000000000 --- a/src/server/credentials/test.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "installed": { - "client_id": "343179513178-ud6tvmh275r2fq93u9eesrnc66t6akh9.apps.googleusercontent.com", - "project_id": "quickstart-1565056383187", - "auth_uri": "https://accounts.google.com/o/oauth2/auth", - "token_uri": "https://oauth2.googleapis.com/token", - "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", - "client_secret": "w8KIFSc0MQpmUYHed4qEzn8b", - "redirect_uris": [ - "urn:ietf:wg:oauth:2.0:oob", - "http://localhost" - ] - } -} \ No newline at end of file -- cgit v1.2.3-70-g09d2