aboutsummaryrefslogtreecommitdiff
path: root/src/server
diff options
context:
space:
mode:
Diffstat (limited to 'src/server')
-rw-r--r--src/server/ApiManagers/DeleteManager.ts85
-rw-r--r--src/server/ApiManagers/GeneralGoogleManager.ts19
-rw-r--r--src/server/ApiManagers/GooglePhotosManager.ts2
-rw-r--r--src/server/ApiManagers/SessionManager.ts2
-rw-r--r--src/server/ApiManagers/UploadManager.ts2
-rw-r--r--src/server/ApiManagers/UserManager.ts2
-rw-r--r--src/server/DashSession/DashSessionAgent.ts2
-rw-r--r--src/server/DashUploadUtils.ts7
-rw-r--r--src/server/GarbageCollector.ts6
-rw-r--r--src/server/IDatabase.ts4
-rw-r--r--src/server/MemoryDatabase.ts23
-rw-r--r--src/server/Websocket/Websocket.ts44
-rw-r--r--src/server/apis/google/GoogleApiServerUtils.ts47
-rw-r--r--src/server/authentication/models/current_user_utils.ts185
-rw-r--r--src/server/authentication/models/user_model.ts4
-rw-r--r--src/server/database.ts116
-rw-r--r--src/server/remapUrl.ts4
17 files changed, 289 insertions, 265 deletions
diff --git a/src/server/ApiManagers/DeleteManager.ts b/src/server/ApiManagers/DeleteManager.ts
index 9e70af2eb..bd80d6500 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, PublicHandler } from "../RouteManager";
+import { Method, _permission_denied } from "../RouteManager";
import { WebSocket } from "../Websocket/Websocket";
import { Database } from "../database";
import rimraf = require("rimraf");
-import { pathToDirectory, Directory } from "./UploadManager";
import { filesDirectory } from "..";
import { DashUploadUtils } from "../DashUploadUtils";
import { mkdirSync } from "fs";
+import RouteSubscriber from "../RouteSubscriber";
export default class DeleteManager extends ApiManager {
@@ -14,68 +14,39 @@ export default class DeleteManager extends ApiManager {
register({
method: Method.GET,
- subscription: "/delete",
- secureHandler: async ({ res, isRelease }) => {
+ subscription: new RouteSubscriber("delete").add("target?"),
+ secureHandler: async ({ req, res, isRelease }) => {
if (isRelease) {
- return _permission_denied(res, deletionPermissionError);
+ return _permission_denied(res, "Cannot perform a delete operation outside of the development environment!");
}
- await WebSocket.deleteFields();
- res.redirect("/home");
- }
- });
- register({
- method: Method.GET,
- subscription: "/deleteAll",
- secureHandler: async ({ res, isRelease }) => {
- if (isRelease) {
- return _permission_denied(res, deletionPermissionError);
+ const { target } = req.params;
+ const { doDelete } = WebSocket;
+
+ if (!target) {
+ await doDelete();
+ } else {
+ let all = false;
+ switch (target) {
+ case "all":
+ all = true;
+ case "database":
+ await doDelete(false);
+ if (!all) break;
+ case "files":
+ rimraf.sync(filesDirectory);
+ mkdirSync(filesDirectory);
+ await DashUploadUtils.buildFileDirectories();
+ break;
+ default:
+ await Database.Instance.dropSchema(target);
+ }
}
- await WebSocket.deleteAll();
- res.redirect("/home");
- }
- });
- register({
- method: Method.GET,
- subscription: "/deleteAssets",
- secureHandler: async ({ res, isRelease }) => {
- if (isRelease) {
- return _permission_denied(res, deletionPermissionError);
- }
- rimraf.sync(filesDirectory);
- mkdirSync(filesDirectory);
- await DashUploadUtils.buildFileDirectories();
- res.redirect("/delete");
- }
- });
-
- register({
- method: Method.GET,
- subscription: "/deleteWithAux",
- secureHandler: async ({ res, isRelease }) => {
- if (isRelease) {
- return _permission_denied(res, deletionPermissionError);
- }
- await Database.Auxiliary.DeleteAll();
- res.redirect("/delete");
- }
- });
-
- register({
- method: Method.GET,
- subscription: "/deleteWithGoogleCredentials",
- secureHandler: async ({ res, isRelease }) => {
- if (isRelease) {
- return _permission_denied(res, deletionPermissionError);
- }
- await Database.Auxiliary.GoogleAuthenticationToken.DeleteAll();
- res.redirect("/delete");
+ res.redirect("/home");
}
});
}
-}
-
-const deletionPermissionError = "Cannot perform a delete operation outside of the development environment!";
+} \ No newline at end of file
diff --git a/src/server/ApiManagers/GeneralGoogleManager.ts b/src/server/ApiManagers/GeneralGoogleManager.ts
index a5240edbc..17968cc7d 100644
--- a/src/server/ApiManagers/GeneralGoogleManager.ts
+++ b/src/server/ApiManagers/GeneralGoogleManager.ts
@@ -1,10 +1,8 @@
import ApiManager, { Registration } from "./ApiManager";
import { Method, _permission_denied } from "../RouteManager";
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!";
+import { Database } from "../database";
const EndpointHandlerMap = new Map<GoogleApiServerUtils.Action, GoogleApiServerUtils.ApiRouter>([
["create", (api, params) => api.create(params)],
@@ -20,11 +18,11 @@ export default class GeneralGoogleManager extends ApiManager {
method: Method.GET,
subscription: "/readGoogleAccessToken",
secureHandler: async ({ user, res }) => {
- const token = await GoogleApiServerUtils.retrieveAccessToken(user.id);
- if (!token) {
+ const { credentials } = (await GoogleApiServerUtils.retrieveCredentials(user.id));
+ if (!credentials?.access_token) {
return res.send(GoogleApiServerUtils.generateAuthenticationUrl());
}
- return res.send(token);
+ return res.send(credentials);
}
});
@@ -37,6 +35,15 @@ export default class GeneralGoogleManager extends ApiManager {
});
register({
+ method: Method.GET,
+ subscription: "/revokeGoogleAccessToken",
+ secureHandler: async ({ user, res }) => {
+ await Database.Auxiliary.GoogleAuthenticationToken.Revoke(user.id);
+ res.send();
+ }
+ });
+
+ register({
method: Method.POST,
subscription: new RouteSubscriber("googleDocs").add("sector", "action"),
secureHandler: async ({ req, res, user }) => {
diff --git a/src/server/ApiManagers/GooglePhotosManager.ts b/src/server/ApiManagers/GooglePhotosManager.ts
index 88219423d..11841a603 100644
--- a/src/server/ApiManagers/GooglePhotosManager.ts
+++ b/src/server/ApiManagers/GooglePhotosManager.ts
@@ -56,7 +56,7 @@ export default class GooglePhotosManager extends ApiManager {
const { media } = req.body;
// first we need to ensure that we know the google account to which these photos will be uploaded
- const token = await GoogleApiServerUtils.retrieveAccessToken(user.id);
+ const token = (await GoogleApiServerUtils.retrieveCredentials(user.id))?.credentials?.access_token;
if (!token) {
return _error(res, authenticationError);
}
diff --git a/src/server/ApiManagers/SessionManager.ts b/src/server/ApiManagers/SessionManager.ts
index bcaa6598f..fa2f6002a 100644
--- a/src/server/ApiManagers/SessionManager.ts
+++ b/src/server/ApiManagers/SessionManager.ts
@@ -55,7 +55,7 @@ export default class SessionManager extends ApiManager {
register({
method: Method.GET,
- subscription: this.secureSubscriber("delete"),
+ 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.");
diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts
index 98f029c7d..b185d3b55 100644
--- a/src/server/ApiManagers/UploadManager.ts
+++ b/src/server/ApiManagers/UploadManager.ts
@@ -171,7 +171,7 @@ export default class UploadManager extends ApiManager {
await Promise.all(docs.map((doc: any) => new Promise(res => Database.Instance.replace(doc.id, doc, (err, r) => {
err && console.log(err);
res();
- }, true, "newDocuments"))));
+ }, true))));
} catch (e) { console.log(e); }
unlink(path_2, () => { });
}
diff --git a/src/server/ApiManagers/UserManager.ts b/src/server/ApiManagers/UserManager.ts
index d9d346cc1..68b3107ae 100644
--- a/src/server/ApiManagers/UserManager.ts
+++ b/src/server/ApiManagers/UserManager.ts
@@ -89,8 +89,6 @@ export default class UserManager extends ApiManager {
}
});
-
-
register({
method: Method.GET,
subscription: "/activity",
diff --git a/src/server/DashSession/DashSessionAgent.ts b/src/server/DashSession/DashSessionAgent.ts
index 5cbba13de..ef9b88541 100644
--- a/src/server/DashSession/DashSessionAgent.ts
+++ b/src/server/DashSession/DashSessionAgent.ts
@@ -37,7 +37,7 @@ export class DashSessionAgent extends AppliedSessionAgent {
monitor.addReplCommand("debug", [/\S+\@\S+/], async ([to]) => this.dispatchZippedDebugBackup(to));
monitor.on("backup", this.backup);
monitor.on("debug", async ({ to }) => this.dispatchZippedDebugBackup(to));
- monitor.on("delete", WebSocket.deleteFields);
+ monitor.on("delete", WebSocket.doDelete);
monitor.coreHooks.onCrashDetected(this.dispatchCrashReport);
return sessionKey;
}
diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts
index 3f903a861..8567631cd 100644
--- a/src/server/DashUploadUtils.ts
+++ b/src/server/DashUploadUtils.ts
@@ -325,12 +325,7 @@ export namespace DashUploadUtils {
const outputPath = path.resolve(outputDirectory, writtenFiles[suffix] = InjectSize(outputFileName, suffix));
await new Promise<void>(async (resolve, reject) => {
const source = streamProvider();
- let readStream: Stream;
- if (source instanceof Promise) {
- readStream = await source;
- } else {
- readStream = source;
- }
+ let readStream: Stream = source instanceof Promise ? await source : source;
if (resizer) {
readStream = readStream.pipe(resizer.withMetadata());
}
diff --git a/src/server/GarbageCollector.ts b/src/server/GarbageCollector.ts
index 5729c3ee5..24745cbb4 100644
--- a/src/server/GarbageCollector.ts
+++ b/src/server/GarbageCollector.ts
@@ -76,7 +76,7 @@ async function GarbageCollect(full: boolean = true) {
if (!fetchIds.length) {
continue;
}
- const docs = await new Promise<{ [key: string]: any }[]>(res => Database.Instance.getDocuments(fetchIds, res, "newDocuments"));
+ const docs = await new Promise<{ [key: string]: any }[]>(res => Database.Instance.getDocuments(fetchIds, res));
for (const doc of docs) {
const id = doc.id;
if (doc === undefined) {
@@ -116,10 +116,10 @@ async function GarbageCollect(full: boolean = true) {
const count = Math.min(toDelete.length, 5000);
const toDeleteDocs = toDelete.slice(i, i + count);
i += count;
- const result = await Database.Instance.delete({ _id: { $in: toDeleteDocs } }, "newDocuments");
+ const result = await Database.Instance.delete({ _id: { $in: toDeleteDocs } });
deleted += result.deletedCount || 0;
}
- // const result = await Database.Instance.delete({ _id: { $in: toDelete } }, "newDocuments");
+ // const result = await Database.Instance.delete({ _id: { $in: toDelete } });
console.log(`${deleted} documents deleted`);
await Search.deleteDocuments(toDelete);
diff --git a/src/server/IDatabase.ts b/src/server/IDatabase.ts
index 6a63df485..dd4968579 100644
--- a/src/server/IDatabase.ts
+++ b/src/server/IDatabase.ts
@@ -2,7 +2,6 @@ import * as mongodb from 'mongodb';
import { Transferable } from './Message';
export const DocumentsCollection = 'documents';
-export const NewDocumentsCollection = 'newDocuments';
export interface IDatabase {
update(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateWriteOpResult) => void, upsert?: boolean, collectionName?: string): Promise<void>;
updateMany(query: any, update: any, collectionName?: string): Promise<mongodb.WriteOpResult>;
@@ -12,12 +11,13 @@ export interface IDatabase {
delete(query: any, collectionName?: string): Promise<mongodb.DeleteWriteOpResultObject>;
delete(id: string, collectionName?: string): Promise<mongodb.DeleteWriteOpResultObject>;
- deleteAll(collectionName?: string, persist?: boolean): Promise<any>;
+ dropSchema(...schemaNames: string[]): Promise<any>;
insert(value: any, collectionName?: string): Promise<void>;
getDocument(id: string, fn: (result?: Transferable) => void, collectionName?: string): void;
getDocuments(ids: string[], fn: (result: Transferable[]) => void, collectionName?: string): void;
+ getCollectionNames(): Promise<string[]>;
visit(ids: string[], fn: (result: any) => string[] | Promise<string[]>, collectionName?: string): Promise<void>;
query(query: { [key: string]: any }, projection?: { [key: string]: 0 | 1 }, collectionName?: string): Promise<mongodb.Cursor>;
diff --git a/src/server/MemoryDatabase.ts b/src/server/MemoryDatabase.ts
index 543f96e7f..1f1d702d9 100644
--- a/src/server/MemoryDatabase.ts
+++ b/src/server/MemoryDatabase.ts
@@ -1,4 +1,4 @@
-import { IDatabase, DocumentsCollection, NewDocumentsCollection } from './IDatabase';
+import { IDatabase, DocumentsCollection } from './IDatabase';
import { Transferable } from './Message';
import * as mongodb from 'mongodb';
@@ -15,6 +15,10 @@ export class MemoryDatabase implements IDatabase {
}
}
+ public getCollectionNames() {
+ return Promise.resolve(Object.keys(this.db));
+ }
+
public update(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateWriteOpResult) => void, _upsert?: boolean, collectionName = DocumentsCollection): Promise<void> {
const collection = this.getCollection(collectionName);
const set = "$set";
@@ -41,7 +45,7 @@ export class MemoryDatabase implements IDatabase {
return Promise.resolve(undefined);
}
- public updateMany(query: any, update: any, collectionName = NewDocumentsCollection): Promise<mongodb.WriteOpResult> {
+ public updateMany(query: any, update: any, collectionName = DocumentsCollection): Promise<mongodb.WriteOpResult> {
throw new Error("Can't updateMany a MemoryDatabase");
}
@@ -58,8 +62,15 @@ export class MemoryDatabase implements IDatabase {
return Promise.resolve({} as any);
}
- public deleteAll(collectionName = DocumentsCollection, _persist = true): Promise<any> {
- delete this.db[collectionName];
+ public async dropSchema(...schemaNames: string[]): Promise<any> {
+ const existing = await this.getCollectionNames();
+ let valid: string[];
+ if (schemaNames.length) {
+ valid = schemaNames.filter(collection => existing.includes(collection));
+ } else {
+ valid = existing;
+ }
+ valid.forEach(schemaName => delete this.db[schemaName]);
return Promise.resolve();
}
@@ -69,14 +80,14 @@ export class MemoryDatabase implements IDatabase {
return Promise.resolve();
}
- public getDocument(id: string, fn: (result?: Transferable) => void, collectionName = NewDocumentsCollection): void {
+ public getDocument(id: string, fn: (result?: Transferable) => void, collectionName = DocumentsCollection): void {
fn(this.getCollection(collectionName)[id]);
}
public getDocuments(ids: string[], fn: (result: Transferable[]) => void, collectionName = DocumentsCollection): void {
fn(ids.map(id => this.getCollection(collectionName)[id]));
}
- public async visit(ids: string[], fn: (result: any) => string[] | Promise<string[]>, collectionName = NewDocumentsCollection): Promise<void> {
+ public async visit(ids: string[], fn: (result: any) => string[] | Promise<string[]>, collectionName = DocumentsCollection): Promise<void> {
const visited = new Set<string>();
while (ids.length) {
const count = Math.min(ids.length, 1000);
diff --git a/src/server/Websocket/Websocket.ts b/src/server/Websocket/Websocket.ts
index 947aa4289..844535056 100644
--- a/src/server/Websocket/Websocket.ts
+++ b/src/server/Websocket/Websocket.ts
@@ -10,9 +10,9 @@ import { GoogleCredentialsLoader } from "../credentials/CredentialsLoader";
import { logPort } from "../ActionUtilities";
import { timeMap } from "../ApiManagers/UserManager";
import { green } from "colors";
-import { serverPathToFile, Directory } from "../ApiManagers/UploadManager";
import { networkInterfaces } from "os";
import executeImport from "../../scraping/buxton/final/BuxtonImporter";
+import { DocumentsCollection } from "../IDatabase";
export namespace WebSocket {
@@ -97,7 +97,7 @@ export namespace WebSocket {
Utils.AddServerHandlerCallback(socket, MessageStore.GetField, getField);
Utils.AddServerHandlerCallback(socket, MessageStore.GetFields, getFields);
if (isRelease) {
- Utils.AddServerHandler(socket, MessageStore.DeleteAll, deleteFields);
+ Utils.AddServerHandler(socket, MessageStore.DeleteAll, () => doDelete(false));
}
Utils.AddServerHandler(socket, MessageStore.CreateField, CreateField);
@@ -111,6 +111,12 @@ export namespace WebSocket {
Utils.AddServerHandler(socket, MessageStore.MobileDocumentUpload, content => processMobileDocumentUpload(socket, content));
Utils.AddServerHandlerCallback(socket, MessageStore.GetRefField, GetRefField);
Utils.AddServerHandlerCallback(socket, MessageStore.GetRefFields, GetRefFields);
+
+ /**
+ * Whenever we receive the go-ahead, invoke the import script and pass in
+ * as an emitter and a terminator the functions that simply broadcast a result
+ * or indicate termination to the client via the web socket
+ */
Utils.AddServerHandler(socket, MessageStore.BeginBuxtonImport, () => {
executeImport(
deviceOrError => Utils.Emit(socket, MessageStore.BuxtonDocumentResult, deviceOrError),
@@ -158,24 +164,10 @@ export namespace WebSocket {
}
}
- export async function deleteFields() {
- await Database.Instance.deleteAll();
- if (process.env.DISABLE_SEARCH !== "true") {
- await Search.clear();
- }
- await Database.Instance.deleteAll('newDocuments');
- }
-
- // export async function deleteUserDocuments() {
- // await Database.Instance.deleteAll();
- // await Database.Instance.deleteAll('newDocuments');
- // }
-
- export async function deleteAll() {
- await Database.Instance.deleteAll();
- await Database.Instance.deleteAll('newDocuments');
- await Database.Instance.deleteAll('sessions');
- await Database.Instance.deleteAll('users');
+ export async function doDelete(onlyFields = true) {
+ const target: string[] = [];
+ onlyFields && target.push(DocumentsCollection);
+ await Database.Instance.dropSchema(...target);
if (process.env.DISABLE_SEARCH !== "true") {
await Search.clear();
}
@@ -205,11 +197,11 @@ export namespace WebSocket {
}
function GetRefField([id, callback]: [string, (result?: Transferable) => void]) {
- Database.Instance.getDocument(id, callback, "newDocuments");
+ Database.Instance.getDocument(id, callback);
}
function GetRefFields([ids, callback]: [string[], (result?: Transferable[]) => void]) {
- Database.Instance.getDocuments(ids, callback, "newDocuments");
+ Database.Instance.getDocuments(ids, callback);
}
const suffixMap: { [type: string]: (string | [string, string | ((json: any) => any)]) } = {
@@ -266,7 +258,7 @@ export namespace WebSocket {
function UpdateField(socket: Socket, diff: Diff) {
Database.Instance.update(diff.id, diff.diff,
- () => socket.broadcast.emit(MessageStore.UpdateField.Message, diff), false, "newDocuments");
+ () => socket.broadcast.emit(MessageStore.UpdateField.Message, diff), false);
const docfield = diff.diff.$set || diff.diff.$unset;
if (!docfield) {
return;
@@ -291,7 +283,7 @@ export namespace WebSocket {
}
function DeleteField(socket: Socket, id: string) {
- Database.Instance.delete({ _id: id }, "newDocuments").then(() => {
+ Database.Instance.delete({ _id: id }).then(() => {
socket.broadcast.emit(MessageStore.DeleteField.Message, id);
});
@@ -299,14 +291,14 @@ export namespace WebSocket {
}
function DeleteFields(socket: Socket, ids: string[]) {
- Database.Instance.delete({ _id: { $in: ids } }, "newDocuments").then(() => {
+ Database.Instance.delete({ _id: { $in: ids } }).then(() => {
socket.broadcast.emit(MessageStore.DeleteFields.Message, ids);
});
Search.deleteDocuments(ids);
}
function CreateField(newValue: any) {
- Database.Instance.insert(newValue, "newDocuments");
+ Database.Instance.insert(newValue);
}
}
diff --git a/src/server/apis/google/GoogleApiServerUtils.ts b/src/server/apis/google/GoogleApiServerUtils.ts
index 0f75833ee..48a8da89f 100644
--- a/src/server/apis/google/GoogleApiServerUtils.ts
+++ b/src/server/apis/google/GoogleApiServerUtils.ts
@@ -149,26 +149,6 @@ export namespace GoogleApiServerUtils {
}
/**
- * Returns the lengthy string or access token that can be passed into
- * the headers of an API request or into the constructor of the Photos
- * client API wrapper.
- * @param userId the Dash user id of the user requesting his/her associated
- * access_token
- * @returns the current access_token associated with the requesting
- * Dash user. The access_token is valid for only an hour, and
- * is then refreshed.
- */
- export async function retrieveAccessToken(userId: string): Promise<string> {
- return new Promise(async resolve => {
- const { credentials } = await retrieveCredentials(userId);
- if (!credentials) {
- return resolve();
- }
- resolve(credentials.access_token!);
- });
- }
-
- /**
* Manipulates a mapping such that, in the limit, each Dash user has
* an associated authenticated OAuth2 client at their disposal. This
* function ensures that the client's credentials always remain up to date
@@ -217,18 +197,6 @@ export namespace GoogleApiServerUtils {
}
/**
- * This is what we return to the server in processNewUser(), after the
- * worker OAuth2Client has used the user-pasted authentication code
- * to retrieve an access token and an info token. The avatar is the
- * URL to the Google-hosted mono-color, single white letter profile 'image'.
- */
- export interface GoogleAuthenticationResult {
- access_token: string;
- avatar: string;
- name: string;
- }
-
- /**
* This method receives the authentication code that the
* user pasted into the overlay in the client side and uses the worker
* and the authentication code to fetch the full set of credentials that
@@ -245,7 +213,7 @@ export namespace GoogleApiServerUtils {
* and display basic user information in the overlay on successful authentication.
* This can be expanded as needed by adding properties to the interface GoogleAuthenticationResult.
*/
- export async function processNewUser(userId: string, authenticationCode: string): Promise<GoogleAuthenticationResult> {
+ export async function processNewUser(userId: string, authenticationCode: string): Promise<EnrichedCredentials> {
const credentials = await new Promise<Credentials>((resolve, reject) => {
worker.getToken(authenticationCode, async (err, credentials) => {
if (err || !credentials) {
@@ -257,12 +225,7 @@ export namespace GoogleApiServerUtils {
});
const enriched = injectUserInfo(credentials);
await Database.Auxiliary.GoogleAuthenticationToken.Write(userId, enriched);
- const { given_name, picture } = enriched.userInfo;
- return {
- access_token: enriched.access_token!,
- avatar: picture,
- name: given_name
- };
+ return enriched;
}
/**
@@ -316,15 +279,15 @@ export namespace GoogleApiServerUtils {
* @returns the credentials, or undefined if the user has no stored associated credentials,
* and a flag indicating whether or not they were refreshed during retrieval
*/
- async function retrieveCredentials(userId: string): Promise<{ credentials: Opt<Credentials>, refreshed: boolean }> {
- let credentials: Opt<Credentials> = await Database.Auxiliary.GoogleAuthenticationToken.Fetch(userId);
+ export async function retrieveCredentials(userId: string): Promise<{ credentials: Opt<EnrichedCredentials>, refreshed: boolean }> {
+ let credentials = await Database.Auxiliary.GoogleAuthenticationToken.Fetch(userId);
let refreshed = false;
if (!credentials) {
return { credentials: undefined, refreshed };
}
// check for token expiry
if (credentials.expiry_date! <= new Date().getTime()) {
- credentials = await refreshAccessToken(credentials, userId);
+ credentials = { ...credentials, ...(await refreshAccessToken(credentials, userId)) };
refreshed = true;
}
return { credentials, refreshed };
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index ba5ada6c4..782582930 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -1,5 +1,6 @@
import { action, computed, observable, reaction } from "mobx";
import * as rp from 'request-promise';
+import { Utils } from "../../../Utils";
import { DocServer } from "../../../client/DocServer";
import { Docs, DocumentOptions } from "../../../client/documents/Documents";
import { UndoManager } from "../../../client/util/UndoManager";
@@ -7,8 +8,7 @@ import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc";
import { List } from "../../../new_fields/List";
import { listSpec } from "../../../new_fields/Schema";
import { ScriptField, ComputedField } from "../../../new_fields/ScriptField";
-import { Cast, PromiseValue, StrCast } from "../../../new_fields/Types";
-import { Utils } from "../../../Utils";
+import { Cast, PromiseValue, StrCast, NumCast } from "../../../new_fields/Types";
import { nullAudio, ImageField } from "../../../new_fields/URLField";
import { DragManager } from "../../../client/util/DragManager";
import { InkingControl } from "../../../client/views/InkingControl";
@@ -21,6 +21,7 @@ import { FormattedTextBox } from "../../../client/views/nodes/formattedText/Form
import { MainView } from "../../../client/views/MainView";
import { DocumentType } from "../../../client/documents/DocumentTypes";
import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
+import { DimUnit } from "../../../client/views/collections/collectionMulticolumn/CollectionMulticolumnView";
export class CurrentUserUtils {
private static curr_id: string;
@@ -72,24 +73,62 @@ export class CurrentUserUtils {
}
if (doc["template-button-description"] === undefined) {
- const descriptionTemplate = Docs.Create.TextDocument("", { title: "header", _height: 100 });
- Doc.GetProto(descriptionTemplate).layout = "<div><FormattedTextBox {...props} background='orange' height='50px' fieldKey={'header'}/><FormattedTextBox {...props} height='calc(100% - 50px)' fieldKey={'text'}/></div>";
+ const descriptionTemplate = Docs.Create.TextDocument(" ", { title: "header", _height: 100 }, "header"); // text needs to be a space to allow templateText to be created
+ Doc.GetProto(descriptionTemplate).layout =
+ "<div><FormattedTextBox {...props} height='{this._headerHeight||75}px' background='{this._headerColor||`orange`}' fieldKey={'header'}/>" +
+ "<FormattedTextBox {...props} height='calc(100% - {this._headerHeight||75}px)' fieldKey={'text'}/></div>";
descriptionTemplate.isTemplateDoc = makeTemplate(descriptionTemplate, true, "descriptionView");
doc["template-button-description"] = CurrentUserUtils.ficon({
- onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'),
+ onDragStart: ScriptField.MakeFunction('makeDelegate(this.dragFactory)'),
dragFactory: new PrefetchProxy(descriptionTemplate) as any as Doc,
removeDropProperties: new List<string>(["dropAction"]), title: "description view", icon: "window-maximize"
});
}
+ if (doc["template-button-switch"] === undefined) {
+ const { FreeformDocument, MulticolumnDocument, TextDocument } = Docs.Create;
+
+ const yes = FreeformDocument([], { title: "yes", _height: 35, _width: 50, _LODdisable: true, _dimUnit: DimUnit.Pixel, _dimMagnitude: 40 });
+ const name = TextDocument("name", { title: "name", _height: 35, _width: 70, _dimMagnitude: 1 });
+ const no = FreeformDocument([], { title: "no", _height: 100, _width: 100, _LODdisable: true });
+ const labelTemplate = {
+ doc: {
+ type: "doc", content: [{
+ type: "paragraph",
+ content: [{ type: "dashField", attrs: { fieldKey: "PARAMS", hideKey: true } }]
+ }]
+ },
+ selection: { type: "text", anchor: 1, head: 1 },
+ storedMarks: []
+ };
+ Doc.GetProto(name).text = new RichTextField(JSON.stringify(labelTemplate), "PARAMS");
+ Doc.GetProto(yes).backgroundColor = ComputedField.MakeFunction("self[this.PARAMS] ? 'green':'red'");
+ // Doc.GetProto(no).backgroundColor = ComputedField.MakeFunction("!self[this.PARAMS] ? 'red':'white'");
+ // Doc.GetProto(yes).onClick = ScriptField.MakeScript("self[this.PARAMS] = true");
+ Doc.GetProto(yes).onClick = ScriptField.MakeScript("self[this.PARAMS] = !self[this.PARAMS]");
+ // Doc.GetProto(no).onClick = ScriptField.MakeScript("self[this.PARAMS] = false");
+ const box = MulticolumnDocument([/*no, */ yes, name], { title: "value", _width: 120, _height: 35, });
+ box.isTemplateDoc = makeTemplate(box, true, "switch");
+
+ doc["template-button-switch"] = CurrentUserUtils.ficon({
+ onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'),
+ dragFactory: new PrefetchProxy(box) as any as Doc,
+ removeDropProperties: new List<string>(["dropAction"]), title: "data switch", icon: "toggle-on"
+ });
+ }
+
if (doc["template-button-detail"] === undefined) {
const { TextDocument, MasonryDocument, CarouselDocument } = Docs.Create;
- const carousel = CarouselDocument([], { title: "data", _height: 350, _itemIndex: 0, backgroundColor: "#9b9b9b3F" });
+ const openInTarget = ScriptField.MakeScript("openOnRight(self.doubleClickView)");
+ const carousel = CarouselDocument([], {
+ title: "data", _height: 350, _itemIndex: 0, "_carousel-caption-xMargin": 10, "_carousel-caption-yMargin": 10,
+ onChildDoubleClick: openInTarget, backgroundColor: "#9b9b9b3F"
+ });
const details = TextDocument("", { title: "details", _height: 350, _autoHeight: true });
- const short = TextDocument("", { title: "shortDescription", treeViewOpen: true, treeViewExpandedView: "layout", _height: 50, _autoHeight: true });
+ const short = TextDocument("", { title: "shortDescription", treeViewOpen: true, treeViewExpandedView: "layout", _height: 100, _autoHeight: true });
const long = TextDocument("", { title: "longDescription", treeViewOpen: false, treeViewExpandedView: "layout", _height: 350, _autoHeight: true });
const buxtonFieldKeys = ["year", "originalPrice", "degreesOfFreedom", "company", "attribute", "primaryKey", "secondaryKey", "dimensions"];
@@ -111,8 +150,9 @@ export class CurrentUserUtils {
const descriptionWrapper = MasonryDocument([details, short, long], { ...shared, ...descriptionWrapperOpts });
descriptionWrapper.sectionHeaders = new List<SchemaHeaderField>([
- new SchemaHeaderField("[Long Description]", "LemonChiffon", undefined, undefined, undefined, true),
- new SchemaHeaderField("[Details]", "lightBlue", undefined, undefined, undefined, true),
+ new SchemaHeaderField("[A Short Description]", "dimGray", undefined, undefined, undefined, false),
+ new SchemaHeaderField("[Long Description]", "dimGray", undefined, undefined, undefined, true),
+ new SchemaHeaderField("[Details]", "dimGray", undefined, undefined, undefined, true),
]);
const detailView = Docs.Create.StackingDocument([carousel, descriptionWrapper], { ...shared, ...detailViewOpts });
detailView.isTemplateDoc = makeTemplate(detailView);
@@ -130,13 +170,19 @@ export class CurrentUserUtils {
if (doc["template-buttons"] === undefined) {
doc["template-buttons"] = new PrefetchProxy(Docs.Create.MasonryDocument([doc["template-button-slides"] as Doc, doc["template-button-description"] as Doc,
- doc["template-button-query"] as Doc, doc["template-button-detail"] as Doc], {
- title: "Compound Item Creators", _xMargin: 0, _showTitle: "title",
+ doc["template-button-query"] as Doc, doc["template-button-detail"] as Doc, doc["template-button-switch"] as Doc], {
+ title: "Advanced Item Prototypes", _xMargin: 0, _showTitle: "title",
_autoHeight: true, _width: 500, columnWidth: 35, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled",
dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }),
}));
} else {
- DocListCast(Cast(doc["template-buttons"], Doc, null)?.data); // prefetch templates
+ const curButnTypes = Cast(doc["template-buttons"], Doc, null);
+ const requiredTypes = [doc["template-button-slides"] as Doc, doc["template-button-description"] as Doc,
+ doc["template-button-query"] as Doc, doc["template-button-detail"] as Doc, doc["template-button-switch"] as Doc];
+ DocListCastAsync(curButnTypes.data).then(async curBtns => {
+ await Promise.all(curBtns!);
+ requiredTypes.map(btype => Doc.AddDocToList(curButnTypes, "data", btype));
+ });
}
return doc["template-buttons"] as Doc;
}
@@ -210,28 +256,29 @@ export class CurrentUserUtils {
static setupDefaultIconTemplates(doc: Doc) {
if (doc["template-icon-view"] === undefined) {
const iconView = Docs.Create.TextDocument("", {
- title: "icon", _width: 150, _height: 30, isTemplateDoc: true,
- onClick: ScriptField.MakeScript("deiconifyView(self)")
+ title: "icon", _width: 150, _height: 30, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)")
});
Doc.GetProto(iconView).icon = new RichTextField('{"doc":{"type":"doc","content":[{"type":"paragraph","attrs":{"align":null,"color":null,"id":null,"indent":null,"inset":null,"lineSpacing":null,"paddingBottom":null,"paddingTop":null},"content":[{"type":"dashField","attrs":{"fieldKey":"title","docid":""}}]}]},"selection":{"type":"text","anchor":2,"head":2},"storedMarks":[]}', "");
iconView.isTemplateDoc = makeTemplate(iconView);
doc["template-icon-view"] = new PrefetchProxy(iconView);
}
if (doc["template-icon-view-rtf"] === undefined) {
- const iconRtfView = Docs.Create.LabelDocument({ title: "icon_" + DocumentType.RTF, textTransform: "unset", letterSpacing: "unset", _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onClick: ScriptField.MakeScript("deiconifyView(self)") });
+ const iconRtfView = Docs.Create.LabelDocument({
+ title: "icon_" + DocumentType.RTF, textTransform: "unset", letterSpacing: "unset",
+ _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)")
+ });
iconRtfView.isTemplateDoc = makeTemplate(iconRtfView, true, "icon_" + DocumentType.RTF);
doc["template-icon-view-rtf"] = new PrefetchProxy(iconRtfView);
}
if (doc["template-icon-view-img"] === undefined) {
const iconImageView = Docs.Create.ImageDocument("http://www.cs.brown.edu/~bcz/face.gif", {
- title: "data", _width: 50, isTemplateDoc: true,
- onClick: ScriptField.MakeScript("deiconifyView(self)")
+ title: "data", _width: 50, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)")
});
iconImageView.isTemplateDoc = makeTemplate(iconImageView, true, "icon_" + DocumentType.IMG);
doc["template-icon-view-img"] = new PrefetchProxy(iconImageView);
}
if (doc["template-icon-view-col"] === undefined) {
- const iconColView = Docs.Create.TreeDocument([], { title: "data", _width: 180, _height: 80, onClick: ScriptField.MakeScript("deiconifyView(self)") });
+ const iconColView = Docs.Create.TreeDocument([], { title: "data", _width: 180, _height: 80, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)") });
iconColView.isTemplateDoc = makeTemplate(iconColView, true, "icon_" + DocumentType.COL);
doc["template-icon-view-col"] = new PrefetchProxy(iconColView);
}
@@ -240,8 +287,12 @@ export class CurrentUserUtils {
doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc], { title: "icon templates", _height: 75 }));
} else {
const templateIconsDoc = Cast(doc["template-icons"], Doc, null);
- DocListCastAsync(templateIconsDoc).then(list => templateIconsDoc.data = new List<Doc>([doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc,
- doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc]));
+ const requiredTypes = [doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc,
+ doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc];
+ DocListCastAsync(templateIconsDoc.data).then(async curIcons => {
+ await Promise.all(curIcons!);
+ requiredTypes.map(ntype => Doc.AddDocToList(templateIconsDoc, "data", ntype));
+ });
}
return doc["template-icons"] as Doc;
}
@@ -252,15 +303,23 @@ export class CurrentUserUtils {
}[] {
if (doc.emptyPresentation === undefined) {
doc.emptyPresentation = Docs.Create.PresDocument(new List<Doc>(),
- { title: "Presentation", _viewType: CollectionViewType.Stacking, _LODdisable: true, _chromeStatus: "replaced", _showTitle: "title", boxShadow: "0 0" });
+ { title: "Presentation", _viewType: CollectionViewType.Stacking, targetDropAction: "alias", _LODdisable: true, _chromeStatus: "replaced", _showTitle: "title", boxShadow: "0 0" });
}
if (doc.emptyCollection === undefined) {
doc.emptyCollection = Docs.Create.FreeformDocument([],
{ _nativeWidth: undefined, _nativeHeight: undefined, _LODdisable: true, _width: 150, _height: 100, title: "freeform" });
}
+ if (doc.emptyDocHolder === undefined) {
+ doc.emptyDocHolder = Docs.Create.DocumentDocument(
+ ComputedField.MakeFunction("selectedDocs(this,this.excludeCollections,[_last_])?.[0]") as any,
+ { _width: 250, _height: 250, title: "container" });
+ }
+ if (doc.emptyWebpage === undefined) {
+ doc.emptyWebpage = Docs.Create.WebDocument("", { title: "New Webpage", _width: 600, UseCors: true });
+ }
return [
{ title: "Drag a collection", label: "Col", icon: "folder", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyCollection as Doc },
- { title: "Drag a web page", label: "Web", icon: "globe-asia", ignoreClick: true, drag: 'Docs.Create.WebDocument("", { title: "New Webpage" })' },
+ { title: "Drag a web page", label: "Web", icon: "globe-asia", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyWebpage as Doc },
{ title: "Drag a cat image", label: "Img", icon: "cat", ignoreClick: true, drag: 'Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { _width: 250, _nativeWidth:250, title: "an image of a cat" })' },
{ title: "Drag a screenshot", label: "Grab", icon: "photo-video", ignoreClick: true, drag: 'Docs.Create.ScreenshotDocument("", { _width: 400, _height: 200, title: "screen snapshot" })' },
{ title: "Drag a webcam", label: "Cam", icon: "video", ignoreClick: true, drag: 'Docs.Create.WebCamDocument("", { _width: 400, _height: 400, title: "a test cam" })' },
@@ -277,10 +336,10 @@ export class CurrentUserUtils {
// { title: "use stamp", icon: "stamp", click: 'activateStamp(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this)', backgroundColor: "orange", ischecked: `sameDocs(this.activePen.inkPen, this)`, activePen: doc },
// { title: "use eraser", icon: "eraser", click: 'activateEraser(this.activePen.inkPen = sameDocs(this.activePen.inkPen, this) ? undefined : this);', ischecked: `sameDocs(this.activePen.inkPen, this)`, backgroundColor: "pink", activePen: doc },
// { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activePen.inkPen = this;', ischecked: `sameDocs(this.activePen.inkPen, this)`, backgroundColor: "white", activePen: doc },
- { title: "Drag a document previewer", label: "Prev", icon: "expand", ignoreClick: true, drag: 'Docs.Create.DocumentDocument(ComputedField.MakeFunction("selectedDocs(this,this.excludeCollections,[_last_])?.[0]"), { _width: 250, _height: 250, title: "container" })' },
- { title: "Drag a Calculator REPL", label: "repl", icon: "calculator", click: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' },
+ { title: "Drag a document previewer", label: "Prev", icon: "expand", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory,true)', dragFactory: doc.emptyDocHolder as Doc },
+ { title: "Toggle a Calculator REPL", label: "repl", icon: "calculator", click: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' },
+ { title: "Connect a Google Account", label: "Google Account", icon: "external-link-alt", click: 'GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(true)' },
{ title: "query", icon: "bolt", label: "Col", ignoreClick: true, drag: 'Docs.Create.SearchDocument({ _width: 200, title: "an image of a cat" })' },
-
];
}
@@ -296,21 +355,22 @@ export class CurrentUserUtils {
alreadyCreatedButtons = dragDocs.map(d => StrCast(d.title));
}
}
- const creatorBtns = CurrentUserUtils.creatorBtnDescriptors(doc).filter(d => !alreadyCreatedButtons?.includes(d.title)).
- map(data => Docs.Create.FontIconDocument({
- _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100,
- icon: data.icon,
- title: data.title,
- label: data.label,
- ignoreClick: data.ignoreClick,
- dropAction: data.click ? "copy" : undefined,
- onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined,
- onClick: data.click ? ScriptField.MakeScript(data.click) : undefined,
- ischecked: data.ischecked ? ComputedField.MakeFunction(data.ischecked) : undefined,
- activePen: data.activePen,
- backgroundColor: data.backgroundColor, removeDropProperties: new List<string>(["dropAction"]),
- dragFactory: data.dragFactory,
- }));
+ const buttons = CurrentUserUtils.creatorBtnDescriptors(doc).filter(d => !alreadyCreatedButtons?.includes(d.title));
+ const creatorBtns = buttons.map(({ title, label, icon, ignoreClick, drag, click, ischecked, activePen, backgroundColor, dragFactory }) => Docs.Create.FontIconDocument({
+ _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100,
+ icon,
+ title,
+ label,
+ ignoreClick,
+ dropAction: "copy",
+ onDragStart: drag ? ScriptField.MakeFunction(drag) : undefined,
+ onClick: click ? ScriptField.MakeScript(click) : undefined,
+ ischecked: ischecked ? ComputedField.MakeFunction(ischecked) : undefined,
+ activePen,
+ backgroundColor,
+ removeDropProperties: new List<string>(["dropAction"]),
+ dragFactory,
+ }));
if (dragCreatorSet === undefined) {
doc.myItemCreators = new PrefetchProxy(Docs.Create.MasonryDocument(creatorBtns, {
@@ -448,13 +508,13 @@ export class CurrentUserUtils {
return doc.myWorkspaces as Doc;
}
- static setupDocumentCollection(doc: Doc) {
- if (doc.myDocuments === undefined) {
- doc.myDocuments = new PrefetchProxy(Docs.Create.TreeDocument([], {
- title: "DOCUMENTS", _height: 42, forceActive: true, boxShadow: "0 0", treeViewPreventOpen: true, lockedPosition: true,
+ static setupCatalog(doc: Doc) {
+ if (doc.myCatalog === undefined) {
+ doc.myCatalog = new PrefetchProxy(Docs.Create.TreeDocument([], {
+ title: "CATALOG", _height: 42, forceActive: true, boxShadow: "0 0", treeViewPreventOpen: false, lockedPosition: true,
}));
}
- return doc.myDocuments as Doc;
+ return doc.myCatalog as Doc;
}
static setupRecentlyClosed(doc: Doc) {
// setup Recently Closed library item
@@ -474,7 +534,7 @@ export class CurrentUserUtils {
// setup the Library button which will display the library panel. This panel includes a collection of workspaces, documents, and recently closed views
static setupLibraryPanel(doc: Doc, sidebarContainer: Doc) {
const workspaces = CurrentUserUtils.setupWorkspaces(doc);
- const documents = CurrentUserUtils.setupDocumentCollection(doc);
+ const documents = CurrentUserUtils.setupCatalog(doc);
const recentlyClosed = CurrentUserUtils.setupRecentlyClosed(doc);
if (doc["tabs-button-library"] === undefined) {
@@ -482,7 +542,7 @@ export class CurrentUserUtils {
_width: 50, _height: 25, title: "Library", _fontSize: 10,
letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)",
sourcePanel: new PrefetchProxy(Docs.Create.TreeDocument([workspaces, documents, recentlyClosed, doc], {
- title: "Library", _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "move", lockedPosition: true, boxShadow: "0 0", dontRegisterChildren: true
+ title: "Library", _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "alias", lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true
})) as any as Doc,
targetContainer: new PrefetchProxy(sidebarContainer) as any as Doc,
onClick: ScriptField.MakeScript("this.targetContainer.proto = this.sourcePanel;")
@@ -581,7 +641,7 @@ export class CurrentUserUtils {
}
if (doc.activePresentation === undefined) {
doc.activePresentation = Docs.Create.PresDocument(new List<Doc>(), {
- title: "Presentation", _viewType: CollectionViewType.Stacking,
+ title: "Presentation", _viewType: CollectionViewType.Stacking, targetDropAction: "alias",
_LODdisable: true, _chromeStatus: "replaced", _showTitle: "title", boxShadow: "0 0"
});
}
@@ -594,25 +654,37 @@ export class CurrentUserUtils {
}
static setupClickEditorTemplates(doc: Doc) {
- if (doc.childClickFuncs === undefined) {
+ if (doc["clickFuncs-child"] === undefined) {
const openInTarget = Docs.Create.ScriptingDocument(ScriptField.MakeScript(
- "docCast(thisContainer.target).then((target) => { target && docCast(this.source).then((source) => { target.proto.data = new List([source || this]); } ); } )",
- { target: Doc.name }), { title: "On Child Clicked (open in target)", _width: 300, _height: 200 });
+ "docCast(thisContainer.target).then((target) => {" +
+ " target && docCast(this.source).then((source) => { " +
+ " target.proto.data = new List([source || this]); " +
+ " }); " +
+ "})",
+ { target: Doc.name }), { title: "Click to open in target", _width: 300, _height: 200, targetScriptKey: "onChildClick" });
+
+ const openDetail = Docs.Create.ScriptingDocument(ScriptField.MakeScript(
+ "openOnRight(self.doubleClickView)",
+ { target: Doc.name }), { title: "Double click to open doubleClickView", _width: 300, _height: 200, targetScriptKey: "onChildDoubleClick" });
- doc.childClickFuncs = Docs.Create.TreeDocument([openInTarget], { title: "on Child Click function templates" });
+ doc["clickFuncs-child"] = Docs.Create.TreeDocument([openInTarget, openDetail], { title: "on Child Click function templates" });
}
// this is equivalent to using PrefetchProxies to make sure all the childClickFuncs have been retrieved.
- PromiseValue(Cast(doc.childClickFuncs, Doc)).then(func => func && PromiseValue(func.data).then(DocListCast));
+ PromiseValue(Cast(doc["clickFuncs-child"], Doc)).then(func => func && PromiseValue(func.data).then(DocListCast));
if (doc.clickFuncs === undefined) {
const onClick = Docs.Create.ScriptingDocument(undefined, {
title: "onClick", "onClick-rawScript": "console.log('click')",
isTemplateDoc: true, isTemplateForField: "onClick", _width: 300, _height: 200
}, "onClick");
+ const onDoubleClick = Docs.Create.ScriptingDocument(undefined, {
+ title: "onDoubleClick", "onDoubleClick-rawScript": "console.log('double click')",
+ isTemplateDoc: true, isTemplateForField: "onDoubleClick", _width: 300, _height: 200
+ }, "onDoubleClick");
const onCheckedClick = Docs.Create.ScriptingDocument(undefined, {
title: "onCheckedClick", "onCheckedClick-rawScript": "console.log(heading + checked + containingTreeView)", "onCheckedClick-params": new List<string>(["heading", "checked", "containingTreeView"]), isTemplateDoc: true, isTemplateForField: "onCheckedClick", _width: 300, _height: 200
}, "onCheckedClick");
- doc.clickFuncs = Docs.Create.TreeDocument([onClick, onCheckedClick], { title: "onClick funcs" });
+ doc.clickFuncs = Docs.Create.TreeDocument([onClick, onDoubleClick, onCheckedClick], { title: "onClick funcs" });
}
PromiseValue(Cast(doc.clickFuncs, Doc)).then(func => func && PromiseValue(func.data).then(DocListCast));
@@ -623,6 +695,11 @@ export class CurrentUserUtils {
new InkingControl();
doc.title = Doc.CurrentUserEmail;
doc.activePen = doc;
+ doc.inkColor = StrCast(doc.backgroundColor, "");
+ doc.fontSize = NumCast(doc.fontSize, 12);
+ doc["constants-snapThreshold"] = NumCast(doc["constants-snapThreshold"], 10); //
+ doc["constants-dragThreshold"] = NumCast(doc["constants-dragThreshold"], 4); //
+ Utils.DRAG_THRESHOLD = NumCast(doc["constants-dragThreshold"]);
this.setupDefaultIconTemplates(doc); // creates a set of icon templates triggered by the document deoration icon
this.setupDocTemplates(doc); // sets up the template menu of templates
this.setupRightSidebar(doc); // sets up the right sidebar collection for mobile upload documents and sharing
diff --git a/src/server/authentication/models/user_model.ts b/src/server/authentication/models/user_model.ts
index 78e39dbc1..a0b688328 100644
--- a/src/server/authentication/models/user_model.ts
+++ b/src/server/authentication/models/user_model.ts
@@ -74,9 +74,9 @@ userSchema.pre("save", function save(next) {
const comparePassword: comparePasswordFunction = function (this: DashUserModel, candidatePassword, cb) {
// Choose one of the following bodies for authentication logic.
- // secure
+ // secure (expected, default)
bcrypt.compare(candidatePassword, this.password, cb);
- // bypass password
+ // bypass password (debugging)
// cb(undefined, true);
};
diff --git a/src/server/database.ts b/src/server/database.ts
index ad285765b..580f7f919 100644
--- a/src/server/database.ts
+++ b/src/server/database.ts
@@ -4,7 +4,7 @@ import { Opt } from '../new_fields/Doc';
import { Utils, emptyFunction } from '../Utils';
import { Credentials } from 'google-auth-library';
import { GoogleApiServerUtils } from './apis/google/GoogleApiServerUtils';
-import { IDatabase } from './IDatabase';
+import { IDatabase, DocumentsCollection } from './IDatabase';
import { MemoryDatabase } from './MemoryDatabase';
import * as mongoose from 'mongoose';
import { Upload } from './SharedMediaTypes';
@@ -14,7 +14,7 @@ export namespace Database {
export let disconnect: Function;
const schema = 'Dash';
const port = 27017;
- export const url = `mongodb://localhost:${port}/`;
+ export const url = `mongodb://localhost:${port}/${schema}`;
enum ConnectionStates {
disconnected = 0,
@@ -47,28 +47,29 @@ export namespace Database {
}
export class Database implements IDatabase {
- public static DocumentsCollection = 'documents';
private MongoClient = mongodb.MongoClient;
private currentWrites: { [id: string]: Promise<void> } = {};
private db?: mongodb.Db;
private onConnect: (() => void)[] = [];
- doConnect() {
+ async doConnect() {
console.error(`\nConnecting to Mongo with URL : ${url}\n`);
- this.MongoClient.connect(url, { connectTimeoutMS: 30000, socketTimeoutMS: 30000, useUnifiedTopology: true }, (_err, client) => {
- console.error("mongo connect response\n");
- if (!client) {
- console.error("\nMongo connect failed with the error:\n");
- console.log(_err);
- process.exit(0);
- }
- this.db = client.db();
- this.onConnect.forEach(fn => fn());
+ return new Promise<void>(resolve => {
+ this.MongoClient.connect(url, { connectTimeoutMS: 30000, socketTimeoutMS: 30000, useUnifiedTopology: true }, (_err, client) => {
+ console.error("mongo connect response\n");
+ if (!client) {
+ console.error("\nMongo connect failed with the error:\n");
+ console.log(_err);
+ process.exit(0);
+ }
+ this.db = client.db();
+ this.onConnect.forEach(fn => fn());
+ resolve();
+ });
});
}
- public async update(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateWriteOpResult) => void, upsert = true, collectionName = Database.DocumentsCollection) {
-
+ public async update(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateWriteOpResult) => void, upsert = true, collectionName = DocumentsCollection) {
if (this.db) {
const collection = this.db.collection(collectionName);
const prom = this.currentWrites[id];
@@ -93,7 +94,7 @@ export namespace Database {
}
}
- public replace(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateWriteOpResult) => void, upsert = true, collectionName = Database.DocumentsCollection) {
+ public replace(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateWriteOpResult) => void, upsert = true, collectionName = DocumentsCollection) {
if (this.db) {
const collection = this.db.collection(collectionName);
const prom = this.currentWrites[id];
@@ -117,9 +118,21 @@ export namespace Database {
}
}
+ public async getCollectionNames() {
+ const cursor = this.db?.listCollections();
+ const collectionNames: string[] = [];
+ if (cursor) {
+ while (await cursor.hasNext()) {
+ const collection: any = await cursor.next();
+ collection && collectionNames.push(collection.name);
+ }
+ }
+ return collectionNames;
+ }
+
public delete(query: any, collectionName?: string): Promise<mongodb.DeleteWriteOpResultObject>;
public delete(id: string, collectionName?: string): Promise<mongodb.DeleteWriteOpResultObject>;
- public delete(id: any, collectionName = Database.DocumentsCollection) {
+ public delete(id: any, collectionName = DocumentsCollection) {
if (typeof id === "string") {
id = { _id: id };
}
@@ -131,25 +144,26 @@ export namespace Database {
}
}
- public async deleteAll(collectionName = Database.DocumentsCollection, persist = true): Promise<any> {
- return new Promise(resolve => {
- const executor = async (database: mongodb.Db) => {
- if (persist) {
- await database.collection(collectionName).deleteMany({});
- } else {
- await database.dropCollection(collectionName);
- }
- resolve();
- };
- if (this.db) {
- executor(this.db);
+ public async dropSchema(...targetSchemas: string[]): Promise<any> {
+ const executor = async (database: mongodb.Db) => {
+ const existing = await Instance.getCollectionNames();
+ let valid: string[];
+ if (targetSchemas.length) {
+ valid = targetSchemas.filter(collection => existing.includes(collection));
} else {
- this.onConnect.push(() => this.db && executor(this.db));
+ valid = existing;
}
- });
+ const pending = Promise.all(valid.map(schemaName => database.dropCollection(schemaName)));
+ return (await pending).every(dropOutcome => dropOutcome);
+ };
+ if (this.db) {
+ return executor(this.db);
+ } else {
+ this.onConnect.push(() => this.db && executor(this.db));
+ }
}
- public async insert(value: any, collectionName = Database.DocumentsCollection) {
+ public async insert(value: any, collectionName = DocumentsCollection) {
if (this.db) {
if ("id" in value) {
value._id = value.id;
@@ -177,7 +191,7 @@ export namespace Database {
}
}
- public getDocument(id: string, fn: (result?: Transferable) => void, collectionName = "newDocuments") {
+ public getDocument(id: string, fn: (result?: Transferable) => void, collectionName = DocumentsCollection) {
if (this.db) {
this.db.collection(collectionName).findOne({ _id: id }, (err, result) => {
if (result) {
@@ -193,7 +207,7 @@ export namespace Database {
}
}
- public getDocuments(ids: string[], fn: (result: Transferable[]) => void, collectionName = Database.DocumentsCollection) {
+ public getDocuments(ids: string[], fn: (result: Transferable[]) => void, collectionName = DocumentsCollection) {
if (this.db) {
this.db.collection(collectionName).find({ _id: { "$in": ids } }).toArray((err, docs) => {
if (err) {
@@ -211,7 +225,7 @@ export namespace Database {
}
}
- public async visit(ids: string[], fn: (result: any) => string[] | Promise<string[]>, collectionName = "newDocuments"): Promise<void> {
+ public async visit(ids: string[], fn: (result: any) => string[] | Promise<string[]>, collectionName = DocumentsCollection): Promise<void> {
if (this.db) {
const visited = new Set<string>();
while (ids.length) {
@@ -238,7 +252,7 @@ export namespace Database {
}
}
- public query(query: { [key: string]: any }, projection?: { [key: string]: 0 | 1 }, collectionName = "newDocuments"): Promise<mongodb.Cursor> {
+ public query(query: { [key: string]: any }, projection?: { [key: string]: 0 | 1 }, collectionName = DocumentsCollection): Promise<mongodb.Cursor> {
if (this.db) {
let cursor = this.db.collection(collectionName).find(query);
if (projection) {
@@ -252,7 +266,7 @@ export namespace Database {
}
}
- public updateMany(query: any, update: any, collectionName = "newDocuments") {
+ public updateMany(query: any, update: any, collectionName = DocumentsCollection) {
if (this.db) {
const db = this.db;
return new Promise<mongodb.WriteOpResult>(res => db.collection(collectionName).update(query, update, (_, result) => res(result)));
@@ -277,12 +291,13 @@ export namespace Database {
}
}
- export const Instance: IDatabase = getDatabase();
+ export const Instance = getDatabase();
export namespace Auxiliary {
export enum AuxiliaryCollections {
- GooglePhotosUploadHistory = "uploadedFromGooglePhotos"
+ GooglePhotosUploadHistory = "uploadedFromGooglePhotos",
+ GoogleAuthentication = "googleAuthentication"
}
const SanitizedCappedQuery = async (query: { [key: string]: any }, collection: string, cap: number, removeId = true) => {
@@ -306,27 +321,30 @@ export namespace Database {
export namespace GoogleAuthenticationToken {
- const GoogleAuthentication = "googleAuthentication";
-
- export type StoredCredentials = Credentials & { _id: string };
+ type StoredCredentials = GoogleApiServerUtils.EnrichedCredentials & { _id: string };
export const Fetch = async (userId: string, removeId = true): Promise<Opt<StoredCredentials>> => {
- return SanitizedSingletonQuery<StoredCredentials>({ userId }, GoogleAuthentication, removeId);
+ return SanitizedSingletonQuery<StoredCredentials>({ userId }, AuxiliaryCollections.GoogleAuthentication, removeId);
};
export const Write = async (userId: string, enrichedCredentials: GoogleApiServerUtils.EnrichedCredentials) => {
- return Instance.insert({ userId, canAccess: [], ...enrichedCredentials }, GoogleAuthentication);
+ return Instance.insert({ userId, canAccess: [], ...enrichedCredentials }, AuxiliaryCollections.GoogleAuthentication);
};
export const Update = async (userId: string, access_token: string, expiry_date: number) => {
const entry = await Fetch(userId, false);
if (entry) {
const parameters = { $set: { access_token, expiry_date } };
- return Instance.update(entry._id, parameters, emptyFunction, true, GoogleAuthentication);
+ return Instance.update(entry._id, parameters, emptyFunction, true, AuxiliaryCollections.GoogleAuthentication);
}
};
- export const DeleteAll = () => Instance.deleteAll(GoogleAuthentication, false);
+ export const Revoke = async (userId: string) => {
+ const entry = await Fetch(userId, false);
+ if (entry) {
+ Instance.delete({ _id: entry._id }, AuxiliaryCollections.GoogleAuthentication);
+ }
+ };
}
@@ -338,12 +356,6 @@ export namespace Database {
return Instance.insert(bundle, AuxiliaryCollections.GooglePhotosUploadHistory);
};
- export const DeleteAll = async (persist = false) => {
- const collectionNames = Object.values(AuxiliaryCollections);
- const pendingDeletions = collectionNames.map(name => Instance.deleteAll(name, persist));
- return Promise.all(pendingDeletions);
- };
-
}
}
diff --git a/src/server/remapUrl.ts b/src/server/remapUrl.ts
index 45d2fdd33..91a3cb6bf 100644
--- a/src/server/remapUrl.ts
+++ b/src/server/remapUrl.ts
@@ -1,6 +1,4 @@
import { Database } from "./database";
-import { Search } from "./Search";
-import * as path from 'path';
//npx ts-node src/server/remapUrl.ts
@@ -50,7 +48,7 @@ async function update() {
return new Promise(res => Database.Instance.update(doc[0], doc[1], () => {
console.log("wrote " + JSON.stringify(doc[1]));
res();
- }, false, "newDocuments"));
+ }, false));
}));
console.log("Done");
// await Promise.all(updates.map(update => {