Commit b7054c9b204f88b7ebcc1f3853955e674a931b87

Authored by Deepak
1 parent c4d3e07d0f
Exists in master

added specific organisation login

Showing 43 changed files with 555 additions and 656 deletions   Show diff stats
... ... @@ -16,7 +16,7 @@ standard-minifier-js@1.2.2 # JS minifier run for production mode
16 16 es5-shim@4.6.15 # ECMAScript 5 compatibility for older browsers.
17 17 ecmascript@0.6.3 # Enable ECMAScript2015+ syntax in app code
18 18  
19   -accounts-password@1.3.4
  19 +accounts-password
20 20 accounts-base@1.2.14
21 21 check
22 22 audit-argument-checks@1.0.7
... ... @@ -35,3 +35,5 @@ dburles:factory@1.0.0
35 35 ddp-rate-limiter@1.0.6
36 36 shell-server@0.2.2
37 37 aldeed:simple-schema
  38 +momentjs:moment
  39 +meteorhacks:picker
... ...
... ... @@ -56,6 +56,7 @@ mdg:validated-method@1.1.0
56 56 mdg:validation-error@0.5.1
57 57 meteor@1.6.1
58 58 meteor-base@1.0.4
  59 +meteorhacks:picker@1.0.3
59 60 minifier-css@1.2.16
60 61 minifier-js@1.2.18
61 62 minimongo@1.0.20
... ... @@ -63,6 +64,7 @@ mobile-experience@1.0.4
63 64 mobile-status-bar@1.0.14
64 65 modules@0.7.9
65 66 modules-runtime@0.7.9
  67 +momentjs:moment@2.17.1
