aboutsummaryrefslogtreecommitdiff
path: root/src/server
diff options
context:
space:
mode:
Diffstat (limited to 'src/server')
-rw-r--r--src/server/Client.ts12
-rw-r--r--src/server/Message.ts127
-rw-r--r--src/server/RouteStore.ts30
-rw-r--r--src/server/Search.ts49
-rw-r--r--src/server/ServerUtil.ts71
-rw-r--r--src/server/authentication/config/passport.ts13
-rw-r--r--src/server/authentication/controllers/user.ts107
-rw-r--r--src/server/authentication/controllers/user_controller.ts266
-rw-r--r--src/server/authentication/models/current_user_utils.ts137
-rw-r--r--src/server/authentication/models/user_model.ts (renamed from src/server/authentication/models/User.ts)35
-rw-r--r--src/server/database.ts123
-rw-r--r--src/server/index.ts426
-rw-r--r--src/server/public/files/.gitignore1
13 files changed, 922 insertions, 475 deletions
diff --git a/src/server/Client.ts b/src/server/Client.ts
index 6b8841658..e6f953712 100644
--- a/src/server/Client.ts
+++ b/src/server/Client.ts
@@ -1,15 +1,11 @@
import { computed } from "mobx";
export class Client {
- constructor(guid: string) {
- this.guid = guid
- }
+ private _guid: string;
- private guid: string;
-
- @computed
- public get GUID(): string {
- return this.guid
+ constructor(guid: string) {
+ this._guid = guid;
}
+ @computed public get GUID(): string { return this._guid; }
} \ No newline at end of file
diff --git a/src/server/Message.ts b/src/server/Message.ts
index 8a00f6b59..e9a8b0f0c 100644
--- a/src/server/Message.ts
+++ b/src/server/Message.ts
@@ -1,125 +1,48 @@
import { Utils } from "../Utils";
export class Message<T> {
- private name: string;
- private guid: string;
-
- get Name(): string {
- return this.name;
- }
-
- get Message(): string {
- return this.guid
- }
+ private _name: string;
+ private _guid: string;
constructor(name: string) {
- this.name = name;
- this.guid = Utils.GenerateDeterministicGuid(name)
- }
-
- GetValue() {
- return this.Name;
- }
-}
-
-class TestMessageArgs {
- hello: string = "";
-}
-
-export class SetFieldArgs {
- field: string;
- value: any;
-
- constructor(f: string, v: any) {
- this.field = f
- this.value = v
+ this._name = name;
+ this._guid = Utils.GenerateDeterministicGuid(name);
}
-}
-export class GetFieldArgs {
- field: string;
-
- constructor(f: string) {
- this.field = f
- }
+ get Name(): string { return this._name; }
+ get Message(): string { return this._guid; }
}
export enum Types {
- Number, List, Key, Image, Web, Document, Text, RichText, DocumentReference, Html, Video, Audio, Ink, PDF
-}
-
-export class DocumentTransfer implements Transferable {
- readonly type = Types.Document
- _id: string
-
- constructor(readonly obj: { type: Types, data: [string, string][], _id: string }) {
- this._id = obj._id
- }
-}
-
-export class ImageTransfer implements Transferable {
- readonly type = Types.Image
-
- constructor(readonly _id: string) { }
-}
-
-export class KeyTransfer implements Transferable {
- name: string
- readonly _id: string
- readonly type = Types.Key
-
- constructor(i: string, n: string) {
- this.name = n
- this._id = i
- }
-}
-
-export class ListTransfer implements Transferable {
- type = Types.List;
-
- constructor(readonly _id: string) { }
-}
-
-export class NumberTransfer implements Transferable {
- readonly type = Types.Number
-
- constructor(readonly value: number, readonly _id: string) { }
+ Number, List, Key, Image, Web, Document, Text, Icon, RichText, DocumentReference,
+ Html, Video, Audio, Ink, PDF, Tuple, HistogramOp, Boolean, Script, Templates
}
-export class TextTransfer implements Transferable {
- value: string
- readonly _id: string
- readonly type = Types.Text
-
- constructor(t: string, i: string) {
- this.value = t
- this._id = i
- }
+export interface Transferable {
+ readonly id: string;
+ readonly type: Types;
+ readonly data?: any;
}
-export class RichTextTransfer implements Transferable {
- value: string
- readonly _id: string
- readonly type = Types.Text
-
- constructor(t: string, i: string) {
- this.value = t
- this._id = i
- }
+export interface Reference {
+ readonly id: string;
}
-export interface Transferable {
- readonly _id: string
- readonly type: Types
+export interface Diff extends Reference {
+ readonly diff: any;
}
export namespace MessageStore {
export const Foo = new Message<string>("Foo");
export const Bar = new Message<string>("Bar");
- export const AddDocument = new Message<DocumentTransfer>("Add Document");
- export const SetField = new Message<{ _id: string, data: any, type: Types }>("Set Field")
- export const GetField = new Message<string>("Get Field")
- export const GetFields = new Message<string[]>("Get Fields")
+ export const SetField = new Message<Transferable>("Set Field"); // send Transferable (no reply)
+ export const GetField = new Message<string>("Get Field"); // send string 'id' get Transferable back
+ export const GetFields = new Message<string[]>("Get Fields"); // send string[] of 'id' get Transferable[] back
export const GetDocument = new Message<string>("Get Document");
export const DeleteAll = new Message<any>("Delete All");
-} \ No newline at end of file
+
+ export const GetRefField = new Message<string>("Get Ref Field");
+ export const GetRefFields = new Message<string[]>("Get Ref Fields");
+ export const UpdateField = new Message<Diff>("Update Ref Field");
+ export const CreateField = new Message<Reference>("Create Ref Field");
+}
diff --git a/src/server/RouteStore.ts b/src/server/RouteStore.ts
new file mode 100644
index 000000000..fdf5b6a5c
--- /dev/null
+++ b/src/server/RouteStore.ts
@@ -0,0 +1,30 @@
+// PREPEND ALL ROUTES WITH FORWARD SLASHES!
+
+export enum RouteStore {
+ // GENERAL
+ root = "/",
+ home = "/home",
+ corsProxy = "/corsProxy",
+ delete = "/delete",
+ deleteAll = "/deleteAll",
+
+ // UPLOAD AND STATIC FILE SERVING
+ public = "/public",
+ upload = "/upload",
+ images = "/images",
+
+ // USER AND WORKSPACES
+ getCurrUser = "/getCurrentUser",
+ getUserDocumentId = "/getUserDocumentId",
+ updateCursor = "/updateCursor",
+
+ openDocumentWithId = "/doc/:docId",
+
+ // AUTHENTICATION
+ signup = "/signup",
+ login = "/login",
+ logout = "/logout",
+ forgot = "/forgotpassword",
+ reset = "/reset/:token",
+
+} \ No newline at end of file
diff --git a/src/server/Search.ts b/src/server/Search.ts
new file mode 100644
index 000000000..5ca5578a7
--- /dev/null
+++ b/src/server/Search.ts
@@ -0,0 +1,49 @@
+import * as rp from 'request-promise';
+import { Database } from './database';
+import { thisExpression } from 'babel-types';
+
+export class Search {
+ public static Instance = new Search();
+ private url = 'http://localhost:8983/solr/';
+
+ public async updateDocument(document: any) {
+ try {
+ const res = await rp.post(this.url + "dash/update", {
+ headers: { 'content-type': 'application/json' },
+ body: JSON.stringify([document])
+ });
+ return res;
+ } catch (e) {
+ console.warn("Search error: " + e + document);
+ }
+ }
+
+ public async search(query: string) {
+ try {
+ const searchResults = JSON.parse(await rp.get(this.url + "dash/select", {
+ qs: {
+ q: query,
+ fl: "id"
+ }
+ }));
+ const fields = searchResults.response.docs;
+ const ids = fields.map((field: any) => field.id);
+ return ids;
+ } catch {
+ return [];
+ }
+ }
+
+ public async clear() {
+ try {
+ return await rp.post(this.url + "dash/update", {
+ body: {
+ delete: {
+ query: "*:*"
+ }
+ },
+ json: true
+ });
+ } catch { }
+ }
+} \ No newline at end of file
diff --git a/src/server/ServerUtil.ts b/src/server/ServerUtil.ts
deleted file mode 100644
index 5331c9e30..000000000
--- a/src/server/ServerUtil.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-
-import { Field } from './../fields/Field';
-import { TextField } from './../fields/TextField';
-import { NumberField } from './../fields/NumberField';
-import { RichTextField } from './../fields/RichTextField';
-import { Key } from './../fields/Key';
-import { ImageField } from './../fields/ImageField';
-import { ListField } from './../fields/ListField';
-import { Document } from './../fields/Document';
-import { Server } from './../client/Server';
-import { Types } from './Message';
-import { Utils } from '../Utils';
-import { HtmlField } from '../fields/HtmlField';
-import { WebField } from '../fields/WebField';
-import { AudioField } from '../fields/AudioField';
-import { VideoField } from '../fields/VideoField';
-import {InkField} from '../fields/InkField';
-import {PDFField} from '../fields/PDFField';
-
-
-
-
-export class ServerUtils {
- public static FromJson(json: any): Field {
- let obj = json
- let data: any = obj.data
- let id: string = obj._id
- let type: Types = obj.type
-
- if (!(data !== undefined && id && type !== undefined)) {
- console.log("how did you manage to get an object that doesn't have a data or an id?")
- return new TextField("Something to fill the space", Utils.GenerateGuid());
- }
-
- switch (type) {
- case Types.Number:
- return new NumberField(data, id, false)
- case Types.Text:
- return new TextField(data, id, false)
- case Types.Html:
- return new HtmlField(data, id, false)
- case Types.Web:
- return new WebField(new URL(data), id, false)
- case Types.RichText:
- return new RichTextField(data, id, false)
- case Types.Key:
- return new Key(data, id, false)
- case Types.Image:
- return new ImageField(new URL(data), id, false)
- case Types.PDF:
- return new PDFField(new URL(data), id, false)
- case Types.List:
- return ListField.FromJson(id, data)
- case Types.Audio:
- return new AudioField(new URL(data), id, false)
- case Types.Video:
- return new VideoField(new URL(data), id, false)
- case Types.Ink:
- return InkField.FromJson(id, data);
- case Types.Document:
- let doc: Document = new Document(id, false)
- let fields: [string, string][] = data as [string, string][]
- fields.forEach(element => {
- doc._proxies.set(element[0], element[1]);
- });
- return doc
- default:
- throw Error("Error, unrecognized field type received from server. If you just created a new field type, be sure to add it here");
- }
- }
-} \ No newline at end of file
diff --git a/src/server/authentication/config/passport.ts b/src/server/authentication/config/passport.ts
index 05f6c3133..d42741410 100644
--- a/src/server/authentication/config/passport.ts
+++ b/src/server/authentication/config/passport.ts
@@ -1,9 +1,10 @@
-import * as passport from 'passport'
+import * as passport from 'passport';
import * as passportLocal from 'passport-local';
import * as mongodb from 'mongodb';
import * as _ from "lodash";
-import { default as User } from '../models/User';
+import { default as User } from '../models/user_model';
import { Request, Response, NextFunction } from "express";
+import { RouteStore } from '../../RouteStore';
const LocalStrategy = passportLocal.Strategy;
@@ -18,10 +19,10 @@ passport.deserializeUser<any, any>((id, done) => {
});
// AUTHENTICATE JUST WITH EMAIL AND PASSWORD
-passport.use(new LocalStrategy({ usernameField: 'email' }, (email, password, done) => {
+passport.use(new LocalStrategy({ usernameField: 'email', passReqToCallback: true }, (req, email, password, done) => {
User.findOne({ email: email.toLowerCase() }, (error: any, user: any) => {
if (error) return done(error);
- if (!user) return done(undefined, false, { message: "Invalid email or password" }) // invalid email
+ if (!user) return done(undefined, false, { message: "Invalid email or password" }); // invalid email
user.comparePassword(password, (error: Error, isMatch: boolean) => {
if (error) return done(error);
if (!isMatch) return done(undefined, false, { message: "Invalid email or password" }); // invalid password
@@ -35,8 +36,8 @@ export let isAuthenticated = (req: Request, res: Response, next: NextFunction) =
if (req.isAuthenticated()) {
return next();
}
- return res.redirect("/login");
-}
+ return res.redirect(RouteStore.login);
+};
export let isAuthorized = (req: Request, res: Response, next: NextFunction) => {
const provider = req.path.split("/").slice(-1)[0];
diff --git a/src/server/authentication/controllers/user.ts b/src/server/authentication/controllers/user.ts
deleted file mode 100644
index f74ff9039..000000000
--- a/src/server/authentication/controllers/user.ts
+++ /dev/null
@@ -1,107 +0,0 @@
-import { default as User, UserModel, AuthToken } from "../models/User";
-import { Request, Response, NextFunction } from "express";
-import * as passport from "passport";
-import { IVerifyOptions } from "passport-local";
-import "../config/passport";
-import * as request from "express-validator";
-const flash = require("express-flash");
-import * as session from "express-session";
-import * as pug from 'pug';
-
-/**
- * GET /signup
- * Signup page.
- */
-export let getSignup = (req: Request, res: Response) => {
- if (req.user) {
- return res.redirect("/");
- }
- res.render("signup.pug", {
- title: "Sign Up"
- });
-};
-
-/**
- * POST /signup
- * Create a new local account.
- */
-export let postSignup = (req: Request, res: Response, next: NextFunction) => {
- req.assert("email", "Email is not valid").isEmail();
- req.assert("password", "Password must be at least 4 characters long").len({ min: 4 });
- req.assert("confirmPassword", "Passwords do not match").equals(req.body.password);
- req.sanitize("email").normalizeEmail({ gmail_remove_dots: false });
-
- const errors = req.validationErrors();
-
- if (errors) {
- req.flash("errors", "Unable to facilitate sign up. Please try again.");
- return res.redirect("/signup");
- }
-
- const user = new User({
- email: req.body.email,
- password: req.body.password
- });
-
- User.findOne({ email: req.body.email }, (err, existingUser) => {
- if (err) { return next(err); }
- if (existingUser) {
- req.flash("errors", "Account with that email address already exists.");
- return res.redirect("/signup");
- }
- user.save((err) => {
- if (err) { return next(err); }
- req.logIn(user, (err) => {
- if (err) {
- return next(err);
- }
- res.redirect("/");
- });
- });
- });
-};
-
-
-/**
- * GET /login
- * Login page.
- */
-export let getLogin = (req: Request, res: Response) => {
- if (req.user) {
- return res.redirect("/");
- }
- res.send("<p>dear lord please render</p>");
- // res.render("account/login", {
- // title: "Login"
- // });
-};
-
-/**
- * POST /login
- * Sign in using email and password.
- */
-export let postLogin = (req: Request, res: Response, next: NextFunction) => {
- req.assert("email", "Email is not valid").isEmail();
- req.assert("password", "Password cannot be blank").notEmpty();
- req.sanitize("email").normalizeEmail({ gmail_remove_dots: false });
-
- const errors = req.validationErrors();
-
- if (errors) {
- req.flash("errors", "Unable to login at this time. Please try again.");
- return res.redirect("/login");
- }
-
- passport.authenticate("local", (err: Error, user: UserModel, info: IVerifyOptions) => {
- if (err) { return next(err); }
- if (!user) {
- req.flash("errors", info.message);
- return res.redirect("/login");
- }
- req.logIn(user, (err) => {
- if (err) { return next(err); }
- req.flash("success", "Success! You are logged in.");
- res.redirect("/");
- });
- })(req, res, next);
-}; \ No newline at end of file
diff --git a/src/server/authentication/controllers/user_controller.ts b/src/server/authentication/controllers/user_controller.ts
new file mode 100644
index 000000000..1dacdf3fa
--- /dev/null
+++ b/src/server/authentication/controllers/user_controller.ts
@@ -0,0 +1,266 @@
+import { default as User, DashUserModel, AuthToken } from "../models/user_model";
+import { Request, Response, NextFunction } from "express";
+import * as passport from "passport";
+import { IVerifyOptions } from "passport-local";
+import "../config/passport";
+import * as request from "express-validator";
+import flash = require("express-flash");
+import * as session from "express-session";
+import * as pug from 'pug';
+import * as async from 'async';
+import * as nodemailer from 'nodemailer';
+import c = require("crypto");
+import { RouteStore } from "../../RouteStore";
+import { Utils } from "../../../Utils";
+
+/**
+ * GET /signup
+ * Directs user to the signup page
+ * modeled by signup.pug in views
+ */
+export let getSignup = (req: Request, res: Response) => {
+ if (req.user) {
+ let user = req.user;
+ return res.redirect(RouteStore.home);
+ }
+ res.render("signup.pug", {
+ title: "Sign Up",
+ user: req.user,
+ });
+};
+
+/**
+ * POST /signup
+ * Create a new local account.
+ */
+export let postSignup = (req: Request, res: Response, next: NextFunction) => {
+ req.assert("email", "Email is not valid").isEmail();
+ req.assert("password", "Password must be at least 4 characters long").len({ min: 4 });
+ req.assert("confirmPassword", "Passwords do not match").equals(req.body.password);
+ req.sanitize("email").normalizeEmail({ gmail_remove_dots: false });
+
+ const errors = req.validationErrors();
+
+ if (errors) {
+ res.render("signup.pug", {
+ title: "Sign Up",
+ user: req.user,
+ });
+ return res.redirect(RouteStore.signup);
+ }
+
+ const email = req.body.email;
+ const password = req.body.password;
+
+ const user = new User({
+ email,
+ password,
+ userDocumentId: Utils.GenerateGuid()
+ });
+
+ User.findOne({ email }, (err, existingUser) => {
+ if (err) { return next(err); }
+ if (existingUser) {
+ return res.redirect(RouteStore.login);
+ }
+ user.save((err) => {
+ if (err) { return next(err); }
+ req.logIn(user, (err) => {
+ if (err) {
+ return next(err);
+ }
+ res.redirect(RouteStore.home);
+ });
+ });
+ });
+
+};
+
+
+/**
+ * GET /login
+ * Login page.
+ */
+export let getLogin = (req: Request, res: Response) => {
+ if (req.user) {
+ return res.redirect(RouteStore.home);
+ }
+ res.render("login.pug", {
+ title: "Log In",
+ user: req.user
+ });
+};
+
+/**
+ * POST /login
+ * Sign in using email and password.
+ * On failure, redirect to signup page
+ */
+export let postLogin = (req: Request, res: Response, next: NextFunction) => {
+ req.assert("email", "Email is not valid").isEmail();
+ req.assert("password", "Password cannot be blank").notEmpty();
+ req.sanitize("email").normalizeEmail({ gmail_remove_dots: false });
+
+ const errors = req.validationErrors();
+
+ if (errors) {
+ req.flash("errors", "Unable to login at this time. Please try again.");
+ return res.redirect(RouteStore.signup);
+ }
+
+ passport.authenticate("local", (err: Error, user: DashUserModel, info: IVerifyOptions) => {
+ if (err) { next(err); return; }
+ if (!user) {
+ return res.redirect(RouteStore.signup);
+ }
+ req.logIn(user, (err) => {
+ if (err) { next(err); return; }
+ res.redirect(RouteStore.home);
+ });
+ })(req, res, next);
+};
+
+/**
+ * GET /logout
+ * Invokes the logout function on the request
+ * and destroys the user's current session.
+ */
+export let getLogout = (req: Request, res: Response) => {
+ req.logout();
+ const sess = req.session;
+ if (sess) {
+ sess.destroy((err) => { if (err) { console.log(err); } });
+ }
+ res.redirect(RouteStore.login);
+};
+
+export let getForgot = function (req: Request, res: Response) {
+ res.render("forgot.pug", {
+ title: "Recover Password",
+ user: req.user,
+ });
+};
+
+export let postForgot = function (req: Request, res: Response, next: NextFunction) {
+ const email = req.body.email;
+ async.waterfall([
+ function (done: any) {
+ let token: string;
+ c.randomBytes(20, function (err: any, buffer: Buffer) {
+ if (err) {
+ done(null);
+ return;
+ }
+ done(null, buffer.toString('hex'));
+ });
+ },
+ function (token: string, done: any) {
+ User.findOne({ email }, function (err, user: DashUserModel) {
+ if (!user) {
+ // NO ACCOUNT WITH SUBMITTED EMAIL
+ res.redirect(RouteStore.forgot);
+ return;
+ }
+ user.passwordResetToken = token;
+ user.passwordResetExpires = new Date(Date.now() + 3600000); // 1 HOUR
+ user.save(function (err: any) {
+ done(null, token, user);
+ });
+ });
+ },
+ function (token: Uint16Array, user: DashUserModel, done: any) {
+ const smtpTransport = nodemailer.createTransport({
+ service: 'Gmail',
+ auth: {
+ user: 'brownptcdash@gmail.com',
+ pass: 'browngfx1'
+ }
+ });
+ const mailOptions = {
+ to: user.email,
+ from: 'brownptcdash@gmail.com',
+ subject: 'Dash Password Reset',
+ text: 'You are receiving this because you (or someone else) have requested the reset of the password for your account.\n\n' +
+ 'Please click on the following link, or paste this into your browser to complete the process:\n\n' +
+ 'http://' + req.headers.host + '/reset/' + token + '\n\n' +
+ 'If you did not request this, please ignore this email and your password will remain unchanged.\n'
+ };
+ smtpTransport.sendMail(mailOptions, function (err) {
+ // req.flash('info', 'An e-mail has been sent to ' + user.email + ' with further instructions.');
+ done(null, err, 'done');
+ });
+ }
+ ], function (err) {
+ if (err) return next(err);
+ res.redirect(RouteStore.forgot);
+ });
+};
+
+export let getReset = function (req: Request, res: Response) {
+ User.findOne({ passwordResetToken: req.params.token, passwordResetExpires: { $gt: Date.now() } }, function (err, user: DashUserModel) {
+ if (!user || err) {
+ return res.redirect(RouteStore.forgot);
+ }
+ res.render("reset.pug", {
+ title: "Reset Password",
+ user: req.user,
+ });
+ });
+};
+
+export let postReset = function (req: Request, res: Response) {
+ async.waterfall([
+ function (done: any) {
+ User.findOne({ passwordResetToken: req.params.token, passwordResetExpires: { $gt: Date.now() } }, function (err, user: DashUserModel) {
+ if (!user || err) {
+ return res.redirect('back');
+ }
+
+ req.assert("password", "Password must be at least 4 characters long").len({ min: 4 });
+ req.assert("confirmPassword", "Passwords do not match").equals(req.body.password);
+
+ if (req.validationErrors()) {
+ return res.redirect('back');
+ }
+
+ user.password = req.body.password;
+ user.passwordResetToken = undefined;
+ user.passwordResetExpires = undefined;
+
+ user.save(function (err) {
+ if (err) {
+ res.redirect(RouteStore.login);
+ return;
+ }
+ req.logIn(user, function (err) {
+ if (err) {
+ return;
+ }
+ });
+ done(null, user);
+ });
+ });
+ },
+ function (user: DashUserModel, done: any) {
+ const smtpTransport = nodemailer.createTransport({
+ service: 'Gmail',
+ auth: {
+ user: 'brownptcdash@gmail.com',
+ pass: 'browngfx1'
+ }
+ });
+ const mailOptions = {
+ to: user.email,
+ from: 'brownptcdash@gmail.com',
+ subject: 'Your password has been changed',
+ text: 'Hello,\n\n' +
+ 'This is a confirmation that the password for your account ' + user.email + ' has just been changed.\n'
+ };
+ smtpTransport.sendMail(mailOptions, function (err) {
+ done(null, err);
+ });
+ }
+ ], function (err) {
+ res.redirect(RouteStore.login);
+ });
+}; \ No newline at end of file
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
new file mode 100644
index 000000000..aef2d3f4a
--- /dev/null
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -0,0 +1,137 @@
+import { computed, observable, action, runInAction } from "mobx";
+import * as rp from 'request-promise';
+import { Docs } from "../../../client/documents/Documents";
+import { Attribute, AttributeGroup, Catalog, Schema, AggregateFunction } from "../../../client/northstar/model/idea/idea";
+import { ArrayUtil } from "../../../client/northstar/utils/ArrayUtil";
+import { RouteStore } from "../../RouteStore";
+import { DocServer } from "../../../client/DocServer";
+import { Doc, Opt, Field } from "../../../new_fields/Doc";
+import { List } from "../../../new_fields/List";
+import { CollectionViewType } from "../../../client/views/collections/CollectionBaseView";
+import { CollectionTreeView } from "../../../client/views/collections/CollectionTreeView";
+import { CollectionView } from "../../../client/views/collections/CollectionView";
+import { NorthstarSettings, Gateway } from "../../../client/northstar/manager/Gateway";
+import { AttributeTransformationModel } from "../../../client/northstar/core/attribute/AttributeTransformationModel";
+import { ColumnAttributeModel } from "../../../client/northstar/core/attribute/AttributeModel";
+import { HistogramOperation } from "../../../client/northstar/operations/HistogramOperation";
+import { Cast, PromiseValue } from "../../../new_fields/Types";
+import { listSpec } from "../../../new_fields/Schema";
+
+export class CurrentUserUtils {
+ private static curr_email: string;
+ private static curr_id: string;
+ @observable private static user_document: Doc;
+ //TODO tfs: these should be temporary...
+ private static mainDocId: string | undefined;
+
+ public static get email() { return this.curr_email; }
+ public static get id() { return this.curr_id; }
+ @computed public static get UserDocument() { return this.user_document; }
+ public static get MainDocId() { return this.mainDocId; }
+ public static set MainDocId(id: string | undefined) { this.mainDocId = id; }
+
+ private static createUserDocument(id: string): Doc {
+ let doc = new Doc(id, true);
+ doc.viewType = CollectionViewType.Tree;
+ doc.layout = CollectionView.LayoutString();
+ doc.title = this.email;
+ doc.data = new List<Doc>();
+ doc.excludeFromLibrary = true;
+ doc.optionalRightCollection = Docs.SchemaDocument(["title"], [], { title: "Pending documents" });
+ // doc.library = Docs.TreeDocument([doc], { title: `Library: ${CurrentUserUtils.email}` });
+ // (doc.library as Doc).excludeFromLibrary = true;
+ return doc;
+ }
+
+ public static async loadCurrentUser(): Promise<any> {
+ let userPromise = rp.get(DocServer.prepend(RouteStore.getCurrUser)).then(response => {
+ if (response) {
+ let obj = JSON.parse(response);
+ CurrentUserUtils.curr_id = obj.id as string;
+ CurrentUserUtils.curr_email = obj.email as string;
+ } else {
+ throw new Error("There should be a user! Why does Dash think there isn't one?");
+ }
+ });
+ let userDocPromise = await rp.get(DocServer.prepend(RouteStore.getUserDocumentId)).then(id => {
+ if (id) {
+ return DocServer.GetRefField(id).then(field =>
+ runInAction(() => this.user_document = field instanceof Doc ? field : this.createUserDocument(id)));
+ } else {
+ throw new Error("There should be a user id! Why does Dash think there isn't one?");
+ }
+ });
+ try {
+ const getEnvironment = await fetch("/assets/env.json", { redirect: "follow", method: "GET", credentials: "include" });
+ NorthstarSettings.Instance.UpdateEnvironment(await getEnvironment.json());
+ await Gateway.Instance.ClearCatalog();
+ const extraSchemas = Cast(CurrentUserUtils.UserDocument.DBSchemas, listSpec("string"), []);
+ let extras = await Promise.all(extraSchemas.map(sc => Gateway.Instance.GetSchema("", sc)));
+ let catprom = CurrentUserUtils.SetNorthstarCatalog(await Gateway.Instance.GetCatalog(), extras);
+ if (catprom) await Promise.all(catprom);
+ } catch (e) {
+
+ }
+ return Promise.all([userPromise, userDocPromise]);
+ }
+
+ /* Northstar catalog ... really just for testing so this should eventually go away */
+ // --------------- Northstar hooks ------------- /
+ static _northstarSchemas: Doc[] = [];
+ @observable private static _northstarCatalog?: Catalog;
+ @computed public static get NorthstarDBCatalog() { return this._northstarCatalog; }
+
+ @action static SetNorthstarCatalog(ctlog: Catalog, extras: Catalog[]) {
+ CurrentUserUtils.NorthstarDBCatalog = ctlog;
+ if (ctlog && ctlog.schemas) {
+ extras.map(ex => ctlog.schemas!.push(ex));
+ return ctlog.schemas.map(async schema => {
+ let schemaDocuments: Doc[] = [];
+ let attributesToBecomeDocs = CurrentUserUtils.GetAllNorthstarColumnAttributes(schema);
+ await Promise.all(attributesToBecomeDocs.reduce((promises, attr) => {
+ promises.push(DocServer.GetRefField(attr.displayName! + ".alias").then(action((field: Opt<Field>) => {
+ if (field instanceof Doc) {
+ schemaDocuments.push(field);
+ } else {
+ var atmod = new ColumnAttributeModel(attr);
+ let histoOp = new HistogramOperation(schema.displayName!,
+ new AttributeTransformationModel(atmod, AggregateFunction.None),
+ new AttributeTransformationModel(atmod, AggregateFunction.Count),
+ new AttributeTransformationModel(atmod, AggregateFunction.Count));
+ schemaDocuments.push(Docs.HistogramDocument(histoOp, { width: 200, height: 200, title: attr.displayName! }));
+ }
+ })));
+ return promises;
+ }, [] as Promise<void>[]));
+ return CurrentUserUtils._northstarSchemas.push(Docs.TreeDocument(schemaDocuments, { width: 50, height: 100, title: schema.displayName! }));
+ });
+ }
+ }
+ public static set NorthstarDBCatalog(ctlog: Catalog | undefined) { this._northstarCatalog = ctlog; }
+
+ public static AddNorthstarSchema(schema: Schema, schemaDoc: Doc) {
+ if (this._northstarCatalog && CurrentUserUtils._northstarSchemas) {
+ this._northstarCatalog.schemas!.push(schema);
+ CurrentUserUtils._northstarSchemas.push(schemaDoc);
+ let schemas = Cast(CurrentUserUtils.UserDocument.DBSchemas, listSpec("string"), []);
+ schemas.push(schema.displayName!);
+ CurrentUserUtils.UserDocument.DBSchemas = new List<string>(schemas);
+ }
+ }
+ public static GetNorthstarSchema(name: string): Schema | undefined {
+ return !this._northstarCatalog || !this._northstarCatalog.schemas ? undefined :
+ ArrayUtil.FirstOrDefault<Schema>(this._northstarCatalog.schemas, (s: Schema) => s.displayName === name);
+ }
+ public static GetAllNorthstarColumnAttributes(schema: Schema) {
+ const recurs = (attrs: Attribute[], g?: AttributeGroup) => {
+ if (g && g.attributes) {
+ attrs.push.apply(attrs, g.attributes);
+ if (g.attributeGroups) {
+ g.attributeGroups.forEach(ng => recurs(attrs, ng));
+ }
+ }
+ return attrs;
+ };
+ return recurs([] as Attribute[], schema ? schema.rootAttributeGroup : undefined);
+ }
+} \ No newline at end of file
diff --git a/src/server/authentication/models/User.ts b/src/server/authentication/models/user_model.ts
index 9752c4260..ee85e1c05 100644
--- a/src/server/authentication/models/User.ts
+++ b/src/server/authentication/models/user_model.ts
@@ -1,9 +1,8 @@
//@ts-ignore
import * as bcrypt from "bcrypt-nodejs";
-import * as crypto from "crypto";
//@ts-ignore
import * as mongoose from "mongoose";
-var url = 'mongodb://localhost:27017/Dash'
+var url = 'mongodb://localhost:27017/Dash';
mongoose.connect(url, { useNewUrlParser: true });
@@ -16,12 +15,13 @@ mongoose.connection.on('error', function (error) {
mongoose.connection.on('disconnected', function () {
console.log('connection closed');
});
-export type UserModel = mongoose.Document & {
+export type DashUserModel = mongoose.Document & {
email: string,
password: string,
- passwordResetToken: string,
- passwordResetExpires: Date,
- tokens: AuthToken[],
+ passwordResetToken?: string,
+ passwordResetExpires?: Date,
+
+ userDocumentId: string;
profile: {
name: string,
@@ -47,10 +47,11 @@ const userSchema = new mongoose.Schema({
passwordResetToken: String,
passwordResetExpires: Date,
+ userDocumentId: String,
+
facebook: String,
twitter: String,
google: String,
- tokens: Array,
profile: {
name: String,
@@ -65,22 +66,26 @@ const userSchema = new mongoose.Schema({
* Password hash middleware.
*/
userSchema.pre("save", function save(next) {
- const user = this as UserModel;
- if (!user.isModified("password")) { return next(); }
+ const user = this as DashUserModel;
+ if (!user.isModified("password")) {
+ return next();
+ }
bcrypt.genSalt(10, (err, salt) => {
- if (err) { return next(err); }
+ if (err) {
+ return next(err);
+ }
bcrypt.hash(user.password, salt, () => void {}, (err: mongoose.Error, hash) => {
- if (err) { return next(err); }
+ if (err) {
+ return next(err);
+ }
user.password = hash;
next();
});
});
});
-const comparePassword: comparePasswordFunction = function (this: UserModel, candidatePassword, cb) {
- bcrypt.compare(candidatePassword, this.password, (err: mongoose.Error, isMatch: boolean) => {
- cb(err, isMatch);
- });
+const comparePassword: comparePasswordFunction = function (this: DashUserModel, candidatePassword, cb) {
+ bcrypt.compare(candidatePassword, this.password, cb);
};
userSchema.methods.comparePassword = comparePassword;
diff --git a/src/server/database.ts b/src/server/database.ts
index 07c5819ab..69005d2d3 100644
--- a/src/server/database.ts
+++ b/src/server/database.ts
@@ -1,81 +1,100 @@
-import { action, configure } from 'mobx';
import * as mongodb from 'mongodb';
-import { ObjectID } from 'mongodb';
import { Transferable } from './Message';
-import { Utils } from '../Utils';
export class Database {
- public static Instance = new Database()
+ public static DocumentsCollection = 'documents';
+ public static Instance = new Database();
private MongoClient = mongodb.MongoClient;
private url = 'mongodb://localhost:27017/Dash';
+ private currentWrites: { [id: string]: Promise<void> } = {};
private db?: mongodb.Db;
constructor() {
- this.MongoClient.connect(this.url, (err, client) => {
- this.db = client.db()
- })
+ this.MongoClient.connect(this.url, (err, client) => this.db = client.db());
}
- public update(id: string, value: any) {
+ public update(id: string, value: any, callback: () => void, upsert = true, collectionName = Database.DocumentsCollection) {
if (this.db) {
- let collection = this.db.collection('documents');
- collection.update({ _id: id }, { $set: value }, {
- upsert: true
- });
+ let collection = this.db.collection(collectionName);
+ const prom = this.currentWrites[id];
+ let newProm: Promise<void>;
+ const run = (): Promise<void> => {
+ return new Promise<void>(resolve => {
+ collection.updateOne({ _id: id }, value, { upsert }
+ , (err, res) => {
+ if (this.currentWrites[id] === newProm) {
+ delete this.currentWrites[id];
+ }
+ resolve();
+ callback();
+ });
+ });
+ };
+ newProm = prom ? prom.then(run) : run();
+ this.currentWrites[id] = newProm;
}
}
- public delete(id: string) {
- if (this.db) {
- let collection = this.db.collection('documents');
- collection.remove({ _id: id });
- }
+ public delete(id: string, collectionName = Database.DocumentsCollection) {
+ this.db && this.db.collection(collectionName).remove({ id: id });
}
- public deleteAll() {
- if (this.db) {
- let collection = this.db.collection('documents');
- collection.deleteMany({});
- }
+ public deleteAll(collectionName = Database.DocumentsCollection): Promise<any> {
+ return new Promise(res =>
+ this.db && this.db.collection(collectionName).deleteMany({}, res));
}
- public insert(kvpairs: any) {
- if (this.db) {
- let collection = this.db.collection('documents');
- collection.insertOne(kvpairs, (err: any, res: any) => {
- if (err) {
- // console.log(err)
- return
- }
- });
+ public insert(value: any, collectionName = Database.DocumentsCollection) {
+ if (!this.db) { return; }
+ if ("id" in value) {
+ value._id = value.id;
+ delete value.id;
}
+ const id = value._id;
+ const collection = this.db.collection(collectionName);
+ const prom = this.currentWrites[id];
+ let newProm: Promise<void>;
+ const run = (): Promise<void> => {
+ return new Promise<void>(resolve => {
+ collection.insertOne(value, (err, res) => {
+ if (this.currentWrites[id] === newProm) {
+ delete this.currentWrites[id];
+ }
+ resolve();
+ });
+ });
+ };
+ newProm = prom ? prom.then(run) : run();
+ this.currentWrites[id] = newProm;
}
- public getDocument(id: string, fn: (res: any) => void) {
- var result: JSON;
- if (this.db) {
- let collection = this.db.collection('documents');
- collection.findOne({ _id: id }, (err: any, res: any) => {
- result = res
- if (!result) {
- fn(undefined)
- }
- fn(result)
- })
- };
+ public getDocument(id: string, fn: (result?: Transferable) => void, collectionName = Database.DocumentsCollection) {
+ this.db && this.db.collection(collectionName).findOne({ _id: id }, (err, result) => {
+ if (result) {
+ result.id = result._id;
+ delete result._id;
+ fn(result);
+ } else {
+ fn(undefined);
+ }
+ });
}
- public getDocuments(ids: string[], fn: (res: any) => void) {
- if (this.db) {
- let collection = this.db.collection('documents');
- let cursor = collection.find({ _id: { "$in": ids } })
- cursor.toArray((err, docs) => {
- fn(docs);
- })
- };
+ public getDocuments(ids: string[], fn: (result: Transferable[]) => void, collectionName = Database.DocumentsCollection) {
+ this.db && this.db.collection(collectionName).find({ _id: { "$in": ids } }).toArray((err, docs) => {
+ if (err) {
+ console.log(err.message);
+ console.log(err.errmsg);
+ }
+ fn(docs.map(doc => {
+ doc.id = doc._id;
+ delete doc._id;
+ return doc;
+ }));
+ });
}
public print() {
- console.log("db says hi!")
+ console.log("db says hi!");
}
}
diff --git a/src/server/index.ts b/src/server/index.ts
index 0d0b65b22..da6bc0165 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -1,68 +1,66 @@
-import * as express from 'express'
-const app = express()
-import * as webpack from 'webpack'
-import * as wdm from 'webpack-dev-middleware';
-import * as whm from 'webpack-hot-middleware';
-import * as path from 'path'
-import * as formidable from 'formidable'
+import * as bodyParser from 'body-parser';
+import { exec } from 'child_process';
+import * as cookieParser from 'cookie-parser';
+import * as express from 'express';
+import * as session from 'express-session';
+import * as expressValidator from 'express-validator';
+import * as formidable from 'formidable';
+import * as fs from 'fs';
+import * as mobileDetect from 'mobile-detect';
+import { ObservableMap } from 'mobx';
import * as passport from 'passport';
-import { MessageStore, Message, SetFieldArgs, GetFieldArgs, Transferable } from "./Message";
-import { Client } from './Client';
+import * as path from 'path';
+import * as request from 'request';
+import * as rp from 'request-promise';
+import * as io from 'socket.io';
import { Socket } from 'socket.io';
+import * as webpack from 'webpack';
+import * as wdm from 'webpack-dev-middleware';
+import * as whm from 'webpack-hot-middleware';
import { Utils } from '../Utils';
-import { ObservableMap } from 'mobx';
-import { FieldId, Field } from '../fields/Field';
+import { getForgot, getLogin, getLogout, getReset, getSignup, postForgot, postLogin, postReset, postSignup } from './authentication/controllers/user_controller';
+import { DashUserModel } from './authentication/models/user_model';
+import { Client } from './Client';
import { Database } from './database';
-import { ServerUtils } from './ServerUtil';
-import { ObjectID } from 'mongodb';
-import { Document } from '../fields/Document';
-import * as io from 'socket.io'
-import * as passportConfig from './authentication/config/passport';
-import { getLogin, postLogin, getSignup, postSignup } from './authentication/controllers/user';
+import { MessageStore, Transferable, Types, Diff } from "./Message";
+import { RouteStore } from './RouteStore';
+const app = express();
const config = require('../../webpack.config');
const compiler = webpack(config);
const port = 1050; // default port to listen
-const serverPort = 1234;
-import * as expressValidator from 'express-validator';
+const serverPort = 4321;
import expressFlash = require('express-flash');
-import * as bodyParser from 'body-parser';
-import * as session from 'express-session';
+import flash = require('connect-flash');
import c = require("crypto");
+import { Search } from './Search';
+import { debug } from 'util';
+import _ = require('lodash');
const MongoStore = require('connect-mongo')(session);
const mongoose = require('mongoose');
-const bluebird = require('bluebird');
-import { performance } from 'perf_hooks'
-import * as fs from 'fs';
-import * as request from 'request'
-const download = (url: string, dest: fs.PathLike) => {
- request.get(url).pipe(fs.createWriteStream(dest));
-}
+const download = (url: string, dest: fs.PathLike) => request.get(url).pipe(fs.createWriteStream(dest));
const mongoUrl = 'mongodb://localhost:27017/Dash';
-// mongoose.Promise = bluebird;
-mongoose.connect(mongoUrl)//.then(
-// () => { /** ready to use. The `mongoose.connect()` promise resolves to undefined. */ },
-// ).catch((err: any) => {
-// console.log("MongoDB connection error. Please make sure MongoDB is running. " + err);
-// process.exit();
-// });
-mongoose.connection.on('connected', function () {
- console.log("connected");
-})
+mongoose.connect(mongoUrl);
+mongoose.connection.on('connected', () => console.log("connected"));
-app.use(bodyParser.json());
-app.use(bodyParser.urlencoded({ extended: true }));
-app.use(expressValidator());
-app.use(expressFlash());
-app.use(require('express-session')({
- secret: `${c.randomBytes(64)}`,
+// SESSION MANAGEMENT AND AUTHENTICATION MIDDLEWARE
+// ORDER OF IMPORTS MATTERS
+
+app.use(cookieParser());
+app.use(session({
+ secret: "64d6866242d3b5a5503c675b32c9605e4e90478e9b77bcf2bc",
resave: true,
+ cookie: { maxAge: 7 * 24 * 60 * 60 * 1000 },
saveUninitialized: true,
- store: new MongoStore({
- url: 'mongodb://localhost:27017/Dash'
- })
+ store: new MongoStore({ url: 'mongodb://localhost:27017/Dash' })
}));
+
+app.use(flash());
+app.use(expressFlash());
+app.use(bodyParser.json());
+app.use(bodyParser.urlencoded({ extended: true }));
+app.use(expressValidator());
app.use(passport.initialize());
app.use(passport.session());
app.use((req, res, next) => {
@@ -70,111 +68,311 @@ app.use((req, res, next) => {
next();
});
-app.get("/signup", getSignup);
-app.post("/signup", postSignup);
-app.get("/login", getLogin);
-app.post("/login", postLogin);
-
-// IMAGE UPLOADING HANDLER
-app.post("/upload", (req, res, err) => {
- let form = new formidable.IncomingForm()
- form.uploadDir = __dirname + "/public/files/"
- form.keepExtensions = true
- // let path = req.body.path;
- console.log("upload")
- form.parse(req, (err, fields, files) => {
- console.log("parsing")
- let names: any[] = [];
- for (const name in files) {
- let file = files[name];
- names.push(`/files/` + path.basename(file.path));
+app.get("/hello", (req, res) => res.send("<p>Hello</p>"));
+
+enum Method {
+ GET,
+ POST
+}
+
+/**
+ * Please invoke this function when adding a new route to Dash's server.
+ * It ensures that any requests leading to or containing user-sensitive information
+ * does not execute unless Passport authentication detects a user logged in.
+ * @param method whether or not the request is a GET or a POST
+ * @param handler the action to invoke, recieving a DashUserModel and, as expected, the Express.Request and Express.Response
+ * @param onRejection an optional callback invoked on return if no user is found to be logged in
+ * @param subscribers the forward slash prepended path names (reference and add to RouteStore.ts) that will all invoke the given @param handler
+ */
+function addSecureRoute(method: Method,
+ handler: (user: DashUserModel, res: express.Response, req: express.Request) => void,
+ onRejection: (res: express.Response) => any = (res) => res.redirect(RouteStore.logout),
+ ...subscribers: string[]
+) {
+ let abstracted = (req: express.Request, res: express.Response) => {
+ if (req.user) {
+ handler(req.user, res, req);
+ } else {
+ onRejection(res);
+ }
+ };
+ subscribers.forEach(route => {
+ switch (method) {
+ case Method.GET:
+ app.get(route, abstracted);
+ break;
+ case Method.POST:
+ app.post(route, abstracted);
+ break;
}
- res.send(names);
});
-})
+}
-app.use(express.static(__dirname + '/public'));
-app.use('/images', express.static(__dirname + '/public'))
+// STATIC FILE SERVING
+app.use(express.static(__dirname + RouteStore.public));
+app.use(RouteStore.images, express.static(__dirname + RouteStore.public));
-let FieldStore: ObservableMap<FieldId, Field> = new ObservableMap();
+app.get("/pull", (req, res) =>
+ exec('"C:\\Program Files\\Git\\git-bash.exe" -c "git pull"', (err, stdout, stderr) => {
+ if (err) {
+ res.send(err.message);
+ return;
+ }
+ res.redirect("/");
+ }));
-// define a route handler for the default home page
-app.get("/", (req, res) => {
- res.sendFile(path.join(__dirname, '../../deploy/index.html'));
-});
+// SEARCH
-app.get("/hello", (req, res) => {
- res.send("<p>Hello</p>");
-})
+// GETTERS
-app.use("/corsProxy", (req, res) => {
- req.pipe(request(req.url.substring(1))).pipe(res);
+app.get("/search", async (req, res) => {
+ let query = req.query.query || "hello";
+ let results = await Search.Instance.search(query);
+ res.send(results);
});
-app.get("/delete", (req, res) => {
- deleteAll();
- res.redirect("/");
-});
+// anyone attempting to navigate to localhost at this port will
+// first have to login
+addSecureRoute(
+ Method.GET,
+ (user, res) => res.redirect(RouteStore.home),
+ undefined,
+ RouteStore.root
+);
+
+addSecureRoute(
+ Method.GET,
+ (user, res, req) => {
+ let detector = new mobileDetect(req.headers['user-agent'] || "");
+ let filename = detector.mobile() !== null ? 'mobile/image.html' : 'index.html';
+ res.sendFile(path.join(__dirname, '../../deploy/' + filename));
+ },
+ undefined,
+ RouteStore.home,
+ RouteStore.openDocumentWithId
+);
-app.use(wdm(compiler, {
- publicPath: config.output.publicPath
-}))
+addSecureRoute(
+ Method.GET,
+ (user, res) => res.send(user.userDocumentId || ""),
+ undefined,
+ RouteStore.getUserDocumentId,
+);
-app.use(whm(compiler))
+addSecureRoute(
+ Method.GET,
+ (user, res) => res.send(JSON.stringify({ id: user.id, email: user.email })),
+ undefined,
+ RouteStore.getCurrUser
+);
+
+// SETTERS
+
+addSecureRoute(
+ Method.POST,
+ (user, res, req) => {
+ let form = new formidable.IncomingForm();
+ form.uploadDir = __dirname + "/public/files/";
+ form.keepExtensions = true;
+ // let path = req.body.path;
+ console.log("upload");
+ form.parse(req, (err, fields, files) => {
+ console.log("parsing");
+ let names: string[] = [];
+ for (const name in files) {
+ names.push(`/files/` + path.basename(files[name].path));
+ }
+ res.send(names);
+ });
+ },
+ undefined,
+ RouteStore.upload
+);
+
+// AUTHENTICATION
+
+// Sign Up
+app.get(RouteStore.signup, getSignup);
+app.post(RouteStore.signup, postSignup);
+
+// Log In
+app.get(RouteStore.login, getLogin);
+app.post(RouteStore.login, postLogin);
+
+// Log Out
+app.get(RouteStore.logout, getLogout);
+
+// FORGOT PASSWORD EMAIL HANDLING
+app.get(RouteStore.forgot, getForgot);
+app.post(RouteStore.forgot, postForgot);
+
+// RESET PASSWORD EMAIL HANDLING
+app.get(RouteStore.reset, getReset);
+app.post(RouteStore.reset, postReset);
+
+app.use(RouteStore.corsProxy, (req, res) =>
+ req.pipe(request(req.url.substring(1))).pipe(res));
+
+app.get(RouteStore.delete, (req, res) =>
+ deleteFields().then(() => res.redirect(RouteStore.home)));
+
+app.get(RouteStore.deleteAll, (req, res) =>
+ deleteAll().then(() => res.redirect(RouteStore.home)));
+
+app.use(wdm(compiler, { publicPath: config.output.publicPath }));
+
+app.use(whm(compiler));
// start the Express server
-app.listen(port, () => {
- console.log(`server started at http://localhost:${port}`);
-})
+app.listen(port, () =>
+ console.log(`server started at http://localhost:${port}`));
const server = io();
interface Map {
[key: string]: Client;
}
-let clients: Map = {}
+let clients: Map = {};
server.on("connection", function (socket: Socket) {
- console.log("a user has connected")
+ console.log("a user has connected");
- Utils.Emit(socket, MessageStore.Foo, "handshooken")
+ Utils.Emit(socket, MessageStore.Foo, "handshooken");
- Utils.AddServerHandler(socket, MessageStore.Bar, barReceived)
- Utils.AddServerHandler(socket, MessageStore.SetField, (args) => setField(socket, args))
- Utils.AddServerHandlerCallback(socket, MessageStore.GetField, getField)
- Utils.AddServerHandlerCallback(socket, MessageStore.GetFields, getFields)
- Utils.AddServerHandler(socket, MessageStore.DeleteAll, deleteAll)
-})
+ Utils.AddServerHandler(socket, MessageStore.Bar, barReceived);
+ Utils.AddServerHandler(socket, MessageStore.SetField, (args) => setField(socket, args));
+ Utils.AddServerHandlerCallback(socket, MessageStore.GetField, getField);
+ Utils.AddServerHandlerCallback(socket, MessageStore.GetFields, getFields);
+ Utils.AddServerHandler(socket, MessageStore.DeleteAll, deleteFields);
-function deleteAll() {
- Database.Instance.deleteAll();
+ Utils.AddServerHandler(socket, MessageStore.CreateField, CreateField);
+ Utils.AddServerHandler(socket, MessageStore.UpdateField, diff => UpdateField(socket, diff));
+ Utils.AddServerHandlerCallback(socket, MessageStore.GetRefField, GetRefField);
+ Utils.AddServerHandlerCallback(socket, MessageStore.GetRefFields, GetRefFields);
+});
+
+async function deleteFields() {
+ await Database.Instance.deleteAll();
+ await Search.Instance.clear();
+ await Database.Instance.deleteAll('newDocuments');
+}
+
+async function deleteAll() {
+ await Database.Instance.deleteAll();
+ await Database.Instance.deleteAll('newDocuments');
+ await Database.Instance.deleteAll('sessions');
+ await Database.Instance.deleteAll('users');
+ await Search.Instance.clear();
}
function barReceived(guid: String) {
clients[guid.toString()] = new Client(guid.toString());
}
-function addDocument(document: Document) {
+function getField([id, callback]: [string, (result?: Transferable) => void]) {
+ Database.Instance.getDocument(id, (result?: Transferable) =>
+ callback(result ? result : undefined));
+}
+function getFields([ids, callback]: [string[], (result: Transferable[]) => void]) {
+ Database.Instance.getDocuments(ids, callback);
}
-function getField([id, callback]: [string, (result: any) => void]) {
- Database.Instance.getDocument(id, (result: any) => {
- if (result) {
- callback(result)
+function setField(socket: Socket, newValue: Transferable) {
+ Database.Instance.update(newValue.id, newValue, () =>
+ socket.broadcast.emit(MessageStore.SetField.Message, newValue));
+ if (newValue.type === Types.Text) {
+ Search.Instance.updateDocument({ id: newValue.id, data: (newValue as any).data });
+ console.log("set field");
+ }
+}
+
+function GetRefField([id, callback]: [string, (result?: Transferable) => void]) {
+ Database.Instance.getDocument(id, callback, "newDocuments");
+}
+
+function GetRefFields([ids, callback]: [string[], (result?: Transferable[]) => void]) {
+ Database.Instance.getDocuments(ids, callback, "newDocuments");
+}
+
+
+const suffixMap: { [type: string]: (string | [string, string | ((json: any) => any)]) } = {
+ "number": "_n",
+ "string": "_t",
+ // "boolean": "_b",
+ "image": ["_t", "url"],
+ "video": ["_t", "url"],
+ "pdf": ["_t", "url"],
+ "audio": ["_t", "url"],
+ "web": ["_t", "url"],
+ "date": ["_d", value => new Date(value.date).toISOString()],
+ "proxy": ["_i", "fieldId"],
+ "list": ["_l", list => {
+ const results = [];
+ for (const value of list.fields) {
+ const term = ToSearchTerm(value);
+ if (term) {
+ results.push(term.value);
+ }
}
- else {
- callback(undefined)
+ return results.length ? results : null;
+ }]
+};
+
+function ToSearchTerm(val: any): { suffix: string, value: any } | undefined {
+ if (val === null || val === undefined) {
+ return;
+ }
+ const type = val.__type || typeof val;
+ let suffix = suffixMap[type];
+ if (!suffix) {
+ return;
+ }
+
+ if (Array.isArray(suffix)) {
+ const accessor = suffix[1];
+ if (typeof accessor === "function") {
+ val = accessor(val);
+ } else {
+ val = val[accessor];
}
- })
+ suffix = suffix[0];
+ }
+
+ return { suffix, value: val };
}
-function getFields([ids, callback]: [string[], (result: any) => void]) {
- Database.Instance.getDocuments(ids, callback);
+function getSuffix(value: string | [string, any]): string {
+ return typeof value === "string" ? value : value[0];
}
-function setField(socket: Socket, newValue: Transferable) {
- Database.Instance.update(newValue._id, newValue)
- socket.broadcast.emit(MessageStore.SetField.Message, newValue)
+function UpdateField(socket: Socket, diff: Diff) {
+ Database.Instance.update(diff.id, diff.diff,
+ () => socket.broadcast.emit(MessageStore.UpdateField.Message, diff), false, "newDocuments");
+ const docfield = diff.diff.$set;
+ if (!docfield) {
+ return;
+ }
+ const update: any = { id: diff.id };
+ let dynfield = false;
+ for (let key in docfield) {
+ if (!key.startsWith("fields.")) continue;
+ dynfield = true;
+ let val = docfield[key];
+ key = key.substring(7);
+ Object.values(suffixMap).forEach(suf => update[key + getSuffix(suf)] = { set: null });
+ let term = ToSearchTerm(val);
+ if (term !== undefined) {
+ let { suffix, value } = term;
+ update[key + suffix] = { set: value };
+ }
+ }
+ if (dynfield) {
+ Search.Instance.updateDocument(update);
+ }
+}
+
+function CreateField(newValue: any) {
+ Database.Instance.insert(newValue, "newDocuments");
}
server.listen(serverPort);
diff --git a/src/server/public/files/.gitignore b/src/server/public/files/.gitignore
new file mode 100644
index 000000000..f59ec20aa
--- /dev/null
+++ b/src/server/public/files/.gitignore
@@ -0,0 +1 @@
+* \ No newline at end of file