aboutsummaryrefslogtreecommitdiff
path: root/src/server/database.ts
diff options
context:
space:
mode:
authoranika-ahluwalia <anika.ahluwalia@gmail.com>2020-05-15 13:26:37 -0500
committeranika-ahluwalia <anika.ahluwalia@gmail.com>2020-05-15 13:26:37 -0500
commit4bca98333ada6536a1bf2ecf1681c5c17a3a1ae1 (patch)
tree7fc20099971de42756af3d238e2ea4f9a608cbd3 /src/server/database.ts
parent0f54ef61653213bd1b26300cb7d14e3da71d1eea (diff)
parent98c7540fff67c232c1b04f2130ee624f9a70afbd (diff)
Merge branch 'master' of https://github.com/browngraphicslab/Dash-Web into script_documents
Diffstat (limited to 'src/server/database.ts')
-rw-r--r--src/server/database.ts191
1 files changed, 131 insertions, 60 deletions
diff --git a/src/server/database.ts b/src/server/database.ts
index e9373762c..817712fb0 100644
--- a/src/server/database.ts
+++ b/src/server/database.ts
@@ -1,10 +1,10 @@
import * as mongodb from 'mongodb';
import { Transferable } from './Message';
-import { Opt } from '../new_fields/Doc';
+import { Opt } from '../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';
@@ -15,7 +15,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,
@@ -48,27 +48,34 @@ 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)[] = [];
+<<<<<<< HEAD
doConnect() {
+=======
+ async doConnect() {
+>>>>>>> 98c7540fff67c232c1b04f2130ee624f9a70afbd
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();
+ });
});
}
+<<<<<<< HEAD
@@ -77,6 +84,9 @@ export namespace Database {
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) {
+>>>>>>> 98c7540fff67c232c1b04f2130ee624f9a70afbd
if (this.db) {
const collection = this.db.collection(collectionName);
const prom = this.currentWrites[id];
@@ -101,7 +111,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];
@@ -125,9 +135,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 };
}
@@ -139,25 +161,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;
@@ -185,7 +208,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) {
@@ -201,7 +224,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) {
@@ -219,7 +242,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) {
@@ -246,7 +269,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) {
@@ -260,7 +283,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)));
@@ -285,14 +308,28 @@ export namespace Database {
}
}
- export const Instance: IDatabase = getDatabase();
+ export const Instance = getDatabase();
+ /**
+ * Provides definitions and apis for working with
+ * portions of the database not dedicated to storing documents
+ * or Dash-internal user data.
+ */
export namespace Auxiliary {
+ /**
+ * All the auxiliary MongoDB collections (schemas)
+ */
export enum AuxiliaryCollections {
- GooglePhotosUploadHistory = "uploadedFromGooglePhotos"
+ GooglePhotosUploadHistory = "uploadedFromGooglePhotos",
+ GoogleAccess = "googleAuthentication"
}
+ /**
+ * Searches for the @param query in the specified @param collection,
+ * and returns at most the first @param cap results. If @param removeId is true,
+ * as it is by default, each object will be stripped of its database id.
+ */
const SanitizedCappedQuery = async (query: { [key: string]: any }, collection: string, cap: number, removeId = true) => {
const cursor = await Instance.query(query, undefined, collection);
const results = await cursor.toArray();
@@ -303,54 +340,88 @@ export namespace Database {
}) : slice;
};
+ /**
+ * Searches for the @param query in the specified @param collection,
+ * and returns at most the first result. If @param removeId is true,
+ * as it is by default, each object will be stripped of its database id.
+ * Worth the special case since it converts the Array return type to a single
+ * object of the specified type.
+ */
const SanitizedSingletonQuery = async <T>(query: { [key: string]: any }, collection: string, removeId = true): Promise<Opt<T>> => {
const results = await SanitizedCappedQuery(query, collection, 1, removeId);
return results.length ? results[0] : undefined;
};
+ /**
+ * Checks to see if an image with the given @param contentSize
+ * already exists in the aux database, i.e. has already been downloaded from Google Photos.
+ */
export const QueryUploadHistory = async (contentSize: number) => {
return SanitizedSingletonQuery<Upload.ImageInformation>({ contentSize }, AuxiliaryCollections.GooglePhotosUploadHistory);
};
- export namespace GoogleAuthenticationToken {
-
- const GoogleAuthentication = "googleAuthentication";
-
- export type StoredCredentials = Credentials & { _id: string };
+ /**
+ * Records the uploading of the image with the given @param information,
+ * using the given content size as a seed for the database id.
+ */
+ export const LogUpload = async (information: Upload.ImageInformation) => {
+ const bundle = {
+ _id: Utils.GenerateDeterministicGuid(String(information.contentSize)),
+ ...information
+ };
+ return Instance.insert(bundle, AuxiliaryCollections.GooglePhotosUploadHistory);
+ };
+ /**
+ * Manages the storage, retrieval and updating of the access token that
+ * facilitates interactions with all their APIs for a given account.
+ */
+ export namespace GoogleAccessToken {
+
+ /**
+ * Format stored in database.
+ */
+ type StoredCredentials = GoogleApiServerUtils.EnrichedCredentials & { _id: string };
+
+ /**
+ * Retrieves the credentials associaed with @param userId
+ * and optionally removes their database id according to @param removeId.
+ */
export const Fetch = async (userId: string, removeId = true): Promise<Opt<StoredCredentials>> => {
- return SanitizedSingletonQuery<StoredCredentials>({ userId }, GoogleAuthentication, removeId);
+ return SanitizedSingletonQuery<StoredCredentials>({ userId }, AuxiliaryCollections.GoogleAccess, removeId);
};
+ /**
+ * Writes the @param enrichedCredentials to the database, associated
+ * with @param userId for later retrieval and updating.
+ */
export const Write = async (userId: string, enrichedCredentials: GoogleApiServerUtils.EnrichedCredentials) => {
- return Instance.insert({ userId, canAccess: [], ...enrichedCredentials }, GoogleAuthentication);
+ return Instance.insert({ userId, canAccess: [], ...enrichedCredentials }, AuxiliaryCollections.GoogleAccess);
};
+ /**
+ * Updates the @param access_token and @param expiry_date fields
+ * in the stored credentials associated with @param userId.
+ */
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.GoogleAccess);
}
};
- export const DeleteAll = () => Instance.deleteAll(GoogleAuthentication, false);
-
- }
-
- export const LogUpload = async (information: Upload.ImageInformation) => {
- const bundle = {
- _id: Utils.GenerateDeterministicGuid(String(information.contentSize)),
- ...information
+ /**
+ * Revokes the credentials associated with @param userId.
+ */
+ export const Revoke = async (userId: string) => {
+ const entry = await Fetch(userId, false);
+ if (entry) {
+ Instance.delete({ _id: entry._id }, AuxiliaryCollections.GoogleAccess);
+ }
};
- 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);
- };
+ }
}