66 68 mongo@1.1.15
67 69 mongo-id@1.0.6
68 70 npm-bcrypt@0.9.2
... ...
imports/api/documents/documents.js
... ... @@ -1,36 +0,0 @@
1   -import { Mongo } from 'meteor/mongo';
2   -import { SimpleSchema } from 'meteor/aldeed:simple-schema';
3   -import { Factory } from 'meteor/dburles:factory';
4   -
5   -const Documents = new Mongo.Collection('Documents');
6   -export default Documents;
7   -
8   -Documents.allow({
9   - insert: () => false,
10   - update: () => false,
11   - remove: () => false,
12   -});
13   -
14   -Documents.deny({
15   - insert: () => true,
16   - update: () => true,
17   - remove: () => true,
18   -});
19   -
20   -Documents.schema = new SimpleSchema({
21   - title: {
22   - type: String,
23   - label: 'The title of the document.',
24   - },
25   - body: {
26   - type: String,
27   - label: 'The body of the document.',
28   - },
29   -});
30   -
31   -Documents.attachSchema(Documents.schema);
32   -
33   -Factory.define('document', Documents, {
34   - title: () => 'Factory Title',
35   - body: () => 'Factory Body',
36   -});
imports/api/documents/documents.tests.js
... ... @@ -1,11 +0,0 @@
1   -/* eslint-env mocha */
2   -/* eslint-disable func-names, prefer-arrow-callback */
3   -
4   -import { assert } from 'meteor/practicalmeteor:chai';
5   -import Documents from './documents.js';
6   -
7   -describe('Documents collection', function () {
8   - it('registers the collection with Mongo properly', function () {
9   - assert.equal(typeof Documents, 'object');
10   - });
11   -});
imports/api/documents/methods.js
... ... @@ -1,35 +0,0 @@
1   -import { SimpleSchema } from 'meteor/aldeed:simple-schema';
2   -import { ValidatedMethod } from 'meteor/mdg:validated-method';
3   -import Documents from './documents';
4   -import rateLimit from '../../modules/rate-limit.js';
5   -
6   -export const upsertDocument = new ValidatedMethod({
7   - name: 'documents.upsert',
8   - validate: new SimpleSchema({
9   - _id: { type: String, optional: true },
10   - title: { type: String, optional: true },
11   - body: { type: String, optional: true },
12   - }).validator(),
13   - run(document) {
14   - return Documents.upsert({ _id: document._id }, { $set: document });
15   - },
16   -});
17   -
18   -export const removeDocument = new ValidatedMethod({
19   - name: 'documents.remove',
20   - validate: new SimpleSchema({
21   - _id: { type: String },
22   - }).validator(),
23   - run({ _id }) {
24   - Documents.remove(_id);
25   - },
26   -});
27   -
28   -rateLimit({
29   - methods: [
30   - upsertDocument,
31   - removeDocument,
32   - ],
33   - limit: 5,
34   - timeRange: 1000,
35   -});
imports/api/documents/methods.tests.js
... ... @@ -1,47 +0,0 @@
1   -/* eslint-env mocha */
2   -/* eslint-disable func-names, prefer-arrow-callback */
3   -
4   -import { Meteor } from 'meteor/meteor';
5   -import { assert } from 'meteor/practicalmeteor:chai';
6   -import { resetDatabase } from 'meteor/xolvio:cleaner';
7   -import { Factory } from 'meteor/dburles:factory';
8   -import Documents from './documents.js';
9   -import { upsertDocument, removeDocument } from './methods.js';
10   -
11   -describe('Documents methods', function () {
12   - beforeEach(function () {
13   - if (Meteor.isServer) {
14   - resetDatabase();
15   - }
16   - });
17   -
18   - it('inserts a document into the Documents collection', function () {
19   - upsertDocument.call({
20   - title: 'You can\'t arrest me, I\'m the Cake Boss!',
21   - body: 'They went nuts!',
22   - });
23   -
24   - const getDocument = Documents.findOne({ title: 'You can\'t arrest me, I\'m the Cake Boss!' });
25   - assert.equal(getDocument.body, 'They went nuts!');
26   - });
27   -
28   - it('updates a document in the Documents collection', function () {
29   - const { _id } = Factory.create('document');
30   -
31   - upsertDocument.call({
32   - _id,
33   - title: 'You can\'t arrest me, I\'m the Cake Boss!',
34   - body: 'They went nuts!',
35   - });
36   -
37   - const getDocument = Documents.findOne(_id);
38   - assert.equal(getDocument.title, 'You can\'t arrest me, I\'m the Cake Boss!');
39   - });
40   -
41   - it('removes a document from the Documents collection', function () {
42   - const { _id } = Factory.create('document');
43   - removeDocument.call({ _id });
44   - const getDocument = Documents.findOne(_id);
45   - assert.equal(getDocument, undefined);
46   - });
47   -});
imports/api/documents/server/publications.js
... ... @@ -1,10 +0,0 @@
1   -import { Meteor } from 'meteor/meteor';
2   -import { check } from 'meteor/check';
3   -import Documents from '../documents';
4   -
5   -Meteor.publish('documents.list', () => Documents.find());
6   -
7   -Meteor.publish('documents.view', (_id) => {
8   - check(_id, String);
9   - return Documents.find(_id);
10   -});
imports/client/app/routes.js
... ... @@ -48,7 +48,7 @@ const detectOrg = () => {
48 48 if(orgSlug!=""){
49 49 Meteor.call('checkExistingOrg', {slug:orgSlug}, function(err, res) {
50 50 if(res){
51   - Session.set('orgId', res._id._str);
  51 + Session.set('orgId', res._id);
52 52 Session.set('orgSlug', orgSlug);
53 53 render(getOrgRoutes(),document.getElementById('app'));
54 54 }else{
... ...
imports/client/assets/css/icons/icomoon/styles.css
... ... @@ -2,7 +2,7 @@
2 2 font-family: 'icomoon';
3 3 src:url('fonts/icomoon.eot?3p0rtw');
4 4 src:url('fonts/icomoon.eot?#iefix3p0rtw') format('embedded-opentype'),
5   - url('fonts/icomoon.woff?3p0rtw') format('font-woff'),
  5 + url('fonts/icomoon.woff?3p0rtw') format('woff'),
6 6 url('fonts/icomoon.ttf?3p0rtw') format('truetype'),
7 7 url('fonts/icomoon.svg?3p0rtw#icomoon') format('svg');
8 8 font-weight: normal;
... ...
imports/client/views/org/app/module/navigation/PublicNavigation.js
... ... @@ -18,7 +18,7 @@ render(){
18 18 var signup = `http://${mainSite}/signup`;
19 19 return(
20 20 <Nav pullRight>
21   - <LinkContainer to={ setQueryParam(this.props.location, { enter: 'login' }) }>
  21 + <LinkContainer to="login">
22 22 <NavItem eventKey={ 2 } href="/login">Log In</NavItem>
23 23 </LinkContainer>
24 24 </Nav>
... ...
imports/client/views/org/enter/module/EnterLayout.js
... ... @@ -37,6 +37,7 @@ export class EnterLayout extends React.Component {
37 37 error: '',
38 38 message: '',
39 39 };
  40 + this.onClearState = this.onClearState.bind(this);
40 41 };
41 42  
42 43 componentWillReceiveProps(nextProps) {
... ... @@ -100,28 +101,39 @@ export class EnterLayout extends React.Component {
100 101 };
101 102  
102 103 onLogin(e) {
  104 + const self = this;
103 105 e.preventDefault();
  106 + const email = this.state.email;
  107 + const password = this.state.password;
104 108 this.onClearState(true);
105   - if(this.state.email.trim() == '' || !validation.validateEmail(this.state.email)){
  109 + if(email.trim() == '' || !validation.validateEmail(this.state.email)){
106 110 this.onClearState();
107 111 Bert.alert('Please enter a valid email address!', 'danger');
108 112 return false;
109 113 }
110   - if(this.state.password.trim() == ''){
  114 + if(password.trim() == ''){
111 115 Bert.alert('Please enter your password!', 'danger');
112 116 this.onClearState();
113 117 return false;
114 118 }
115   - Meteor.loginWithPassword(
116   - this.state.email,
117   - this.state.password,
118   - (e, r) => {
119   - this.onClearState();
120   - if(e) {
121   - this.setState({error: 'Wrong email or password.'})
122   - }
  119 + Meteor.call('checkEmailInOrg', {email:email,orgId:Session.get('orgId')}, function (error, result) {
  120 + if(result.success){
  121 + Meteor.loginWithPassword(
  122 + email,
  123 + password,
  124 + (e, r) => {
  125 + self.onClearState();
  126 + if(e) {
  127 + self.setState({error: 'Wrong password.'})
  128 + }
  129 + }
  130 + );
  131 + }else{
  132 + Bert.alert('This email is not associated to this Organisation!', 'danger');
  133 + self.onClearState();
123 134 }
124   - );
  135 +
  136 + });
125 137 };
126 138  
127 139 render() {
... ...
imports/collections/orgs/methods.js
... ... @@ -31,5 +31,24 @@ export const checkExistingOrg = new ValidatedMethod({
31 31 org = Orgs.findOne({slug:slug});
32 32 return org;
33 33 },
  34 +});
  35 +
  36 +export const checkEmailInOrg = new ValidatedMethod({
  37 + name: 'checkEmailInOrg',
  38 +
  39 + validate: new SimpleSchema({
  40 + email: { type: String },
  41 + orgId: { type: String },
  42 + }).validator(),
  43 +
  44 + run({email, orgId}) {
  45 + console.log(orgId);
  46 + user = Users.findOne({"orgId":orgId, "emails.address":email});
  47 + if(user){
  48 + return {success:true}
  49 + }else{
  50 + return {success:false}
  51 + }
  52 + },
34 53  
35 54 });
... ...
imports/modules/document-editor.js
... ... @@ -1,56 +0,0 @@
1   -/* eslint-disable no-undef */
2   -
3   -import { browserHistory } from 'react-router';
4   -import { Bert } from 'meteor/themeteorchef:bert';
5   -import { upsertDocument } from '../api/documents/methods.js';
6   -import './validation.js';
7   -
8   -let component;
9   -
10   -const handleUpsert = () => {
11   - const { doc } = component.props;
12   - const confirmation = doc && doc._id ? 'Document updated!' : 'Document added!';
13   - const upsert = {
14   - title: document.querySelector('[name="title"]').value.trim(),
15   - body: document.querySelector('[name="body"]').value.trim(),
16   - };
17   -
18   - if (doc && doc._id) upsert._id = doc._id;
19   -
20   - upsertDocument.call(upsert, (error, response) => {
21   - if (error) {
22   - Bert.alert(error.reason, 'danger');
23   - } else {
24   - component.documentEditorForm.reset();
25   - Bert.alert(confirmation, 'success');
26   - browserHistory.push(`/documents/${response.insertedId || doc._id}`);
27   - }
28   - });
29   -};
30   -
31   -const validate = () => {
32   - $(component.documentEditorForm).validate({
33   - rules: {
34   - title: {
35   - required: true,
36   - },
37   - body: {
38   - required: true,
39   - },
40   - },
41   - messages: {
42   - title: {
43   - required: 'Need a title in here, Seuss.',
44   - },
45   - body: {
46   - required: 'This thneeds a body, please.',
47   - },
48   - },
49   - submitHandler() { handleUpsert(); },
50   - });
51   -};
52   -
53   -export default function documentEditor(options) {
54   - component = options.component;
55   - validate();
56   -}
imports/modules/rate-limit.js
... ... @@ -1,18 +0,0 @@
1   -import { Meteor } from 'meteor/meteor';
2   -import { DDPRateLimiter } from 'meteor/ddp-rate-limiter';
3   -import { _ } from 'meteor/underscore';
4   -
5   -const fetchMethodNames = methods => _.pluck(methods, 'name');
6   -
7   -const assignLimits = ({ methods, limit, timeRange }) => {
8   - const methodNames = fetchMethodNames(methods);
9   -
10   - if (Meteor.isServer) {
11   - DDPRateLimiter.addRule({
12   - name(name) { return _.contains(methodNames, name); },
13   - connectionId() { return true; },
14   - }, limit, timeRange);
15   - }
16   -};
17   -
18   -export default function rateLimit(options) { return assignLimits(options); }
imports/server/accounts.js
... ... @@ -0,0 +1,9 @@
  1 +import '/imports/server/emails/config';
  2 +import './accounts/email-templates';
  3 +import './accounts/verifyEmail';
  4 +import './accounts/login';
  5 +import './accounts/resetPassword';
  6 +import '/imports/server/emails/invite';
  7 +import '/imports/server/pages/verifyEmail';
  8 +
  9 +import './accounts/creation';
... ...
imports/server/accounts/creation.js
... ... @@ -2,22 +2,32 @@ import _ from &#39;lodash&#39;;
2 2 import { Accounts } from 'meteor/accounts-base';
3 3 import { SimpleSchema } from 'meteor/aldeed:simple-schema';
4 4 import { ValidatedMethod } from 'meteor/mdg:validated-method';
  5 +
5 6 import { Orgs } from '/imports/collections/orgs/index';
6 7 import { Users } from '/imports/collections/users/index';
7 8  
8 9  
9   -
10 10 Accounts.validateNewUser((user) => {
11 11 return !!user;
12 12 });
13 13  
14 14 Accounts.onCreateUser((options, user) => {
15   - _.assign(user, {
16   - firstName: options.profile.firstName,
17   - lastName: options.profile.lastName,
18   - phones: [],
19   - identities: [],
20   - createdAt: new Date().getTime(),
21   - });
22   - return user;
  15 + if(options.orgSlug){
  16 + orgId = Orgs.insert({
  17 + slug: options.orgSlug,
  18 + name: options.orgName,
  19 + setup: 1,
  20 + users: [{
  21 + userId: user._id,
  22 + role: Users.roles.ADMIN,
  23 + }],
  24 + });
  25 + }
  26 + _.assign(user, {
  27 + role: Users.roles.ADMIN,
  28 + orgId: orgId,
  29 + firstName: options.profile.firstName,
  30 + lastName: options.profile.lastName,
  31 + });
  32 + return user;
23 33 });
... ...
imports/server/accounts/email-templates.js
... ... @@ -0,0 +1,25 @@
  1 +import { Accounts } from 'meteor/accounts-base';
  2 +
  3 +const name = 'Application Name';
  4 +const email = '<support@application.com>';
  5 +const from = `${name} ${email}`;
  6 +const emailTemplates = Accounts.emailTemplates;
  7 +
  8 +emailTemplates.siteName = name;
  9 +emailTemplates.from = from;
  10 +
  11 +emailTemplates.resetPassword = {
  12 + subject() {
  13 + return `[${name}] Reset Your Password`;
  14 + },
  15 + text(user, url) {
  16 + const userEmail = user.emails[0].address;
  17 + const urlWithoutHash = url.replace('#/', '');
  18 +
  19 + return `A password reset has been requested for the account related to this
  20 + address (${userEmail}). To reset the password, visit the following link:
  21 + \n\n${urlWithoutHash}\n\n If you did not request this reset, please ignore
  22 + this email. If you feel something is wrong, please contact our support team:
  23 + ${email}.`;
  24 + },
  25 +};
... ...
imports/server/accounts/login.js
... ... @@ -0,0 +1,6 @@
  1 +Accounts.validateLoginAttempt(function (options) {
  2 + Meteor.call('method', true, function (error, result) {
  3 + // do nothing
  4 + });
  5 + return true
  6 +});
... ...
imports/server/accounts/resetPassword.js
... ... @@ -0,0 +1,31 @@
  1 +import _ from 'lodash';
  2 +import { Accounts } from 'meteor/accounts-base';
  3 +
  4 +
  5 +Accounts.emailTemplates.resetPassword = {
  6 + subject() {
  7 + return '[Blok8] Reset Your Password';
  8 + },
  9 + text(user, url) {
  10 + const userEmail = user.emails[0].address;
  11 + const theUrl = Meteor.absoluteUrl(`?enter=reset&token=${_.last(url.split('/'))}`);
  12 + // const theUrl = decodeURI(`\u003D`);
  13 +
  14 + return (
  15 +`
  16 +Hello, ${user.firstName}!
  17 +
  18 +
  19 +A password reset has been requested for the account related to this address.
  20 +To reset the password, visit the following link:
  21 +
  22 +${theUrl}
  23 +
  24 +If you did not request this reset, please ignore this email.
  25 +
  26 +`
  27 + );
  28 + },
  29 +};
  30 +
  31 +
... ...
imports/server/accounts/verifyEmail.js
... ... @@ -0,0 +1,29 @@
  1 +import _ from 'lodash';
  2 +import { Accounts } from 'meteor/accounts-base';
  3 +
  4 +Accounts.config({
  5 + sendVerificationEmail: true
  6 +});
  7 +
  8 +Accounts.emailTemplates.verifyEmail = {
  9 + subject() {
  10 + return '[YoungDesk] Verify Your Email Address';
  11 + },
  12 + text(user, url) {
  13 + const theUrl = Meteor.absoluteUrl(`back/verifyEmail/${_.last(url.split('/'))}`);
  14 +
  15 + return (
  16 +`
  17 +Hello, ${user.firstName}!
  18 +
  19 +
  20 +To verify your email address, visit the following link:
  21 +
  22 +${theUrl}
  23 +
  24 +If you did not request this verification, please ignore this email.
  25 +
  26 +`
  27 + );
  28 + },
  29 +};
... ...
imports/server/browser-policy.js
... ... @@ -0,0 +1,14 @@
  1 +import { BrowserPolicy } from 'meteor/browser-policy-common';
  2 +// e.g., BrowserPolicy.content.allowOriginForAll( 's3.amazonaws.com' );
  3 +BrowserPolicy.content.allowFontOrigin("data:");
  4 +
  5 +BrowserPolicy.content.disallowEval();
  6 +
  7 +BrowserPolicy.framing.allowAll();
  8 +BrowserPolicy.content.allowInlineScripts();
  9 +BrowserPolicy.content.allowInlineStyles();
  10 +BrowserPolicy.content.allowSameOriginForAll();
  11 +BrowserPolicy.content.allowDataUrlForAll();
  12 +BrowserPolicy.content.allowOriginForAll('*');
  13 +BrowserPolicy.content.allowOriginForAll('http://*');
  14 +BrowserPolicy.content.allowOriginForAll('https://*');
... ...
imports/server/collections.js
... ... @@ -0,0 +1,4 @@
  1 +import '/imports/collections/orgs/publications'
  2 +import '/imports/collections/orgs/methods';
  3 +
  4 +import '/imports/collections/users/publications';
... ...
imports/server/emails/config.js
... ... @@ -0,0 +1,10 @@
  1 +// process.env.MAIL_URL = "smtp://postmaster%40sandboxdac306638463443cb5d8adc4380ea78d.mailgun.org:9a2131ff0360b37dae90faca340e6b8f@smtp.mailgun.org:587";
  2 +// process.env.MAIL_URL = "smtp://block8:cfxJfbe7xI7qfTgE2DB22g@smtp.mandrillapp.com:587"; /* Test Mode */
  3 +
  4 +import { Accounts } from 'meteor/accounts-base';
  5 +
  6 +if(Meteor.settings.public.environment === 'production') {
  7 + process.env.MAIL_URL = "smtp://block8:Ih-DuTSHlO-bdiPE7FBl3Q@smtp.mandrillapp.com:587";
  8 +}
  9 +Accounts.emailTemplates.siteName = 'YoungDesk';
  10 +Accounts.emailTemplates.from = 'YoungDesk <support@youngdesk.com>';
... ...
imports/server/emails/invite.js
... ... @@ -0,0 +1,166 @@
  1 +import { Email } from 'meteor/email'
  2 +import {moment} from 'meteor/momentjs:moment'
  3 +moment.locale('en-au');
  4 +
  5 +export const sendInviteEmail = ({email, firstName, orgName, token}) => {
  6 + const url = Meteor.absoluteUrl(`invite/${token}`);
  7 + const text = `
  8 +Hello, ${firstName}!
  9 +
  10 +You have been invited to join the ${orgName} on Blok8 wallet.
  11 +To join, use the following link:
  12 +
  13 +${url}
  14 +`;
  15 +
  16 + Email.send({
  17 + from: 'Blok 8 <support@mystake.io>',
  18 + to: email,
  19 + subject: `[Blok8] Invitation to ${orgName}`,
  20 + text: text,
  21 + });
  22 +
  23 +};
  24 +
  25 +export const sendChangeSharePriceEmail = ({orgName, firstName, lastName, price, email}) => {
  26 + const text = `
  27 +Hi ${firstName},
  28 +
  29 +The current share price has been changed to $A${price} for the company ${orgName}.
  30 +`;
  31 +
  32 + Email.send({
  33 + from: 'Blok 8 <support@mystake.io>',
  34 + to: email,
  35 + subject: `[Blok8] Share price has changed for ${orgName}.`,
  36 + text: text,
  37 + });
  38 +
  39 +};
  40 +
  41 +export const sendChangeNameEmail = ({firstName, lastName, email}) => {
  42 +
  43 + console.log(firstName);
  44 + console.log(lastName);
  45 + console.log(email);
  46 + const text = `
  47 +Hi ${firstName},
  48 +
  49 +Your name has been changed to "${firstName} ${lastName}".
  50 +
  51 +If you did not change your name, please reset your password.
  52 +`;
  53 +
  54 + Email.send({
  55 + from: 'Blok 8 <support@mystake.io>',
  56 + to: email,
  57 + subject: `[Blok8] - Name Change`,
  58 + text: text,
  59 + });
  60 +
  61 +};
  62 +
  63 +export const sendChangePasswordEmail = ({firstName, lastName, email}) => {
  64 +
  65 + console.log(firstName);
  66 + console.log(lastName);
  67 + console.log(email);
  68 + let date = moment().format('LLL');
  69 + const text = `
  70 +Hi ${firstName},
  71 +
  72 +Your password was recently changed on ${date}.
  73 +
  74 +If you did not change your password, please reset your password immediately.
  75 +`;
  76 +
  77 + Email.send({
  78 + from: 'Blok 8 <support@mystake.io>',
  79 + to: email,
  80 + subject: `[Blok8] - Password Change`,
  81 + text: text,
  82 + });
  83 +
  84 +};
  85 +
  86 +export const sendChangeEmail = ({firstName, lastName, email, newEmail}) => {
  87 +
  88 + console.log(firstName);
  89 + console.log(lastName);
  90 + console.log(email);
  91 + let date = moment().format('LLL');
  92 + const text = `
  93 +Hi ${firstName},
  94 +
  95 +Your email address was recently changed on ${newEmail} on ${date}.
  96 +
  97 +If you did not change your email address, please contact us immediately by responding to this email.
  98 +`;
  99 +
  100 + Email.send({
  101 + from: 'Blok 8 <support@mystake.io>',
  102 + to: email,
  103 + subject: `[Blok8] - Email Change`,
  104 + text: text,
  105 + });
  106 +
  107 +};
  108 +
  109 +
  110 +export const sendIssueSharesEmail = ({orgName, firstName, lastName, quantity, purchasePrice, shareClass,email}) => {
  111 + console.log(quantity);
  112 + console.log(purchasePrice);
  113 + console.log(shareClass);
  114 + console.log(email);
  115 + const text = `
  116 +Hi ${firstName},
  117 +
  118 +${quantity} ${shareClass} shares in ${orgName} have been issued to you for A$${purchasePrice} in total.
  119 +
  120 +`;
  121 +
  122 + Email.send({
  123 + from: 'Blok 8 <support@mystake.io>',
  124 + to: email,
  125 + subject: `[Blok8] New shares have been issued to you in ${orgName}.`,
  126 + text: text,
  127 + });
  128 +
  129 +};
  130 +
  131 +export const sendNewAnnouncementsEmail = ({orgId, orgName, createdByUser, title, description, uniqueUrl, firstName, lastName, email}) => {
  132 + const text = `
  133 +Hi ${firstName},
  134 +
  135 +A new announcement has been issued by the company secretary!
  136 +
  137 +Title: ${title}.
  138 +
  139 +`;
  140 +
  141 + Email.send({
  142 + from: 'Blok 8 <support@mystake.io>',
  143 + to: email,
  144 + subject: `[Blok8] ${orgName} - New Announcement`,
  145 + text: text,
  146 + });
  147 +
  148 +};
  149 +
  150 +
  151 +export const sendIssueDividendsEmail = ({ orgId, orgName, dividend, firstName, lastName, email }) => {
  152 + const text = `
  153 +Hi ${firstName}!
  154 +
  155 +A new dividend has been issued at $A${dividend} per ordinary share for the company "${orgName}".
  156 +
  157 +`;
  158 +
  159 + Email.send({
  160 + from: 'Blok 8 <support@mystake.io>',
  161 + to: email,
  162 + subject: `[Blok8] A new dividend has been issued by ${orgName}`,
  163 + text: text,
  164 + });
  165 +
  166 +};
... ...
imports/server/fixtures.js
... ... @@ -0,0 +1,23 @@
  1 +import { Meteor } from 'meteor/meteor';
  2 +import { Roles } from 'meteor/alanning:roles';
  3 +import { Accounts } from 'meteor/accounts-base';
  4 +
  5 +if (!Meteor.isProduction) {
  6 + const users = [{
  7 + email: 'admin@admin.com',
  8 + password: 'password',
  9 + profile: {
  10 + name: { first: 'Carl', last: 'Winslow' },
  11 + },
  12 + roles: ['admin'],
  13 + }];
  14 +
  15 + users.forEach(({ email, password, profile, roles }) => {
  16 + const userExists = Meteor.users.findOne({ 'emails.address': email });
  17 +
  18 + if (!userExists) {
  19 + const userId = Accounts.createUser({ email, password, profile });
  20 + Roles.addUsersToRoles(userId, roles);
  21 + }
  22 + });
  23 +}
... ...
imports/server/index.js
... ... @@ -0,0 +1,4 @@
  1 +import './accounts';
  2 +import './collections';
  3 +import './browser-policy';
  4 +import './fixtures';
... ...
imports/server/pages/verifyEmail.js
... ... @@ -0,0 +1,164 @@
  1 +import React from 'react';
  2 +import _ from 'lodash';
  3 +import { Users } from '/imports/collections/users/index';
  4 +
  5 +
  6 +
  7 +
  8 +const finishWithMessage = (res, message, redirect = "/") => {
  9 +
  10 + const output = `
  11 +<html>
  12 +<head>
  13 +<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/simple-line-icons/2.4.1/css/simple-line-icons.css">
  14 +<!-- Latest compiled and minified CSS -->
  15 +<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
  16 +<style type="text/css">
  17 +.congo-wrap .header {
  18 + background: #379AC7;
  19 + background: -webkit-linear-gradient(-90deg, #379AC7, #0460D9);
  20 + background: linear-gradient(180deg, #379AC7, #0460D9);
  21 + height: 80px;
  22 + padding: 11px 0;
  23 +}
  24 + .logo img{
  25 + width:170px;
  26 + }
  27 +.congo-wrap .header .container {
  28 + width: 100%;
  29 + padding:0 20px;
  30 +}
  31 +.congo-wrap{
  32 + background:#f6f7f7;
  33 + height:100%;
  34 +}
  35 +.widgetbox-congrats {
  36 + max-width: 720px;
  37 + margin: 0px auto;
  38 + background: #fff;
  39 + position: relative;
  40 + top: 20px;
  41 + text-align: center;
  42 + border: 1px solid #eee;
  43 + font-size: 20px;
  44 +}
  45 +
  46 +body {
  47 + margin: 0;
  48 + padding: 0;
  49 +}
  50 +
  51 +.widgetbox-congrats .congo-msg {
  52 + padding: 90px 0;
  53 + border-bottom: 1px solid #eee;
  54 +}
  55 +
  56 +.widgetbox-congrats.congo-msg h4 {margin: 0 !important;padding: 0 !important;}
  57 +
  58 +.text-blue {
  59 + color: #0059ce;
  60 +}
  61 +
  62 +.widgetbox-congrats .congo-msg h4 {
  63 + margin: 8px 0;
  64 + font-size: 24px;
  65 +}
  66 +
  67 +.widgetbox-congrats .congo-msg .icon {
  68 + color: #0F67C8;
  69 + font-size: 90px;
  70 + font-weight: 100;
  71 + margin-bottom: 20px;
  72 + display: block;
  73 +}
  74 +
  75 +.widgetbox-congrats .widgetbox-footer {
  76 + padding: 31px 10px;
  77 +}
  78 +
  79 +.widgetbox-congrats .widgetbox-footer .btn {
  80 + background: #0e89d3;
  81 + color: #fff;
  82 + padding: 10px 30px;
  83 + text-transform: uppercase;
  84 +}
  85 +</style>
  86 +
  87 +</head>
  88 +
  89 +<body>
  90 +<div class="congo-wrap">
  91 +<div class="header">
  92 + <div class="container">
  93 + <div class="logo">
  94 + <img src="/files/images/svg/logo--white.svg" altt=""/>
  95 + </div>
  96 + </div>
  97 +</div>
  98 + <div class="container">
  99 + <div class="widgetbox-congrats">
  100 + <div class="congo-msg">
  101 + <div class="text-blue">
  102 + <i class="icon icon-simple icon-check"></i>
  103 + <h4>Congrats!</h4>
  104 + </div>
  105 + <p> ${message}</p>
  106 + </div>
  107 + <div class="widgetbox-footer">
  108 + <a href="${redirect}" class="btn btn-lg btn-prmary">Continue</a>
  109 + </div>
  110 +
  111 + </div>
  112 + </div>
  113 +</div>
  114 +
  115 +</html>
  116 + `;
  117 +
  118 + res.writeHead(200, {
  119 + 'Content-Length': output.length,
  120 + 'Content-Type': 'text/html',
  121 + });
  122 + res.end(output);
  123 +};
  124 +
  125 +
  126 +
  127 +Picker.route('/back/verifyEmail/:token', function(params, req, res, next) {
  128 +
  129 + const user = Users.findOne({'services.email.verificationTokens.token': params.token});
  130 + if(!user) return finishWithMessage(res, 'Invalid or outdated token.');
  131 +
  132 + const token = _.find(user.services.email.verificationTokens, x => x.token === params.token);
  133 + if(new Date().getTime() - token.when.getTime() > 2 * 24 * 60 * 60 * 1000) return finishWithMessage(res, 'Invalid or outdated token.');
  134 +
  135 + finishWithMessage(res, 'Email has been verified.',"/");
  136 + const idx = _.findIndex(user.emails, x => x.address === token.address);
  137 +
  138 + Users.update({_id: user._id}, {$set: {
  139 + [`emails.${idx}.verified`]: true,
  140 + }});
  141 +
  142 +
  143 +
  144 +
  145 +// "services": {
  146 +// "password": {
  147 +// "bcrypt": "$2a$10$FqOvMve/MonERrLIOCJbruvS9iHoz5ixknGfm/ZAiOt9EiF43W4Z6"
  148 +// },
  149 +// "email": {
  150 +// "verificationTokens": [
  151 +// {
  152 +// "token": "05T-Ht_BzprDSAOVIYC-yUvDQ3Hp0wNym4eTx2g1VmI",
  153 +// "address": "krowa@druga.je",
  154 +// "when": new Date(1479502106631)
  155 +// }
  156 +// ]
  157 +// },
  158 +
  159 +
  160 +
  161 +
  162 +
  163 +
  164 +});
... ...
imports/startup/server/accounts/creation.js
... ... @@ -1,35 +0,0 @@
1   -import _ from 'lodash';
2   -import { Accounts } from 'meteor/accounts-base';
3   -import { SimpleSchema } from 'meteor/aldeed:simple-schema';
4   -import { ValidatedMethod } from 'meteor/mdg:validated-method';
5   -
6   -import { Orgs } from '/imports/collections/orgs/index';
7   -import { Users } from '/imports/collections/users/index';
8   -
9   -
10   -Accounts.validateNewUser((user) => {
11   - return !!user;
12   -});
13   -
14   -Accounts.onCreateUser((options, user) => {
15   - console.log(options);
16   - if(options.orgSlug){
17   - orgId = Orgs.insert({
18   - slug: options.orgSlug,
19   - name: options.orgName,
20   - setup: 1,
21   - users: [{
22   - userId: user._id,
23   - role: Users.roles.ADMIN,
24   - }],
25   - });
26   - }
27   - console.log(user);
28   - _.assign(user, {
29   - role: Users.roles.ADMIN,
30   - orgId: orgId,
31   - firstName: options.profile.firstName,
32   - lastName: options.profile.lastName,
33   - });
34   - return user;
35   -});
imports/startup/server/accounts/email-templates.js
... ... @@ -1,25 +0,0 @@
1   -import { Accounts } from 'meteor/accounts-base';
2   -
3   -const name = 'Application Name';
4   -const email = '<support@application.com>';
5   -const from = `${name} ${email}`;
6   -const emailTemplates = Accounts.emailTemplates;
7   -
8   -emailTemplates.siteName = name;
9   -emailTemplates.from = from;
10   -
11   -emailTemplates.resetPassword = {
12   - subject() {
13   - return `[${name}] Reset Your Password`;
14   - },
15   - text(user, url) {
16   - const userEmail = user.emails[0].address;
17   - const urlWithoutHash = url.replace('#/', '');
18   -
19   - return `A password reset has been requested for the account related to this
20   - address (${userEmail}). To reset the password, visit the following link:
21   - \n\n${urlWithoutHash}\n\n If you did not request this reset, please ignore
22   - this email. If you feel something is wrong, please contact our support team:
23   - ${email}.`;
24   - },
25   -};
imports/startup/server/accounts/resetPassword.js
... ... @@ -1,31 +0,0 @@
1   -import _ from 'lodash';
2   -import { Accounts } from 'meteor/accounts-base';
3   -
4   -
5   -Accounts.emailTemplates.resetPassword = {
6   - subject() {
7   - return '[Blok8] Reset Your Password';
8   - },
9   - text(user, url) {
10   - const userEmail = user.emails[0].address;
11   - const theUrl = Meteor.absoluteUrl(`?enter=reset&token=${_.last(url.split('/'))}`);
12   - // const theUrl = decodeURI(`\u003D`);
13   -
14   - return (
15   -`
16   -Hello, ${user.firstName}!
17   -
18   -
19   -A password reset has been requested for the account related to this address.
20   -To reset the password, visit the following link:
21   -
22   -${theUrl}
23   -
24   -If you did not request this reset, please ignore this email.
25   -
26   -`
27   - );
28   - },
29   -};
30   -
31   -
imports/startup/server/accounts/verifyEmail.js
... ... @@ -1,29 +0,0 @@
1   -import _ from 'lodash';
2   -import { Accounts } from 'meteor/accounts-base';
3   -
4   -Accounts.config({
5   - sendVerificationEmail: true
6   -});
7   -
8   -Accounts.emailTemplates.verifyEmail = {
9   - subject() {
10   - return '[Blok8] Verify Your Email Address';
11   - },
12   - text(user, url) {
13   - const theUrl = Meteor.absoluteUrl(`back/verifyEmail/${_.last(url.split('/'))}`);
14   -
15   - return (
16   -`
17   -Hello, ${user.firstName}!
18   -
19   -
20   -To verify your email address, visit the following link:
21   -
22   -${theUrl}
23   -
24   -If you did not request this verification, please ignore this email.
25   -
26   -`
27   - );
28   - },
29   -};
imports/startup/server/api.js
... ... @@ -1,7 +0,0 @@
1   -import '../../api/documents/methods.js';
2   -import '../../api/documents/server/publications.js';
3   -
4   -import '/imports/collections/orgs/publications'
5   -import '/imports/collections/orgs/methods';
6   -
7   -import '/imports/collections/users/publications';
imports/startup/server/browser-policy.js
... ... @@ -1,14 +0,0 @@
1   -import { BrowserPolicy } from 'meteor/browser-policy-common';
2   -// e.g., BrowserPolicy.content.allowOriginForAll( 's3.amazonaws.com' );
3   -BrowserPolicy.content.allowFontOrigin("data:");
4   -
5   -BrowserPolicy.content.disallowEval();
6   -
7   -BrowserPolicy.framing.allowAll();
8   -BrowserPolicy.content.allowInlineScripts();
9   -BrowserPolicy.content.allowInlineStyles();
10   -BrowserPolicy.content.allowSameOriginForAll();
11   -BrowserPolicy.content.allowDataUrlForAll();
12   -BrowserPolicy.content.allowOriginForAll('*');
13   -BrowserPolicy.content.allowOriginForAll('http://*');
14   -BrowserPolicy.content.allowOriginForAll('https://*');
imports/startup/server/fixtures.js
... ... @@ -1,23 +0,0 @@
1   -import { Meteor } from 'meteor/meteor';
2   -import { Roles } from 'meteor/alanning:roles';
3   -import { Accounts } from 'meteor/accounts-base';
4   -
5   -if (!Meteor.isProduction) {
6   - const users = [{
7   - email: 'admin@admin.com',
8   - password: 'password',
9   - profile: {
10   - name: { first: 'Carl', last: 'Winslow' },
11   - },
12   - roles: ['admin'],
13   - }];
14   -
15   - users.forEach(({ email, password, profile, roles }) => {
16   - const userExists = Meteor.users.findOne({ 'emails.address': email });
17   -
18   - if (!userExists) {
19   - const userId = Accounts.createUser({ email, password, profile });
20   - Roles.addUsersToRoles(userId, roles);
21   - }
22   - });
23   -}
imports/startup/server/index.js
... ... @@ -1,5 +0,0 @@
1   -import './accounts/email-templates';
2   -import './accounts/creation';
3   -import './browser-policy';
4   -import './fixtures';
5   -import './api';
imports/ui/pages/Documents.js
... ... @@ -1,25 +0,0 @@
1   -import React from 'react';
2   -import { Link } from 'react-router';
3   -import { Row, Col, Button } from 'react-bootstrap';
4   -import DocumentsList from '../containers/DocumentsList.js';
5   -
6   -const Documents = () => (
7   - <div className="Documents">
8   - <Row>
9   - <Col xs={ 12 }>
10   - <div className="page-header clearfix">
11   - <h4 className="pull-left">Documents</h4>
12   - <Link to="/documents/new">
13   - <Button
14   - bsStyle="success"
15   - className="pull-right"
16   - >New Document</Button>
17   - </Link>
18   - </div>
19   - <DocumentsList />
20   - </Col>
21   - </Row>
22   - </div>
23   -);
24   -
25   -export default Documents;
imports/ui/pages/EditDocument.js
... ... @@ -1,15 +0,0 @@
1   -import React from 'react';
2   -import DocumentEditor from '../components/DocumentEditor.js';
3   -
4   -const EditDocument = ({ doc }) => (
5   - <div className="EditDocument">
6   - <h4 className="page-header">Editing "{ doc.title }"</h4>
7   - <DocumentEditor doc={ doc } />
8   - </div>
9   -);
10   -
11   -EditDocument.propTypes = {
12   - doc: React.PropTypes.object,
13   -};
14   -
15   -export default EditDocument;
imports/ui/pages/Login.js
... ... @@ -1,54 +0,0 @@
1   -import React from 'react';
2   -import { Link } from 'react-router';
3   -import { Row, Col, FormGroup, ControlLabel, FormControl, Button } from 'react-bootstrap';
4   -import handleLogin from '../../modules/login';
5   -
6   -export default class Login extends React.Component {
7   - componentDidMount() {
8   - handleLogin({ component: this });
9   - }
10   -
11   - handleSubmit(event) {
12   - event.preventDefault();
13   - }
14   -
15   - render() {
16   - return (
17   - <div className="Login">
18   - <Row>
19   - <Col xs={ 12 } sm={ 6 } md={ 4 }>
20   - <h4 className="page-header">Login</h4>
21   - <form
22   - ref={ form => (this.loginForm = form) }
23   - className="login"
24   - onSubmit={ this.handleSubmit }
25   - >
26   - <FormGroup>
27   - <ControlLabel>Email Address</ControlLabel>
28   - <FormControl
29   - type="email"
30   - ref="emailAddress"
31   - name="emailAddress"
32   - placeholder="Email Address"
33   - />
34   - </FormGroup>
35   - <FormGroup>
36   - <ControlLabel>
37   - <span className="pull-left">Password</span>
38   - <Link className="pull-right" to="/recover-password">Forgot Password?</Link>
39   - </ControlLabel>
40   - <FormControl
41   - type="password"
42   - ref="password"
43   - name="password"
44   - placeholder="Password"
45   - />
46   - </FormGroup>
47   - <Button type="submit" bsStyle="success">Login</Button>
48   - </form>
49   - </Col>
50   - </Row>
51   - </div>
52   - );
53   - }
54   -}
imports/ui/pages/NewDocument.js
... ... @@ -1,11 +0,0 @@
1   -import React from 'react';
2   -import DocumentEditor from '../components/DocumentEditor.js';
3   -
4   -const NewDocument = () => (
5   - <div className="NewDocument">
6   - <h4 className="page-header">New Document</h4>
7   - <DocumentEditor />
8   - </div>
9   -);
10   -
11   -export default NewDocument;
imports/ui/pages/RecoverPassword.js
... ... @@ -1,43 +0,0 @@
1   -import React from 'react';
2   -import { Row, Col, Alert, FormGroup, FormControl, Button } from 'react-bootstrap';
3   -import handleRecoverPassword from '../../modules/recover-password';
4   -
5   -export default class RecoverPassword extends React.Component {
6   - componentDidMount() {
7   - handleRecoverPassword({ component: this });
8   - }
9   -
10   - handleSubmit(event) {
11   - event.preventDefault();
12   - }
13   -
14   - render() {
15   - return (
16   - <div className="RecoverPassword">
17   - <Row>
18   - <Col xs={ 12 } sm={ 6 } md={ 4 }>
19   - <h4 className="page-header">Recover Password</h4>
20   - <Alert bsStyle="info">
21   - Enter your email address below to receive a link to reset your password.
22   - </Alert>
23   - <form
24   - ref={ form => (this.recoverPasswordForm = form) }
25   - className="recover-password"
26   - onSubmit={ this.handleSubmit }
27   - >
28   - <FormGroup>
29   - <FormControl
30   - type="email"
31   - ref="emailAddress"
32   - name="emailAddress"
33   - placeholder="Email Address"
34   - />
35   - </FormGroup>
36   - <Button type="submit" bsStyle="success">Recover Password</Button>
37   - </form>
38   - </Col>
39   - </Row>
40   - </div>
41   - );
42   - }
43   -}
imports/ui/pages/ResetPassword.js
... ... @@ -1,58 +0,0 @@
1   -import React from 'react';
2   -import { Row, Col, Alert, FormGroup, ControlLabel, FormControl, Button } from 'react-bootstrap';
3   -import handleResetPassword from '../../modules/reset-password';
4   -
5   -export default class ResetPassword extends React.Component {
6   - componentDidMount() {
7   - handleResetPassword({ component: this, token: this.props.params.token });
8   - }
9   -
10   - handleSubmit(event) {
11   - event.preventDefault();
12   - }
13   -
14   - render() {
15   - return (
16   - <div className="ResetPassword">
17   - <Row>
18   - <Col xs={ 12 } sm={ 6 } md={ 4 }>
19   - <h4 className="page-header">Reset Password</h4>
20   - <Alert bsStyle="info">
21   - To reset your password, enter a new one below. You will be logged in
22   - with your new password.
23   - </Alert>
24   - <form
25   - ref={ form => (this.resetPasswordForm = form) }
26   - className="reset-password"
27   - onSubmit={ this.handleSubmit }
28   - >
29   - <FormGroup>
30   - <ControlLabel>New Password</ControlLabel>
31   - <FormControl
32   - type="password"
33   - ref="newPassword"
34   - name="newPassword"
35   - placeholder="New Password"
36   - />
37   - </FormGroup>
38   - <FormGroup>
39   - <ControlLabel>Repeat New Password</ControlLabel>
40   - <FormControl
41   - type="password"
42   - ref="repeatNewPassword"
43   - name="repeatNewPassword"
44   - placeholder="Repeat New Password"
45   - />
46   - </FormGroup>
47   - <Button type="submit" bsStyle="success">Reset Password &amp; Login</Button>
48   - </form>
49   - </Col>
50   - </Row>
51   - </div>
52   - );
53   - }
54   -}
55   -
56   -ResetPassword.propTypes = {
57   - params: React.PropTypes.object,
58   -};
imports/ui/pages/ViewDocument.js
... ... @@ -1,43 +0,0 @@
1   -import React from 'react';
2   -import { ButtonToolbar, ButtonGroup, Button } from 'react-bootstrap';
3   -import { browserHistory } from 'react-router';
4   -import { Bert } from 'meteor/themeteorchef:bert';
5   -import { removeDocument } from '../../api/documents/methods.js';
6   -
7   -const handleEdit = (_id) => {
8   - browserHistory.push(`/documents/${_id}/edit`);
9   -}
10   -
11   -const handleRemove = (_id) => {
12   - if (confirm('Are you sure? This is permanent!')) {
13   - removeDocument.call({ _id }, (error) => {
14   - if (error) {
15   - Bert.alert(error.reason, 'danger');
16   - } else {
17   - Bert.alert('Document deleted!', 'success');
18   - browserHistory.push('/documents');
19   - }
20   - });
21   - }
22   -};
23   -
24   -const ViewDocument = ({ doc }) => (
25   - <div className="ViewDocument">
26   - <div className="page-header clearfix">
27   - <h4 className="pull-left">{ doc && doc.title }</h4>
28   - <ButtonToolbar className="pull-right">
29   - <ButtonGroup bsSize="small">
30   - <Button onClick={ () => handleEdit(doc._id) }>Edit</Button>
31   - <Button onClick={ () => handleRemove(doc._id) } className="text-danger">Delete</Button>
32   - </ButtonGroup>
33   - </ButtonToolbar>
34   - </div>
35   - { doc && doc.body }
36   - </div>
37   -);
38   -
39   -ViewDocument.propTypes = {
40   - doc: React.PropTypes.object,
41   -};
42   -
43   -export default ViewDocument;
1   -import '/imports/startup/server';
  1 +import '/imports/server/index';
... ...