├── .gitignore
├── LICENSE
├── Procfile
├── README.md
├── client
├── app-aot.ts
├── app.ts
├── index.html
├── loader.scss
├── modules
│ ├── 404
│ │ ├── 404-routing.module.ts
│ │ ├── 404.component.html
│ │ ├── 404.component.scss
│ │ ├── 404.component.ts
│ │ └── 404.module.ts
│ ├── app.component.html
│ ├── app.component.scss
│ ├── app.component.ts
│ ├── app.module.ts
│ ├── browser-app.module.ts
│ ├── core
│ │ ├── components
│ │ │ ├── footer
│ │ │ │ ├── footer.component.html
│ │ │ │ ├── footer.component.scss
│ │ │ │ └── footer.component.ts
│ │ │ └── header
│ │ │ │ ├── header.component.html
│ │ │ │ ├── header.component.scss
│ │ │ │ └── header.component.ts
│ │ ├── core-routing.module.ts
│ │ ├── core.component.html
│ │ ├── core.component.scss
│ │ ├── core.component.ts
│ │ ├── core.module.ts
│ │ └── services
│ │ │ ├── auth
│ │ │ ├── auth.service.ts
│ │ │ └── token-interceptor.service.ts
│ │ │ └── socketio
│ │ │ └── socketio.service.ts
│ ├── home
│ │ ├── home-routing.module.ts
│ │ ├── home.component.html
│ │ ├── home.component.scss
│ │ ├── home.component.ts
│ │ └── home.module.ts
│ ├── server-app.module.ts
│ ├── shared
│ │ └── shared.module.ts
│ ├── transfer-state
│ │ ├── browser-transfer-state.module.ts
│ │ ├── server-transfer-state.module.ts
│ │ ├── server-transfer-state.ts
│ │ └── transfer-state.ts
│ └── user-profile
│ │ ├── user-profile-routing.module.ts
│ │ ├── user-profile.component.html
│ │ ├── user-profile.component.scss
│ │ ├── user-profile.component.ts
│ │ └── user-profile.module.ts
├── polyfills.ts
├── redux
│ ├── actions
│ │ ├── error
│ │ │ ├── errorHandler.actions.spec.ts
│ │ │ └── errorHandler.actions.ts
│ │ ├── seo
│ │ │ └── seo.actions.ts
│ │ ├── user
│ │ │ ├── user.actions.spec.ts
│ │ │ └── user.actions.ts
│ │ └── userForm
│ │ │ ├── userForm.actions.spec.ts
│ │ │ └── userForm.actions.ts
│ ├── redux.module.ts
│ └── store
│ │ ├── errorHandler
│ │ ├── errorHandler.initial-state.ts
│ │ ├── errorHandler.reducer.spec.ts
│ │ ├── errorHandler.reducer.ts
│ │ ├── errorHandler.transformers.ts
│ │ ├── errorHandler.types.ts
│ │ └── index.ts
│ │ ├── index.ts
│ │ ├── user
│ │ ├── index.ts
│ │ ├── user.initial-state.ts
│ │ ├── user.reducer.spec.ts
│ │ ├── user.reducer.ts
│ │ ├── user.transformers.ts
│ │ └── user.types.ts
│ │ └── userForm
│ │ ├── index.ts
│ │ ├── userForm.initial-state.ts
│ │ ├── userForm.reducer.spec.ts
│ │ ├── userForm.reducer.ts
│ │ ├── userForm.transformers.ts
│ │ └── userForm.types.ts
├── styles.scss
└── vendor.ts
├── config
├── env
│ ├── default.ts
│ ├── development.ts
│ ├── production.ts
│ └── test.ts
├── helpers.js
├── index.ts
├── other
│ ├── .sass-lint.yml
│ ├── generate-ssl-certs.sh
│ └── tslint.json
├── scripts.js
├── test-libs
│ ├── karma-test-shim.js
│ ├── karma.config.js
│ ├── protractor.config.js
│ └── server.test.js
└── webpack
│ ├── webpack.client.js
│ ├── webpack.common.js
│ └── webpack.server.js
├── e2e
├── app.e2e-spec.js
└── header.e2e-spec.js
├── package-lock.json
├── package.json
├── public
├── assets
│ ├── favicon.png
│ ├── footer.jpg
│ ├── goatlogo.svg
│ ├── loader
│ │ ├── fire-1.png
│ │ ├── fire-2.png
│ │ ├── space-goat.png
│ │ ├── star.png
│ │ └── star.svg
│ └── octocat.png
└── fonts
│ └── Fredoka_One.woff2
├── server
├── cassandra-db
│ ├── api
│ │ └── user
│ │ │ ├── prepared.statements.ts
│ │ │ ├── user.controller.ts
│ │ │ ├── user.integration.ts
│ │ │ ├── user.model.ts
│ │ │ ├── user.router.ts
│ │ │ └── user.spec.ts
│ ├── auth
│ │ ├── auth.router.ts
│ │ ├── auth.service.ts
│ │ └── local
│ │ │ ├── local.passport.ts
│ │ │ └── local.router.ts
│ ├── db.model.ts
│ ├── index.ts
│ ├── prepared.statements.ts
│ └── seed.ts
├── db-connect.ts
├── express.ts
├── mongo-db
│ ├── api
│ │ └── user
│ │ │ ├── user.controller.ts
│ │ │ ├── user.integration.ts
│ │ │ ├── user.model.ts
│ │ │ ├── user.router.ts
│ │ │ └── user.spec.ts
│ ├── auth
│ │ ├── auth.router.ts
│ │ ├── auth.service.ts
│ │ └── local
│ │ │ ├── local.passport.ts
│ │ │ └── local.router.ts
│ ├── index.ts
│ └── seed.ts
├── routes.ts
├── server.ts
├── socketio.ts
└── sql-db
│ ├── api
│ └── user
│ │ ├── user.controller.ts
│ │ ├── user.integration.ts
│ │ ├── user.model.ts
│ │ ├── user.router.ts
│ │ └── user.spec.ts
│ ├── auth
│ ├── auth.router.ts
│ ├── auth.service.ts
│ └── local
│ │ ├── local.passport.ts
│ │ └── local.router.ts
│ ├── index.ts
│ └── seed.ts
├── tsconfig-aot.json
├── tsconfig.browser.json
├── tsconfig.json
├── tsconfig.server.json
└── webpack.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # Node build artifacts
2 | node_modules
3 | npm-debug*
4 |
5 | # Local development
6 | *.env
7 | *.dev
8 | .DS_Store
9 | dist
10 | tmp
11 |
12 | ngc-aot
13 | *.ngfactory*
14 | *.shim.ts
15 | *.js.map
16 |
17 | # Docker
18 | Dockerfile
19 | docker-compose.yml
20 |
21 | # certificates
22 | server/sslcerts
23 |
24 | # TypeScript transpiled files
25 | client/**/**/**/*.js
26 |
27 | # CSS files
28 | **/*.css
29 | client/**/**/**/*.css
30 |
31 | # Typings folder
32 | typings
33 |
34 | # e2e test output
35 | config/sys/_test-output
36 |
37 | visualProject.njsproj
38 | .vs
39 |
40 | debug.log
41 | .com*
42 | .org*
43 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 JT
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: node node_modules/gulp/bin/gulp build
2 |
3 |
--------------------------------------------------------------------------------
/client/app-aot.ts:
--------------------------------------------------------------------------------
1 | //The browser platform with a compiler, used for Just in Time loading.
2 | //JIT means Angular compiles the application in the browser and then launches the app
3 | import { platformBrowser } from '@angular/platform-browser';
4 |
5 | //imports the AppModule which is the root module that bootstraps app.component.ts
6 | import { AppModuleNgFactory } from '../ngc-aot/client/modules/app.module.ngfactory';
7 | import { enableProdMode } from '@angular/core';
8 | enableProdMode();
9 |
10 | // Compile and launch the module
11 | platformBrowser().bootstrapModuleFactory(AppModuleNgFactory);
12 |
--------------------------------------------------------------------------------
/client/app.ts:
--------------------------------------------------------------------------------
1 | //The browser platform with a compiler, used for Just in Time loading.
2 | //JIT means Angular compiles the application in the browser and then launches the app
3 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
4 | import { enableProdMode } from '@angular/core';
5 |
6 | //imports the AppModule which is the root module that bootstraps app.component.ts
7 | import { AppModule } from './modules/app.module';
8 |
9 | if (process.env.ENV === 'production') {
10 | enableProdMode();
11 | }
12 |
13 | // Compile and launch the module
14 | platformBrowserDynamic().bootstrapModule(AppModule);
15 |
--------------------------------------------------------------------------------
/client/modules/404/404-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { RouterModule } from '@angular/router';
3 |
4 | import { Four0FourComponent } from './404.component';
5 |
6 | @NgModule({
7 | imports: [RouterModule.forChild([
8 | { path: 'PageNotFound', component: Four0FourComponent }
9 | ])],
10 | exports: [RouterModule]
11 | })
12 | export class Four0FourRoutingModule {}
--------------------------------------------------------------------------------
/client/modules/404/404.component.html:
--------------------------------------------------------------------------------
1 |
Page not found
--------------------------------------------------------------------------------
/client/modules/404/404.component.scss:
--------------------------------------------------------------------------------
1 | :host {
2 | position: relative;
3 | display: block;
4 | width: 100%;
5 | text-align: center;
6 | }
7 |
8 | h1 {
9 | position: fixed;
10 | top: 40%;
11 | left: 0;
12 | right:0;
13 | font-size: 50px;
14 | }
--------------------------------------------------------------------------------
/client/modules/404/404.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, ChangeDetectionStrategy } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'four0four-section',
5 | templateUrl: './404.component.html',
6 | styleUrls: ['./404.component.css'],
7 | changeDetection: ChangeDetectionStrategy.OnPush
8 | })
9 |
10 | export class Four0FourComponent { }
11 |
--------------------------------------------------------------------------------
/client/modules/404/404.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { Four0FourRoutingModule } from './404-routing.module';
4 |
5 | import { Four0FourComponent } from './404.component';
6 |
7 | @NgModule({
8 | imports: [CommonModule, Four0FourRoutingModule],
9 | declarations: [Four0FourComponent]
10 | })
11 |
12 | export class Four0FourModule {
13 |
14 | }
--------------------------------------------------------------------------------
/client/modules/app.component.html:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/client/modules/app.component.scss:
--------------------------------------------------------------------------------
1 | :host{
2 | position: relative;
3 | display: block;
4 | overflow-y: hidden;
5 | overflow-x: hidden;
6 |
7 | }
8 |
9 | header {
10 | position: fixed;
11 | top: 0;
12 | left: 0;
13 | right: 0;
14 | z-index: 10000;
15 | }
16 |
17 | .error-card {
18 | display: none;
19 | position: fixed;
20 | bottom: 10%;
21 | left: 5%;
22 | width: 350px;
23 | transform: translateY(400px);
24 | }
25 |
26 | .error-content {
27 | padding: 15px;
28 | font-size: medium;
29 | font-family: Roboto, "Helvetica Neue", sans-serif;
30 | }
31 |
32 | .user-sign button:hover {
33 | cursor: pointer;
34 | }
35 |
36 | .reg-card {
37 | will-change: transform;
38 | display: none;
39 | position: fixed;
40 | width: 400px;
41 | margin-left: -200px;
42 | top: 20%;
43 | left: 50%;
44 | z-index: 10000;
45 | padding: 0;
46 | }
47 |
48 | .reg-card#reg {
49 | top: 15%;
50 | }
51 |
52 | .reg-content {
53 | padding: 10px;
54 | }
55 |
56 | .reg-content button {
57 | margin-right: 2px;
58 | }
59 | .reg-content #login-btn,
60 | .reg-content #reg-btn {
61 | float: right;
62 | }
63 |
64 |
65 |
66 | @media (max-width: 800px) {
67 | .error-card {
68 | bottom: 0;
69 | left: 0;
70 | right: 0;
71 | width: auto;
72 | transform: translateY(-400px);
73 | }
74 |
75 | .reg-card {
76 | width: auto;
77 | margin: 0;
78 | top: 0;
79 | left: 0;
80 | right: 0;
81 | }
82 | .reg-card#reg {
83 | top: 0;
84 | }
85 | }
--------------------------------------------------------------------------------
/client/modules/app.component.ts:
--------------------------------------------------------------------------------
1 | /*
2 | =================================================================================================================================
3 | -- Bootstrapping component ------------------------------------------------------------------------------------------------------
4 | =================================================================================================================================
5 | ** According to Angular best practices the App component should be used for bootstrapping the application. **
6 | ** This component gets bootstrapped through app.module.ts, the magic occurs in the @NgModule decorater's bootstrap property, **
7 | ** we set that value to the AppComponent class defined in this component **
8 | ** then the app.module.ts gets invoked in the main.ts bootstrap method. **
9 | =================================================================================================================================
10 | */
11 |
12 |
13 | //main imports
14 | import { Component, ChangeDetectionStrategy } from '@angular/core';
15 |
16 | import { select } from '@angular-redux/store';
17 | import { Observable } from 'rxjs/Observable';
18 |
19 | //decorator
20 | @Component({
21 | selector: 'my-app',
22 | templateUrl: 'app.component.html',
23 | styleUrls: ['app.component.css'],
24 | changeDetection: ChangeDetectionStrategy.OnPush
25 | })
26 |
27 | //the main app component which will act as the parent component to all other components in the app.
28 | export class AppComponent {
29 | //the @select() decorator is from NgRedux.
30 | //GOATstack embraces the immutible paradigm, and has a redux store which contains the applications state which can be found in root/client/redux
31 | //you can read more about Redux here: https://github.com/angular-redux/ng2-redux
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/client/modules/app.module.ts:
--------------------------------------------------------------------------------
1 | /*
2 | ==================================================================================
3 | -- Root Module -------------------------------------------------------------------
4 | ==================================================================================
5 | ** Any assets included in this file will be attached **
6 | ** to the global scope of the application. **
7 | ** **
8 | ** The Root Module has two main purposes **
9 | ** 1) It tells Angular about all the apps dependencies **
10 | ** so Angular can build the application tree **
11 | ** 2) It tells Angular how to bootstrap the app **
12 | ** **
13 | ** Find out more here: https://angular.io/docs/ts/latest/guide/appmodule.html **
14 | ----------------------------------------------------------------------------------
15 | */
16 |
17 | /*
18 | -------------------------------------------------------------------
19 | Main component which gets bootstrapped
20 | -------------------------------------------------------------------
21 | ** Named AppComponent in compliance with Angular best practices **
22 | */
23 | import { AppComponent } from './app.component';
24 |
25 | /*
26 | --------------------------------------------------
27 | Modules
28 | --------------------------------------------------
29 | ** other necessary modules for this app
30 | */
31 | import { NgModule } from '@angular/core';
32 | import { BrowserModule } from '@angular/platform-browser';
33 | import { ReduxModule } from '../redux/redux.module';
34 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
35 |
36 | import { CoreModule } from './core/core.module';
37 |
38 | /*
39 | --------------------------------------------------
40 | NgModule
41 | --------------------------------------------------
42 | ** decorator which packages all resources imported above for the app
43 | ** without this decorator Angular cannot use any of those above assets
44 | ** read more here: https://angular.io/docs/ts/latest/guide/ngmodule.html
45 | */
46 | @NgModule({
47 | //imports: this object imports helper modules which are children in the module tree
48 | imports: [
49 | BrowserModule,
50 | ReduxModule,
51 | CoreModule,
52 | BrowserAnimationsModule
53 | ],
54 | //declarations: this object imports all child components which are used in this module
55 | declarations: [ AppComponent ],
56 | //bootstrap: identifies which component is supposed to be bootstrapped
57 | bootstrap: [ AppComponent ]
58 | })
59 |
60 | //by convention the root module is called AppModule as stated in the Angular2 docs
61 | //we call AppModule in app.ts to bootstrap the application which points to the AppComponent defined in @NgModule
62 | export class AppModule {
63 |
64 | }
--------------------------------------------------------------------------------
/client/modules/browser-app.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { BrowserModule } from '@angular/platform-browser';
3 | import { AppComponent } from './app.component';
4 | import { AppModule } from './app.module';
5 | import { BrowserTransferStateModule } from './transfer-state/browser-transfer-state.module';
6 |
7 | @NgModule({
8 | bootstrap: [ AppComponent ],
9 | imports: [
10 | BrowserModule.withServerTransition({
11 | appId: 'my-app'
12 | }),
13 | BrowserTransferStateModule,
14 | AppModule
15 | ]
16 | })
17 | export class BrowserAppModule {}
--------------------------------------------------------------------------------
/client/modules/core/components/footer/footer.component.html:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/client/modules/core/components/footer/footer.component.scss:
--------------------------------------------------------------------------------
1 | footer {
2 | position: fixed;
3 | bottom: 0;
4 | right: 0;
5 | left: 0;
6 | height: 25px;
7 | background-image: url("/public/assets/footer.jpg");
8 | }
9 |
10 | p {
11 | color: white;
12 | text-decoration: none;
13 | display: inline-block;
14 | line-height: 25px;
15 | }
16 |
17 | .octocat {
18 | display: inline-block;
19 | float: left;
20 | height: 25px;
21 | width: auto;
22 | }
23 |
24 | .github p {
25 | margin-left: 3px;
26 | }
27 |
28 | .devs:hover p,
29 | .github:hover p {
30 | text-decoration: underline;
31 | }
32 |
33 | .devs {
34 | float: right;
35 | margin-right: 5px;
36 | }
37 |
--------------------------------------------------------------------------------
/client/modules/core/components/footer/footer.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, ChangeDetectionStrategy } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'footer-section',
5 | templateUrl: './footer.component.html',
6 | styleUrls: ['./footer.component.css'],
7 | changeDetection: ChangeDetectionStrategy.OnPush
8 | })
9 |
10 | export class FooterComponent { }
11 |
--------------------------------------------------------------------------------
/client/modules/core/components/header/header.component.html:
--------------------------------------------------------------------------------
1 |
2 |

3 |
GOATstack
4 |
5 |
17 |
20 |
--------------------------------------------------------------------------------
/client/modules/core/components/header/header.component.scss:
--------------------------------------------------------------------------------
1 | .router-links a.active {
2 | color: orange;
3 | }
4 |
5 | :host {
6 | position: absolute;
7 | top: 0;
8 | left: 0;
9 | right: 0;
10 | margin: 23px 2.5%;
11 | }
12 |
13 | a:hover {
14 | cursor: pointer;
15 | }
16 |
17 | .app-title {
18 | width: 275px;
19 | height: 60px;
20 | float: left;
21 | }
22 |
23 | .app-title h1 {
24 | padding: 10px;
25 | text-align: center;
26 | vertical-align: top;
27 | font-size: 32px;
28 | color: black;
29 | display: inline-block;
30 | }
31 |
32 | .menu-container {
33 | width: -webkit-calc(100% - 275px);
34 | width: -moz-calc(100% - 275px);
35 | width: calc(100% - 275px);
36 | float: left;
37 | margin-top: 11px;
38 | }
39 | .menu-container.menu-mobile {
40 | position: absolute;
41 | right: 0;
42 | top: 80%;
43 | width: auto;
44 | }
45 |
46 | .router-links a {
47 | background-color: #2196f3;
48 | display: inline-block;
49 | text-align: center;
50 | padding: 0 5px;
51 | min-height: 35px;
52 | min-width: 88px;
53 | line-height: 35px;
54 | border-radius: 2px;
55 | color: white;
56 | text-decoration: none;
57 | }
58 |
59 | .router-links button {
60 | float: right;
61 | margin-left: 5px;
62 | padding: 0 5px;
63 | background-color: #967ADC;
64 | border: none;
65 | min-width: 88px;
66 | min-height: 35px;
67 | border-radius: 2px;
68 | color: white;
69 | }
70 |
71 | .menu-container.menu-mobile > .router-links.show {
72 | display: block;
73 | }
74 | .menu-container.menu-mobile > .router-links {
75 | display: none;
76 | float: none;
77 | }
78 | .menu-container.menu-mobile > .router-links > .hidden {
79 | display: none;
80 | }
81 | .menu-container.menu-mobile > .router-links > a,
82 | .menu-container.menu-mobile > .router-links > button {
83 | float: none;
84 | display: block;
85 | margin-top: 5px;
86 | margin-left: 0;
87 | }
88 |
89 | #logo {
90 | display: inline-block;
91 | opacity: 0.8;
92 | width: 75px;
93 | height: 75px;
94 | margin-top: -10px;
95 | }
96 | .app-title h1.night-time {
97 | color: white;
98 | }
99 |
100 | #welcome-user {
101 | position: absolute;
102 | top: 11px;
103 | right: 103px;
104 | }
105 |
106 | .user-mobile#welcome-user {
107 | position: relative;
108 | float: right;
109 | margin-right: 2%;
110 | top: 11px;
111 | right: 0;
112 | }
113 |
114 |
115 | @media (max-width: 550px) {
116 | .app-title {
117 | width: auto;
118 | }
119 | .app-title h1 {
120 | display: none;
121 | }
122 | #logo {
123 | width: 60px;
124 | height: 60px;
125 | margin-top: 0;
126 | margin-left: 10px;
127 | }
128 | }
129 |
130 |
131 | // Menu button
132 |
133 | *,
134 | *:after,
135 | *:before {
136 | box-sizing: border-box;
137 | }
138 |
139 | html {
140 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
141 | }
142 |
143 | body {
144 | background: #434A54;
145 | text-align: center;
146 | padding: 50px 0;
147 | }
148 |
149 | .hidden {
150 | display: none;
151 | }
152 |
153 | /* Nav Trigger */
154 | .nav-trigger {
155 | float: right;
156 | width: 50px;
157 | height: 50px;
158 | position: relative;
159 | background: transparent;
160 | border: none;
161 | vertical-align: middle;
162 | padding: 10px;
163 | margin: 0;
164 | margin-top: 4px;
165 | cursor: pointer;
166 | &:focus {
167 | outline: 0;
168 | }
169 | &:hover {
170 | span,
171 | span:before,
172 | span:after {
173 | background: #AC92EC;
174 | }
175 | }
176 | &:before {
177 | content: '';
178 | opacity: 0;
179 | width: 0;
180 | height: 0;
181 | border-radius: 50%;
182 | position: absolute;
183 | top: 50%;
184 | left: 50%;
185 | background: transparent;
186 | transform: translate(-50%, -50%);
187 | transition: all 0.4s ease;
188 | }
189 | span {
190 | display: block;
191 | position: relative;
192 | &:before,
193 | &:after {
194 | content: '';
195 | position: absolute;
196 | left: 0;
197 | }
198 | &:before {
199 | top: -8px;
200 | }
201 | &:after {
202 | bottom: -8px;
203 | }
204 | }
205 | span,
206 | span:before,
207 | span:after {
208 | width: 100%;
209 | height: 4px;
210 | background: #967ADC;
211 | transition: all 0.4s ease;
212 | }
213 | &.is-active {
214 | &:before {
215 | opacity: 1;
216 | width: 50px;
217 | height: 50px;
218 | background: #fff;
219 | }
220 | span {
221 | background: transparent;
222 | &:before {
223 | top: 0;
224 | transform: rotate(225deg);
225 | }
226 | &:after {
227 | bottom: 0;
228 | transform: rotate(-225deg);
229 | }
230 | }
231 | }
232 | }
--------------------------------------------------------------------------------
/client/modules/core/components/header/header.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, AfterViewInit, ViewChild, ElementRef, HostListener, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
2 | import { select } from '@angular-redux/store';
3 | import { Observable } from 'rxjs/Observable';
4 |
5 | import { UserActions } from '../../../../redux/actions/user/user.actions';
6 | import { UserFormActions } from '../../../../redux/actions/userForm/userForm.actions';
7 |
8 | declare let TweenMax: any;
9 | declare let TimelineMax: any;
10 | declare let Power0: any;
11 |
12 | @Component({
13 | selector: 'header-section',
14 | templateUrl: './header.component.html',
15 | styleUrls: ['./header.component.css'],
16 | changeDetection: ChangeDetectionStrategy.OnPush
17 | })
18 |
19 | export class HeaderComponent implements OnInit, AfterViewInit {
20 |
21 | @ViewChild('menu') m: ElementRef;
22 |
23 | @select('user') user$: Observable;
24 | @select('userForm') userForm$: Observable;
25 | menuHide: boolean = true;
26 | menuOpen: boolean = false;
27 |
28 | linkWidth: number;
29 | buttonWidth: number;
30 | bQuant: number;
31 | savedWidth: number;
32 |
33 | private timeline: any;
34 |
35 | constructor(
36 | private el: ElementRef,
37 | public userActions: UserActions,
38 | public userFormActions: UserFormActions,
39 | private ref: ChangeDetectorRef
40 | ) {}
41 |
42 | ngOnInit() {
43 | this.userActions.getMe();
44 | }
45 |
46 | ngAfterViewInit() {
47 | this.linkWidth = this.m.nativeElement.clientWidth;
48 | this.buttonWidth = this.m.nativeElement.children[0].children[0].clientWidth;
49 | this.bQuant = this.m.nativeElement.children[0].children.length - 1;
50 | this.checkMenuWidth();
51 |
52 | this.initMenuAnima();
53 | }
54 |
55 | openMenu() {
56 | this.menuOpen = !this.menuOpen;
57 | if (this.menuOpen) {
58 | this.timeline.play();
59 | } else {
60 | this.timeline.reverse();
61 | }
62 | this.ref.markForCheck();
63 | }
64 |
65 | checkMenuWidth(): void {
66 | this.linkWidth = this.m.nativeElement.clientWidth;
67 |
68 | if (this.linkWidth - ((this.buttonWidth * this.bQuant) + (4 * this.bQuant)) < 1 && this.menuHide) {
69 | this.savedWidth = window.innerWidth + 50;
70 | this.menuHide = false;
71 |
72 | this.ref.markForCheck();
73 | } else if (window.innerWidth > this.savedWidth && !this.menuHide) {
74 | this.menuHide = true;
75 |
76 | this.ref.markForCheck();
77 | }
78 | }
79 |
80 | initMenuAnima() {
81 | // initialize menu handling animation timeline
82 | this.timeline = new TimelineMax({ paused: true });
83 |
84 | const links = this.m.nativeElement.children[0].children;
85 |
86 | this.timeline
87 | .to(this.m.nativeElement.children[0], 0, { ease: Power0.easeNone, css: { className:'+=show' } })
88 | .to(links[0], 0, { x: 150 })
89 | .to(links[1], 0, { x: 150 })
90 | .to(links[4], 0, { x: 150 })
91 | .to(links[2], 0, { x: 150 })
92 | .to(links[3], 0, { x: 150 })
93 | .to(links[0], 0.5, { x: 0 })
94 | .to(links[1], 0.5, { x: 0 }, '-=0.3')
95 | .to(links[4], 0.5, { x: 0 }, '-=0.3')
96 | .to(links[2], 0.5, { x: 0 }, '-=0.5')
97 | .to(links[3], 0.5, { x: 0 }, '-=0.3');
98 | }
99 |
100 |
101 | @HostListener('window:resize', ['$event'])
102 | resize(event) {
103 | this.checkMenuWidth();
104 | }
105 |
106 | @HostListener('document:click', ['$event'])
107 | body(event) {
108 | let clicked = event.target;
109 | let inside = false;
110 | do {
111 | if (clicked === this.m.nativeElement || clicked === this.el.nativeElement.children[2]) {
112 | inside = true;
113 | }
114 | clicked = clicked.parentNode;
115 | } while (clicked);
116 | if(inside){
117 |
118 | }else{
119 | if (this.menuOpen && !this.menuHide) this.openMenu();
120 | }
121 | }
122 |
123 | @HostListener('window:click', ['$event'])
124 | menu(event) {
125 | let clicked = event.target;
126 | let inside = false;
127 | do {
128 | if (clicked === this.m.nativeElement) {
129 | inside = true;
130 | }
131 | clicked = clicked.parentNode;
132 | } while (clicked);
133 | if(inside){
134 | if (this.menuOpen && !this.menuHide) this.openMenu();
135 | }
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/client/modules/core/core-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { Routes, RouterModule } from '@angular/router';
3 |
4 | export const routes: Routes = [
5 | { path: '', redirectTo: '/', pathMatch: 'full' },
6 | { path: 'profile', redirectTo: '/profile', pathMatch: 'full' },
7 | { path: '**', redirectTo: '/PageNotFound', pathMatch: 'full' }
8 | ];
9 |
10 | @NgModule({
11 | imports: [RouterModule.forRoot(routes)],
12 | exports: [RouterModule]
13 | })
14 | export class CoreRoutingModule {}
--------------------------------------------------------------------------------
/client/modules/core/core.component.html:
--------------------------------------------------------------------------------
1 |
53 |
54 |
55 |
56 |
59 |
60 |
{{ (error$ | async).get('message') }}
61 |
62 |
63 |
--------------------------------------------------------------------------------
/client/modules/core/core.component.scss:
--------------------------------------------------------------------------------
1 | .error-card {
2 | display: none;
3 | position: fixed;
4 | bottom: 10%;
5 | left: 5%;
6 | width: 350px;
7 | transform: translateY(400px);
8 | }
9 |
10 | .error-content {
11 | padding: 15px;
12 | font-size: medium;
13 | font-family: Roboto, "Helvetica Neue", sans-serif;
14 | }
15 |
16 | .user-sign button:hover {
17 | cursor: pointer;
18 | }
19 |
20 | .reg-card {
21 | will-change: transform;
22 | display: none;
23 | position: fixed;
24 | width: 400px;
25 | margin-left: -200px;
26 | top: 20%;
27 | left: 50%;
28 | z-index: 10000;
29 | padding: 0;
30 | background: white;
31 | border-radius: 2px;
32 | box-shadow: 0 3px 1px -2px rgba(0,0,0,.2), 0 2px 2px 0 rgba(0,0,0,.14), 0 1px 5px 0 rgba(0,0,0,.12);
33 | transition: box-shadow 280ms cubic-bezier(.4,0,.2,1);
34 |
35 | .title-color {
36 | background: #2196f3;
37 | color: white;
38 | min-height: 48px;
39 | font-size: 20px;
40 | padding: 0 16px;
41 |
42 | div {
43 | line-height: 48px;
44 | }
45 |
46 | }
47 |
48 | .input-wrapper {
49 | height: 40px;
50 | }
51 |
52 | input {
53 | font: inherit;
54 | background: 0 0;
55 | color: currentColor;
56 | border: none;
57 | outline: 0;
58 | padding: 0;
59 | width: 100%;
60 | border-bottom: 1px solid #cacaca;
61 | }
62 |
63 | input:focus {
64 | border-bottom: 2px solid #2196f3;
65 | }
66 |
67 | button {
68 | background-color: #2196f3;
69 | color: white;
70 | box-shadow: 0 3px 1px -2px rgba(0,0,0,.2), 0 2px 2px 0 rgba(0,0,0,.14), 0 1px 5px 0 rgba(0,0,0,.12);
71 | padding: 0 16px;
72 | line-height: 35px;
73 | border-radius: 2px;
74 | }
75 |
76 | button:last-of-type {
77 | background-color: #4caf50;
78 | }
79 |
80 | }
81 |
82 | .reg-card#reg {
83 | top: 15%;
84 | }
85 |
86 |
87 |
88 | .reg-content {
89 | padding: 10px;
90 | }
91 |
92 | .reg-content button {
93 | margin-right: 2px;
94 | }
95 | .reg-content #login-btn,
96 | .reg-content #reg-btn {
97 | float: right;
98 | }
99 |
100 | .error-card {
101 | .error-color {
102 | background: #4caf50;
103 | color: white;
104 | min-height: 48px;
105 | font-size: 20px;
106 | padding: 0 16px;
107 |
108 | div {
109 | line-height: 48px;
110 | }
111 |
112 | }
113 | }
114 |
115 |
116 |
117 | @media (max-width: 800px) {
118 | .error-card {
119 | bottom: 0;
120 | left: 0;
121 | right: 0;
122 | width: auto;
123 | transform: translateY(-400px);
124 | }
125 |
126 | .reg-card {
127 | width: auto;
128 | margin: 0;
129 | top: 0;
130 | left: 0;
131 | right: 0;
132 | }
133 | .reg-card#reg {
134 | top: 0;
135 | }
136 | }
--------------------------------------------------------------------------------
/client/modules/core/core.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, ViewChild, AfterViewInit, ElementRef, HostListener, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
2 | import { NgRedux, select } from '@angular-redux/store';
3 | import { ErrorHandlerActions } from '../../redux/actions/error/errorHandler.actions';
4 | import { UserFormActions } from '../../redux/actions/userForm/userForm.actions';
5 | import { UserActions } from '../../redux/actions/user/user.actions';
6 | import { SEOActions } from '../../redux/actions/seo/seo.actions';
7 | import { Observable } from 'rxjs/Observable';
8 |
9 | declare let TweenMax: any;
10 | declare let TimelineMax: any;
11 | declare let Power0: any;
12 |
13 | @Component({
14 | selector: 'core-section',
15 | templateUrl: 'core.component.html',
16 | styleUrls: ['core.component.css'],
17 | changeDetection: ChangeDetectionStrategy.OnPush
18 | })
19 | export class CoreComponent implements AfterViewInit {
20 | //this decorator is for NgRedux. you can read more about Redux here: https://github.com/angular-redux/ng2-redux
21 | @select('error') error$: Observable;
22 | @select('userForm') userForm$: Observable;
23 |
24 | userSigning: boolean = false;
25 | userSignup: boolean = false;
26 |
27 | private errorTimeline: any;
28 | private formTimeline: any;
29 | private formTimeline2: any;
30 |
31 | private manuContainer: ElementRef;
32 |
33 | //this decorator gabs the object associated with the #errorToast template variable assigned in the app.componnent.html file,
34 | //-- and assigns this object to the class variable errorToast
35 | @ViewChild('errorToast') errorToast: ElementRef;
36 | @ViewChild('formToast') formToast: ElementRef;
37 |
38 | constructor(
39 | private errorHandler: ErrorHandlerActions,
40 | public userFormActions: UserFormActions,
41 | public userActions: UserActions,
42 | private el: ElementRef,
43 | private ref: ChangeDetectorRef
44 | ) {}
45 |
46 | ngAfterViewInit() {
47 | this.manuContainer = this.el.nativeElement.parentElement.children[0].children[0].children[1];
48 |
49 | // Signin and Signup form timelines
50 | this.formTimeline = new TimelineMax({ paused: true });
51 | this.formTimeline
52 | .to(this.formToast.nativeElement.children[0], 0, {ease: Power0.easeNone, display: 'block'})
53 | .fromTo(this.formToast.nativeElement.children[0], 1, {y:-500}, {y: 0});
54 |
55 | this.formTimeline2 = new TimelineMax({ paused: true });
56 | this.formTimeline2
57 | .to(this.formToast.nativeElement.children[1], 0, {ease: Power0.easeNone, display: 'block'})
58 | .fromTo(this.formToast.nativeElement.children[1], 1, {y:-500}, {y: 0});
59 |
60 | this.userForm$.subscribe(uf => {
61 | this.userSigning = uf.get('userSigning');
62 | this.userSignup = uf.get('userSignup');
63 | uf.get('userSigning') ? this.formTimeline.play(): this.formTimeline.reverse();
64 | uf.get('userSignup') ? this.formTimeline2.play(): this.formTimeline2.reverse();
65 | });
66 |
67 |
68 | // initialize error handling animation timeline
69 | this.errorTimeline = new TimelineMax({ paused: true });
70 | this.errorTimeline
71 | .to(this.errorToast.nativeElement, 0, {display:'block',y:400})
72 | .to(this.errorToast.nativeElement, 1, {y:0})
73 | .to(this.errorToast.nativeElement, 1, {y:400, display:'none'}, "+=3")
74 | .add(() => this.errorHandler.hideError());
75 |
76 | // Let the component be in charge of triggering the animation
77 | this.error$.subscribe((error) => error.get('message') ? this.errorTimeline.play(0) : null);
78 | }
79 |
80 | @HostListener('document:click', ['$event'])
81 | body(event) {
82 | let clicked = event.target;
83 | let inside = false;
84 | do {
85 | if (clicked === this.formToast.nativeElement || clicked === this.manuContainer) {
86 | inside = true;
87 | }
88 | clicked = clicked.parentNode;
89 | } while (clicked);
90 | if(inside){
91 |
92 | }else{
93 | if (this.userSigning)
94 | this.userFormActions.loginForm(false);
95 | if (this.userSignup)
96 | this.userFormActions.registerForm(false);
97 | }
98 | }
99 | }
--------------------------------------------------------------------------------
/client/modules/core/core.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { SharedModule } from '../shared/shared.module';
3 | import { CoreRoutingModule } from './core-routing.module';
4 |
5 | import { HomeModule } from '../home/home.module';
6 | import { UserProfileModule } from '../user-profile/user-profile.module';
7 | import { Four0FourModule } from '../404/404.module';
8 |
9 | import { HttpClientModule, HttpClient, HttpInterceptor, HTTP_INTERCEPTORS } from '@angular/common/http';
10 |
11 | import { CoreComponent } from './core.component';
12 | import { HeaderComponent } from './components/header/header.component';
13 | import { FooterComponent } from './components/footer/footer.component';
14 |
15 | import { ErrorHandlerActions } from '../../redux/actions/error/errorHandler.actions';
16 | import { UserFormActions } from '../../redux/actions/userForm/userForm.actions';
17 | import { UserActions } from '../../redux/actions/user/user.actions';
18 | import { SEOActions } from '../../redux/actions/seo/seo.actions';
19 |
20 | import { SocketService } from './services/socketio/socketio.service';
21 | import { TokenInterceptor } from './services/auth/token-interceptor.service';
22 | import { AuthService } from './services/auth/auth.service';
23 |
24 | //Angular and 3rd party serices
25 | import { Cookie } from 'ng2-cookies/ng2-cookies';
26 |
27 | @NgModule({
28 | imports: [
29 | SharedModule,
30 | CoreRoutingModule,
31 | HomeModule,
32 | UserProfileModule,
33 | Four0FourModule, ],
34 | declarations: [ CoreComponent, HeaderComponent, FooterComponent ],
35 | exports: [ CoreRoutingModule, HttpClientModule, CoreComponent, HeaderComponent, FooterComponent ],
36 | providers: [
37 | {
38 | provide: HTTP_INTERCEPTORS,
39 | useClass: TokenInterceptor,
40 | multi: true
41 | },
42 | HttpClient,
43 | ErrorHandlerActions,
44 | UserActions,
45 | UserFormActions,
46 | SEOActions,
47 |
48 | SocketService,
49 | AuthService,
50 |
51 | Cookie
52 | ]
53 | })
54 | export class CoreModule {
55 |
56 | }
--------------------------------------------------------------------------------
/client/modules/core/services/auth/auth.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { HttpClient, HttpResponse, HttpErrorResponse } from '@angular/common/http';
3 |
4 | import { Observable } from 'rxjs/Observable';
5 | import { Cookie } from 'ng2-cookies/ng2-cookies';
6 |
7 | import 'rxjs/Rx';
8 |
9 | //instead of HttpResponse create custom response interface for extractToken
10 | interface tokenExtraction extends HttpResponse {
11 | token: string,
12 | user: {
13 | created: string,
14 | email: string,
15 | provider: string,
16 | role: string,
17 | username: string
18 | }
19 | }
20 |
21 | @Injectable()
22 | export class AuthService {
23 | constructor(private http: HttpClient) { }
24 |
25 | // Private variables that only this service can use
26 | private authUrl = 'auth/local';
27 | private userUrl = 'api/users';
28 |
29 | private extractToken(res: tokenExtraction) {
30 | Cookie.set('token', res.token);
31 | return res.user;
32 | }
33 |
34 | // This is called when there is a cookie OAuth token
35 | // present in the browser so the user will automatically
36 | // sign in
37 | autoLogin(): Observable {
38 | return this.http.get(this.userUrl + '/me');
39 | }
40 |
41 | login(email: string, password: string): Observable {
42 | let body = {
43 | email: email,
44 | password: password
45 | };
46 |
47 | return this.http.post(this.authUrl, body)
48 | .map(this.extractToken);
49 | }
50 |
51 | signup(username: string, email: string, password: string): Observable {
52 | let body = {
53 | username: username,
54 | email: email,
55 | password: password
56 | };
57 | return this.http.post(this.userUrl, body)
58 | .map(this.extractToken);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/client/modules/core/services/auth/token-interceptor.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
3 | import { Observable } from 'rxjs/Rx';
4 | import { Cookie } from 'ng2-cookies/ng2-cookies';
5 | import * as _ from 'lodash';
6 |
7 | // Extending the Http class so connect a OAuth token if present in the cookies
8 | // When the request is recieved on the server authenticated endpoints will
9 | // have varification that give them the ability to execute
10 | @Injectable()
11 | export class TokenInterceptor implements HttpInterceptor {
12 | constructor() { }
13 |
14 | intercept(req: HttpRequest, next: HttpHandler): Observable> {
15 |
16 | // get the token from a service
17 | const authHeader = `Bearer ${Cookie.get('token')}`;
18 | const authReq = req.clone({setHeaders: {Authorization: authHeader}});
19 |
20 | if(authHeader)
21 | return next.handle(authReq);
22 | else
23 | return next.handle(req);
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/client/modules/core/services/socketio/socketio.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { HttpClient } from '@angular/common/http';
3 | import { Observable } from 'rxjs/Observable';
4 |
5 | import { NgRedux } from '@angular-redux/store';
6 | import { IAppState } from '../../../../redux/store';
7 |
8 | import * as _ from 'lodash';
9 | import * as io from 'socket.io-client';
10 |
11 | @Injectable()
12 | export class SocketService {
13 | socket;
14 |
15 | constructor(private ngRedux: NgRedux) {
16 | // socket.io now auto-configures its connection when we ommit a connection url
17 | this.socket = io.connect({ path: '/socket.io-client' });
18 | }
19 |
20 | /**
21 | * Register listeners to sync an array with updates on a model
22 | *
23 | * Takes the array we want to sync, the model name that socket updates are sent from,
24 | * and an optional callback function after new items are updated.
25 | *
26 | * modelName: The server model with atached socket listener
27 | * array: model array from service subscription
28 | * stateArray: Redux states that this syncUpdates instance will invoke
29 | * index 0: update/add-state, index 1: remove-state
30 | * cb: callback function, will be invoked after redux dispatch
31 | * bcb: beforeCallback function, will be invoked before redux dispatch
32 | * dpDelay: dispatch delay, give dp time until dispatch is called
33 | * NOTE: bcb will be called immidiately, dispatch will wait dp to execute
34 | */
35 | syncUpdates(modelName: string, array: any, stateArray: Array, cb?, bcb?, dpDelay?: number) {
36 | /**
37 | * Syncs item creation/updates on 'model:save'
38 | */
39 | this.socket.on(modelName + ':save', (item) => {
40 | const oldItem = _.find(array, { _id: item._id });
41 | const index = array.indexOf(oldItem);
42 |
43 | let event: string = 'created';
44 | let isNew: boolean;
45 |
46 | // replace oldItem if it exists
47 | // otherwise just add item to the collection
48 | if (oldItem) {
49 | // Update store with new object
50 | isNew = false;
51 | event = 'updated';
52 | } else {
53 | // Finds the model for the listener
54 | // and pushes a new object to store
55 | isNew = true;
56 | }
57 |
58 | // create beforCall observable and set the delay to specified time
59 | const bcbObs = Observable.of(true).map(() => bcb ? bcb(item, index, event) : null).delay(dpDelay ? dpDelay : 0);
60 | //create the normal socketio execution observable
61 | const nowObs = Observable.of(true).map(() => {
62 | this.ngRedux.dispatch({ type: stateArray[0], payload: { index: index, object: item, isNew: isNew } });
63 | });
64 | // create callback observable
65 | const cbObs = Observable.of(true).map(() => cb ? cb(item, index, event) : null);
66 | // concatonate all observables in proper order and subscribe to execute
67 | return Observable.concat(bcbObs, nowObs, cbObs).subscribe();
68 | });
69 |
70 | /**
71 | * Syncs removed items on 'model:remove'
72 | */
73 | this.socket.on(modelName + ':remove', (item) => {
74 | const event = 'deconsted';
75 | const oldItem = _.find(array, { _id: item._id });
76 | const index = array.indexOf(oldItem);
77 | _.remove(array, { _id: item._id });
78 |
79 | const nowObserv = Observable.of(true).map(() => {
80 | this.ngRedux.dispatch({ type: stateArray[1], payload: { index: index, object: item } });
81 | });
82 | const cbObserv = Observable.of(true).map(() => cb ? cb(item, index, event) : null);
83 |
84 | return Observable.concat(nowObserv, cbObserv).subscribe();
85 | });
86 | }
87 |
88 | /**
89 | * Removes listeners for a models updates on the socket
90 | */
91 | unsyncUpdates(modelName: string) {
92 | this.socket.removeListener(modelName + ':save');
93 | this.socket.removeListener(modelName + ':remove');
94 | }
95 |
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/client/modules/home/home-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { RouterModule } from '@angular/router';
3 |
4 | import { HomeComponent } from './home.component';
5 |
6 | @NgModule({
7 | imports: [RouterModule.forChild([
8 | { path: '', component: HomeComponent }
9 | ])],
10 | exports: [RouterModule]
11 | })
12 | export class HomeRoutingModule {}
--------------------------------------------------------------------------------
/client/modules/home/home.component.html:
--------------------------------------------------------------------------------
1 | Home
--------------------------------------------------------------------------------
/client/modules/home/home.component.scss:
--------------------------------------------------------------------------------
1 | :host {
2 | position: relative;
3 | display: block;
4 | width: 100%;
5 | text-align: center;
6 | }
7 |
8 | h1 {
9 | position: fixed;
10 | top: 40%;
11 | left: 0;
12 | right:0;
13 | font-size: 50px;
14 | }
--------------------------------------------------------------------------------
/client/modules/home/home.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, HostListener, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
2 |
3 | import { select } from '@angular-redux/store';
4 | import { Observable } from 'rxjs/Observable';
5 |
6 | @Component({
7 | selector: 'home-section',
8 | templateUrl: './home.component.html',
9 | styleUrls: ['./home.component.css'],
10 | changeDetection: ChangeDetectionStrategy.OnPush
11 | })
12 |
13 | export class HomeComponent {
14 |
15 | constructor() { }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/client/modules/home/home.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { SharedModule } from '../shared/shared.module';
3 | import { HomeRoutingModule } from './home-routing.module';
4 |
5 | import { HomeComponent } from './home.component';
6 |
7 | @NgModule({
8 | imports: [ SharedModule, HomeRoutingModule ],
9 | declarations: [
10 | HomeComponent
11 | ]
12 | })
13 | export class HomeModule { }
--------------------------------------------------------------------------------
/client/modules/server-app.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule, APP_BOOTSTRAP_LISTENER, ApplicationRef } from '@angular/core';
2 | import { ServerModule } from '@angular/platform-server';
3 | import { ServerTransferStateModule } from './transfer-state/server-transfer-state.module';
4 | import { AppComponent } from './app.component';
5 | import { AppModule } from './app.module';
6 | import { TransferState } from './transfer-state/transfer-state';
7 | import { BrowserModule } from '@angular/platform-browser';
8 |
9 | export function onBootstrap(appRef: ApplicationRef, transferState: TransferState) {
10 | return () => {
11 | appRef.isStable
12 | .filter(stable => stable)
13 | .first()
14 | .subscribe(() => {
15 | transferState.inject();
16 | });
17 | };
18 | }
19 |
20 | @NgModule({
21 | bootstrap: [AppComponent],
22 | imports: [
23 | BrowserModule.withServerTransition({
24 | appId: 'my-app'
25 | }),
26 | ServerModule,
27 | ServerTransferStateModule,
28 | AppModule
29 | ],
30 | providers: [
31 | {
32 | provide: APP_BOOTSTRAP_LISTENER,
33 | useFactory: onBootstrap,
34 | multi: true,
35 | deps: [
36 | ApplicationRef,
37 | TransferState
38 | ]
39 | }
40 | ]
41 | })
42 | export class ServerAppModule {
43 |
44 | }
--------------------------------------------------------------------------------
/client/modules/shared/shared.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { FormsModule } from '@angular/forms';
4 |
5 | @NgModule({
6 | imports: [ CommonModule ],
7 | exports: [
8 | CommonModule,
9 | FormsModule
10 | ]
11 | })
12 | export class SharedModule { }
--------------------------------------------------------------------------------
/client/modules/transfer-state/browser-transfer-state.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { TransferState } from './transfer-state';
3 |
4 | export function getTransferState(): TransferState {
5 | const transferState = new TransferState();
6 | transferState.initialize(window['TRANSFER_STATE'] || {});
7 | return transferState;
8 | }
9 |
10 | @NgModule({
11 | providers: [
12 | {
13 | provide: TransferState,
14 | useFactory: getTransferState
15 | }
16 | ]
17 | })
18 | export class BrowserTransferStateModule {
19 |
20 | }
--------------------------------------------------------------------------------
/client/modules/transfer-state/server-transfer-state.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { ServerTransferState } from './server-transfer-state';
3 | import { TransferState } from './transfer-state';
4 |
5 | @NgModule({
6 | providers: [
7 | { provide: TransferState, useClass: ServerTransferState }
8 | ]
9 | })
10 | export class ServerTransferStateModule {
11 |
12 | }
--------------------------------------------------------------------------------
/client/modules/transfer-state/server-transfer-state.ts:
--------------------------------------------------------------------------------
1 | import { Injectable, Optional, RendererFactory2, ViewEncapsulation } from '@angular/core';
2 | import { TransferState } from './transfer-state';
3 | import { PlatformState } from '@angular/platform-server';
4 | import * as serialize from 'serialize-javascript';
5 |
6 | @Injectable()
7 | export class ServerTransferState extends TransferState {
8 | constructor( private state: PlatformState, private rendererFactory: RendererFactory2) {
9 | super();
10 | }
11 |
12 | /**
13 | * Inject the State into the bottom of the
14 | */
15 | inject() {
16 | try {
17 | const document: any = this.state.getDocument();
18 | const transferStateString = serialize(this.toJson());
19 | const renderer = this.rendererFactory.createRenderer(document, {
20 | id: '-1',
21 | encapsulation: ViewEncapsulation.None,
22 | styles: [],
23 | data: {}
24 | });
25 |
26 | const head = document.head;
27 | if (!head) {
28 | throw new Error('Please have as the first element in your document');
29 | }
30 |
31 | const script = renderer.createElement('script');
32 | renderer.setValue(script, `window['TRANSFER_STATE'] = ${transferStateString}`);
33 | renderer.appendChild(head, script);
34 | } catch (e) {
35 | console.error(e);
36 | }
37 | }
38 |
39 |
40 | }
--------------------------------------------------------------------------------
/client/modules/transfer-state/transfer-state.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 |
3 | @Injectable()
4 | export class TransferState {
5 | private _map = new Map();
6 |
7 | constructor() {}
8 |
9 | keys() {
10 | return this._map.keys();
11 | }
12 |
13 | get(key: string): any {
14 | return this._map.get(key);
15 | }
16 |
17 | set(key: string, value: any): Map {
18 | return this._map.set(key, value);
19 | }
20 |
21 | toJson(): any {
22 | const obj = {};
23 | Array.from(this.keys())
24 | .forEach(key => {
25 | obj[key] = this.get(key);
26 | });
27 | return obj;
28 | }
29 |
30 | initialize(obj: any): void {
31 | Object.keys(obj)
32 | .forEach(key => {
33 | this.set(key, obj[key]);
34 | });
35 | }
36 |
37 | inject(): void {}
38 | }
--------------------------------------------------------------------------------
/client/modules/user-profile/user-profile-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { RouterModule } from '@angular/router';
3 |
4 | import { UserProfileComponent } from './user-profile.component';
5 |
6 | @NgModule({
7 | imports: [RouterModule.forChild([
8 | { path: 'profile', component: UserProfileComponent }
9 | ])],
10 | exports: [RouterModule]
11 | })
12 | export class UserProfileRoutingModule {}
--------------------------------------------------------------------------------
/client/modules/user-profile/user-profile.component.html:
--------------------------------------------------------------------------------
1 |
2 | Welcome, {{(user$ | async).getIn(['userItem', 'username'])}}
3 |
4 |
--------------------------------------------------------------------------------
/client/modules/user-profile/user-profile.component.scss:
--------------------------------------------------------------------------------
1 | :host {
2 | position: relative;
3 | display: block;
4 | width: 100%;
5 | text-align: center;
6 | }
7 |
8 | section {
9 | position: fixed;
10 | top: 40%;
11 | left: 0;
12 | right:0;
13 | font-size: 50px;
14 | }
--------------------------------------------------------------------------------
/client/modules/user-profile/user-profile.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, ChangeDetectionStrategy } from '@angular/core';
2 |
3 | import { select } from '@angular-redux/store';
4 | import { Observable } from 'rxjs/Observable';
5 |
6 | @Component({
7 | selector: 'user-profile',
8 | templateUrl: './user-profile.component.html',
9 | styleUrls: ['./user-profile.component.css'],
10 | changeDetection: ChangeDetectionStrategy.OnPush
11 | })
12 |
13 | export class UserProfileComponent {
14 |
15 | @select('user') user$: Observable;
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/client/modules/user-profile/user-profile.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { SharedModule } from '../shared/shared.module';
3 | import { UserProfileRoutingModule } from './user-profile-routing.module'
4 |
5 | import { UserProfileComponent } from './user-profile.component';
6 |
7 | @NgModule({
8 | imports: [ SharedModule, UserProfileRoutingModule ],
9 | declarations: [ UserProfileComponent ]
10 | })
11 | export class UserProfileModule {
12 |
13 | }
--------------------------------------------------------------------------------
/client/polyfills.ts:
--------------------------------------------------------------------------------
1 | /*
2 | ==================================================================================
3 | -- Polyfills for webpack ---------------------------------------------------------
4 | ==================================================================================
5 | ** Some simple polyfills for the development environment **
6 | ** This file is used by webpack to include the proper polyfills **
7 | ** The webpack file is located here: GOATstack/config/webpack/webpack.common.js **
8 | ==================================================================================
9 | */
10 |
11 | import 'core-js/es6';
12 | import 'core-js/es7/reflect';
13 | require('zone.js/dist/zone');
14 |
15 | if (process.env.ENV === 'production') {
16 | // Production
17 | } else {
18 | // Development
19 | Error.stackTraceLimit = Infinity;
20 | }
--------------------------------------------------------------------------------
/client/redux/actions/error/errorHandler.actions.spec.ts:
--------------------------------------------------------------------------------
1 | import { NgRedux } from '@angular-redux/store';
2 | import { MockNgRedux } from '@angular-redux/store/testing';
3 | import { ErrorHandlerActions } from './errorHandler.actions';
4 |
5 | describe('ErrorHandler Actions Creator', () => {
6 | let actions: ErrorHandlerActions;
7 | let mockRedux: NgRedux;
8 |
9 | beforeEach(() => {
10 | mockRedux = MockNgRedux.getInstance();
11 | actions = new ErrorHandlerActions(mockRedux);
12 | });
13 |
14 | it('should dispatch SHOW_ERROR action', () => {
15 | const expectedAction = {
16 | type: ErrorHandlerActions.SHOW_ERROR,
17 | payload: 'Testing Error Message'
18 | };
19 |
20 | spyOn(mockRedux, 'dispatch');
21 | actions.showError('Testing Error Message');
22 |
23 | expect(mockRedux.dispatch).toHaveBeenCalled();
24 | expect(mockRedux.dispatch).toHaveBeenCalledWith(expectedAction);
25 | });
26 |
27 | it('should dispatch HIDE_ERROR action', () => {
28 | const expectedAction = {
29 | type: ErrorHandlerActions.HIDE_ERROR
30 | };
31 |
32 | spyOn(mockRedux, 'dispatch');
33 | actions.hideError();
34 |
35 | expect(mockRedux.dispatch).toHaveBeenCalled();
36 | expect(mockRedux.dispatch).toHaveBeenCalledWith(expectedAction);
37 | });
38 | });
39 |
--------------------------------------------------------------------------------
/client/redux/actions/error/errorHandler.actions.ts:
--------------------------------------------------------------------------------
1 | import { Injectable, ElementRef } from '@angular/core';
2 | import { NgRedux } from '@angular-redux/store';
3 | import { IAppState } from '../../store/index';
4 |
5 | // declare global variables to hook onto gsap library
6 | declare let TweenMax: any;
7 | declare let TimelineMax: any;
8 |
9 | /////////////////////////////////////////////////////////
10 | /* ErrorHandler Actions: Used to call dispatches to change
11 | error object in the store
12 |
13 | SHOW_ERROR -> updates the error message to display
14 | HIDE_ERROR -> removes error message string
15 | */
16 | ////////////////////////////////////////////////////////
17 | @Injectable()
18 | export class ErrorHandlerActions {
19 | timeline: any;
20 |
21 | constructor(private ngRedux: NgRedux) { }
22 |
23 | static SHOW_ERROR: string = 'SHOW_ERROR';
24 | static HIDE_ERROR: string = 'HIDE_ERROR';
25 |
26 | showError(error: string): void {
27 | this.ngRedux.dispatch({
28 | type: ErrorHandlerActions.SHOW_ERROR,
29 | payload: error
30 | });
31 | }
32 |
33 | hideError(): void {
34 | this.ngRedux.dispatch({ type: ErrorHandlerActions.HIDE_ERROR });
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/client/redux/actions/seo/seo.actions.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Title } from '@angular/platform-browser';
3 |
4 | ////////////////////////////////////////////////////////////////////////
5 | // SEO Actions: used to get or change the title, icon link, meta tags
6 | // in the heaa of the index.html
7 | ////////////////////////////////////////////////////////////////////////
8 | @Injectable()
9 | export class SEOActions {
10 | private headElement: any;
11 | private favicon: any;
12 | private metaDescription: any;
13 | private metaKeywords: any;
14 |
15 | constructor(private titleService: Title) {
16 | /**
17 | * get the Element
18 | * @type {any}
19 | */
20 | this.headElement = document.getElementsByTagName('head');
21 | this.favicon = document.head.querySelector('link[rel=icon]');
22 | this.metaDescription = this.getOrCreateMetaElement('description');
23 | this.metaKeywords = this.getOrCreateMetaElement('keywords');
24 | }
25 |
26 | /**
27 | * get the HTML Element when it is in the markup, or create it.
28 | * @param name
29 | * @returns {HTMLElement}
30 | */
31 | private getOrCreateMetaElement(name: string): Element {
32 | let el: Element;
33 | el = document.head.querySelector('meta[name=' + name + ']');
34 | if (el === null) {
35 | el = document.createElement('meta');
36 | el.setAttribute('name', name);
37 | this.headElement[0].appendChild(el);
38 | }
39 | return el;
40 | }
41 |
42 | // get the current site site
43 | getTitle(): string {
44 | return this.titleService.getTitle();
45 | }
46 |
47 | // set the site title
48 | setTitle(newTitle: string): void {
49 | this.titleService.setTitle(newTitle);
50 | }
51 |
52 | // get the current link icon
53 | getLinkFavicon(): string {
54 | return this.favicon.getAttribute('href');
55 | }
56 |
57 | // set the site link icon
58 | setLinkFavicon(href: string): void {
59 | this.favicon.setAttribute('href', href);
60 | }
61 |
62 | // get the current meta description
63 | getMetaDescription(): string {
64 | return this.metaDescription.getAttribute('content');
65 | }
66 |
67 | // set the meta description
68 | setMetaDescription(description: string): void {
69 | this.metaDescription.setAttribute('content', description);
70 | }
71 |
72 | // get the current meta keywords
73 | getMetaKeywords(): Array {
74 | return this.metaKeywords.getAttribute('content').split(',');
75 | }
76 |
77 | // set the meta keywords
78 | setMetaKeywords(keywords: Array): void {
79 | this.metaKeywords.setAttribute('content', keywords.toString());
80 | }
81 |
82 | setAll(object: any): void {
83 | if (object.title)
84 | this.setTitle(object.title);
85 | if (object.favicon)
86 | this.setLinkFavicon(object.favicon);
87 | if (object.description)
88 | this.setMetaDescription(object.description);
89 | if (object.keywords)
90 | this.setMetaKeywords(object.keywords);
91 | }
92 |
93 | }
94 |
--------------------------------------------------------------------------------
/client/redux/actions/user/user.actions.spec.ts:
--------------------------------------------------------------------------------
1 | import { FormGroup, FormControl } from '@angular/forms';
2 | import { Observable } from 'rxjs/Observable';
3 |
4 | import { NgRedux } from '@angular-redux/store';
5 | import { MockNgRedux } from '@angular-redux/store/testing';
6 | import { UserActions } from './user.actions';
7 | import { AuthService } from '../../../modules/core/services/auth/auth.service';
8 | import { ErrorHandlerActions } from '../error/errorHandler.actions';
9 | import { Cookie } from 'ng2-cookies/ng2-cookies';
10 |
11 | const testUser = {
12 | _id: '1234',
13 | created: 'today',
14 | userName: 'testUserName',
15 | firstName: 'testFirstName',
16 | lastName: 'testLastName',
17 | email: 'testEmail',
18 | role: 'testRole'
19 | };
20 |
21 | const error = {
22 | status: 400,
23 | statusText: 'Bad Request',
24 | url: 'test:7001',
25 | message: 'this is a test error message'
26 | };
27 |
28 | class MockAuthService extends AuthService {
29 | constructor() {
30 | super(null);
31 | }
32 |
33 | autoLogin(): Observable {
34 | return Observable.of(testUser);
35 | }
36 | login(email: string, password: string): Observable {
37 | return Observable.of(testUser);
38 | }
39 | signup(username: string, email: string, password: string): Observable {
40 | return Observable.of(testUser);
41 | }
42 | logout() { }
43 | }
44 |
45 | describe('User Actions Creator', () => {
46 | let actions: UserActions;
47 | let authService: AuthService;
48 | let errorActions: ErrorHandlerActions;
49 | let mockRedux: NgRedux;
50 |
51 | beforeEach(() => {
52 | Cookie.delete('token');
53 |
54 | authService = new MockAuthService();
55 | mockRedux = MockNgRedux.getInstance();
56 | errorActions = new ErrorHandlerActions(mockRedux);
57 | actions = new UserActions(mockRedux, errorActions, authService);
58 | });
59 |
60 | it('should dispatch LOGIN_USER action when autoLogin() called', () => {
61 | Cookie.set('token', 'testCookie');
62 |
63 | const expectedActionPre = {
64 | type: UserActions.FETCH_USER
65 | };
66 | const expectedAction = {
67 | type: UserActions.LOGIN_USER,
68 | payload: testUser
69 | };
70 |
71 | spyOn(mockRedux, 'dispatch');
72 | actions.getMe();
73 |
74 | expect(mockRedux.dispatch).toHaveBeenCalled();
75 | expect(mockRedux.dispatch).toHaveBeenCalledWith(expectedActionPre);
76 | expect(mockRedux.dispatch).toHaveBeenCalledWith(expectedAction);
77 | });
78 |
79 | it('should dispatch LOGIN_USER action', () => {
80 | const expectedActionPre = {
81 | type: UserActions.FETCH_USER
82 | };
83 | const expectedAction = {
84 | type: UserActions.LOGIN_USER,
85 | payload: testUser
86 | };
87 |
88 | const form = new FormGroup({
89 | login_email: new FormControl("test"),
90 | login_password: new FormControl("test")
91 | });
92 |
93 | spyOn(mockRedux, 'dispatch');
94 | actions.login(form);
95 |
96 | expect(mockRedux.dispatch).toHaveBeenCalled();
97 | expect(mockRedux.dispatch).toHaveBeenCalledWith(expectedActionPre);
98 | expect(mockRedux.dispatch).toHaveBeenCalledWith(expectedAction);
99 | });
100 |
101 | it('should dispatch REGISTER_USER action', () => {
102 | const expectedActionPre = {
103 | type: UserActions.FETCH_USER
104 | };
105 | const expectedAction = {
106 | type: UserActions.REGISTER_USER,
107 | payload: testUser
108 | };
109 |
110 | const form = new FormGroup({
111 | signup_username: new FormControl("testUserName"),
112 | signup_email: new FormControl("testEmail"),
113 | signup_password: new FormControl("test"),
114 | signup_re_password: new FormControl("test")
115 | });
116 |
117 | spyOn(mockRedux, 'dispatch');
118 | actions.register(form);
119 |
120 | expect(mockRedux.dispatch).toHaveBeenCalled();
121 | expect(mockRedux.dispatch).toHaveBeenCalledWith(expectedActionPre);
122 | expect(mockRedux.dispatch).toHaveBeenCalledWith(expectedAction);
123 | });
124 |
125 | it('should dispatch LOGOUT_USER action', () => {
126 | const expectedAction = { type: UserActions.LOGOUT_USER };
127 |
128 | spyOn(mockRedux, 'dispatch');
129 | actions.logout();
130 |
131 | expect(mockRedux.dispatch).toHaveBeenCalled();
132 | expect(mockRedux.dispatch).toHaveBeenCalledWith(expectedAction);
133 | });
134 | });
135 |
--------------------------------------------------------------------------------
/client/redux/actions/user/user.actions.ts:
--------------------------------------------------------------------------------
1 | import {Injectable} from '@angular/core';
2 | import {FormGroup, NgForm} from '@angular/forms';
3 | import {HttpErrorResponse} from '@angular/common/http';
4 |
5 | import {NgRedux} from '@angular-redux/store';
6 | import {IAppState} from '../../store/index';
7 |
8 | import {AuthService} from '../../../modules/core/services/auth/auth.service';
9 | import {ErrorHandlerActions} from '../error/errorHandler.actions';
10 | import {Cookie} from 'ng2-cookies/ng2-cookies';
11 |
12 | //////////////////////////////////////////////////////////////////////
13 | /* User Actions: used to call dispatches to change the user object
14 | in the store
15 |
16 | LOGIN_USER -> updates the user object with user information
17 | LOGOUT_USER -> clears the user object from the store
18 | REGISTER_USER -> updates the user object with user information
19 | */
20 | //////////////////////////////////////////////////////////////////////
21 | @Injectable()
22 | export class UserActions {
23 | constructor(
24 | private ngRedux: NgRedux,
25 | private errorHandler: ErrorHandlerActions,
26 | private authService: AuthService) { }
27 |
28 | static FETCH_USER: string = 'FETCH_USER';
29 | static INVALIDATE_USER: string = 'INVALIDATE_USER';
30 | static LOGIN_USER: string = 'LOGIN_USER';
31 | static LOGOUT_USER: string = 'LOGOUT_USER';
32 | static REGISTER_USER: string = 'REGISTER_USER';
33 |
34 | invalidateUser(error: Object): void {
35 | this.ngRedux.dispatch({ // if an error happens change state to reflect
36 | type: UserActions.INVALIDATE_USER,
37 | payload: error // pass in the json object made in userService.handleError
38 | });
39 | }
40 |
41 | fetchUser(): void {
42 | this.ngRedux.dispatch({ type: UserActions.FETCH_USER });
43 | }
44 |
45 | getMe(): void {
46 | // We will only execute if there's a token present
47 | if (Cookie.get('token')) {
48 | // First change the state to fetching
49 | this.fetchUser();
50 | // subscribe to the service and wait for a response
51 | this.authService.autoLogin().subscribe(user => {
52 | // once a response comes change the state to reflect user info
53 | this.ngRedux.dispatch({
54 | type: UserActions.LOGIN_USER,
55 | payload: user
56 | });
57 | }, (err: HttpErrorResponse) => this.invalidateUser(err));
58 | }
59 | }
60 |
61 | // Setting lf to type FormGroup causes issues
62 | login(lf: any): void {
63 | console.log('lf', lf.value.login_email.type);
64 | // only if the login form is filled
65 | if (lf.valid) {
66 | // First change the state to fetching
67 | this.fetchUser();
68 | // subscribe to the service and wait for a response
69 | this.authService.login(lf.value.login_email, lf.value.login_password)
70 | .subscribe(user => {
71 | // once a response comes change the state to reflect user info
72 | this.ngRedux.dispatch({
73 | type: UserActions.LOGIN_USER,
74 | payload: user
75 | });
76 | }, (err: HttpErrorResponse) => {
77 | this.invalidateUser(err);
78 | this.errorHandler.showError(err.error.message);
79 | });
80 | } else if(!lf.value.login_email || !lf.value.login_password) {
81 | if(!lf.value.login_email && !lf.value.login_password) {
82 | this.errorHandler.showError("Please enter an Email address and password.");
83 | } else if(!lf.value.login_email) {
84 | this.errorHandler.showError("Please enter an Email address.");
85 | } else if(!lf.value.login_password) {
86 | this.errorHandler.showError("Please enter a password.");
87 | }
88 | }
89 | }
90 |
91 | logout(): void {
92 | // simply delete the cached token
93 | Cookie.delete('token');
94 | // and delete the user object in the state
95 | this.ngRedux.dispatch({ type: UserActions.LOGOUT_USER });
96 | }
97 |
98 | // Setting lf to type FormGroup causes issues
99 | register(rf: any): void {
100 | // only if the form is filled and passwords equal the same
101 | if (rf.valid && (rf.value.signup_password === rf.value.signup_re_password)) {
102 | // First change the state to fetching
103 | this.fetchUser();
104 | // subscribe to the service and wait for a response
105 | this.authService.signup(rf.value.signup_username, rf.value.signup_email, rf.value.signup_password)
106 | .subscribe(user => {
107 | // once a response comes change the state to reflect user info
108 | this.ngRedux.dispatch({
109 | type: UserActions.REGISTER_USER,
110 | payload: user
111 | });
112 | }, (err: HttpErrorResponse) => {
113 | this.invalidateUser(err);
114 | this.errorHandler.showError(err.error.message);
115 | });
116 | }
117 | else if (rf.value.signup_password !== rf.value.signup_re_password)
118 | // if the passwords are not the same, simply display the message
119 | this.errorHandler.showError('Passwords do not match!');
120 | }
121 |
122 | }
123 |
--------------------------------------------------------------------------------
/client/redux/actions/userForm/userForm.actions.spec.ts:
--------------------------------------------------------------------------------
1 | import { NgRedux } from '@angular-redux/store';
2 | import { MockNgRedux } from '@angular-redux/store/testing';
3 | import { UserFormActions } from './userForm.actions';
4 |
5 | describe('UserForm Actions Creator', () => {
6 | let actions: UserFormActions;
7 | let mockRedux: NgRedux;
8 |
9 | beforeEach(() => {
10 | mockRedux = MockNgRedux.getInstance();
11 | actions = new UserFormActions(mockRedux);
12 | });
13 |
14 | it('should dispatch LOGIN_FORM_IN action', () => {
15 | const expectedAction = {
16 | type: UserFormActions.LOGIN_FORM_IN
17 | };
18 |
19 | spyOn(mockRedux, 'dispatch');
20 | actions.loginForm(true);
21 |
22 | expect(mockRedux.dispatch).toHaveBeenCalled();
23 | expect(mockRedux.dispatch).toHaveBeenCalledWith(expectedAction);
24 | });
25 |
26 | it('should dispatch LOGIN_FORM_OUT action', () => {
27 | const expectedAction = {
28 | type: UserFormActions.LOGIN_FORM_OUT
29 | };
30 |
31 | spyOn(mockRedux, 'dispatch');
32 | actions.loginForm(false);
33 |
34 | expect(mockRedux.dispatch).toHaveBeenCalled();
35 | expect(mockRedux.dispatch).toHaveBeenCalledWith(expectedAction);
36 | });
37 |
38 | it('should dispatch REGISTER_FORM_IN action', () => {
39 | const expectedAction = {
40 | type: UserFormActions.REGISTER_FORM_IN
41 | };
42 |
43 | spyOn(mockRedux, 'dispatch');
44 | actions.registerForm(true);
45 |
46 | expect(mockRedux.dispatch).toHaveBeenCalled();
47 | expect(mockRedux.dispatch).toHaveBeenCalledWith(expectedAction);
48 | });
49 |
50 | it('should dispatch REGISTER_FORM_OUT action', () => {
51 | const expectedAction = {
52 | type: UserFormActions.REGISTER_FORM_OUT
53 | };
54 |
55 | spyOn(mockRedux, 'dispatch');
56 | actions.registerForm(false);
57 |
58 | expect(mockRedux.dispatch).toHaveBeenCalled();
59 | expect(mockRedux.dispatch).toHaveBeenCalledWith(expectedAction);
60 | });
61 | });
62 |
--------------------------------------------------------------------------------
/client/redux/actions/userForm/userForm.actions.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { FormGroup, NgForm } from '@angular/forms';
3 |
4 | import { NgRedux } from '@angular-redux/store';
5 | import { IAppState } from '../../store/index';
6 |
7 | /////////////////////////////////////////////////////////////////////////
8 | /* UserForm Actions: used to call dispatches to change the userForm
9 | object in the store
10 |
11 | LOGIN_FORM_IN -> Opens the login form (closes reg form)
12 | LOGIN_FORM_OUT -> Closes the Login form
13 | REGISTER_FORM_IN -> Opens the registration form (closes login form)
14 | REGISTER_FORM_OUT -> Closes the registration form
15 | */
16 | /////////////////////////////////////////////////////////////////////////
17 | @Injectable()
18 | export class UserFormActions {
19 | private userSigning: boolean = false;
20 | private userSignup: boolean = false;
21 |
22 | constructor(private ngRedux: NgRedux) { }
23 |
24 | static LOGIN_FORM_IN: string = 'LOGIN_FORM_IN';
25 | static LOGIN_FORM_OUT: string = 'LOGIN_FORM_OUT';
26 | static REGISTER_FORM_IN: string = 'REGISTER_FORM_IN';
27 | static REGISTER_FORM_OUT: string = 'REGISTER_FORM_OUT';
28 |
29 | loginForm(action: boolean) {
30 | if (action)
31 | this.ngRedux.dispatch({ type: UserFormActions.LOGIN_FORM_IN });
32 | else
33 | this.ngRedux.dispatch({ type: UserFormActions.LOGIN_FORM_OUT });
34 | }
35 |
36 | registerForm(action: boolean) {
37 | if (action)
38 | this.ngRedux.dispatch({ type: UserFormActions.REGISTER_FORM_IN });
39 | else
40 | this.ngRedux.dispatch({ type: UserFormActions.REGISTER_FORM_OUT });
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/client/redux/redux.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule, isDevMode } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { NgReduxModule, NgRedux, DevToolsExtension } from '@angular-redux/store';
4 |
5 | import { IAppState, rootReducer, enhancers } from './store/index';
6 | import { createLogger } from 'redux-logger';
7 |
8 | @NgModule({
9 | imports: [ CommonModule, NgReduxModule ],
10 | providers: [
11 | { provide: DevToolsExtension, useClass: DevToolsExtension }
12 | ]
13 | })
14 | export class ReduxModule {
15 | constructor(
16 | private ngRedux: NgRedux,
17 | private devTool: DevToolsExtension) {
18 |
19 | // configure the store here, this is where the enhancers are set
20 | this.ngRedux.configureStore(rootReducer, {},
21 | isDevMode() ? [createLogger({ collapsed: true })] : [],
22 | isDevMode() && devTool.isEnabled() ? [...enhancers, devTool.enhancer()] : [...enhancers]);
23 | }
24 | }
--------------------------------------------------------------------------------
/client/redux/store/errorHandler/errorHandler.initial-state.ts:
--------------------------------------------------------------------------------
1 | import { reimmutifyError } from './errorHandler.transformers';
2 |
3 | // Define the INITIAL_STATE of the error attribute in the store
4 | export const INITIAL_STATE = reimmutifyError({
5 | message: '',
6 | });
7 |
--------------------------------------------------------------------------------
/client/redux/store/errorHandler/errorHandler.reducer.spec.ts:
--------------------------------------------------------------------------------
1 | import { Map } from 'immutable';
2 | import { errorHandlerReducer } from './errorHandler.reducer';
3 | import { INITIAL_STATE } from './errorHandler.initial-state';
4 | import { ErrorHandlerActions } from '../../actions/error/errorHandler.actions';
5 |
6 | // Testing for the errorHandler reducer
7 | describe('ErrorHandler Reducer', () => {
8 | let initialState = INITIAL_STATE;
9 |
10 | // before each test we will reset the state
11 | beforeEach(() => {
12 | initialState = errorHandlerReducer(undefined, { type: 'TEST_INIT' });
13 | });
14 |
15 | // First test is the state object in fact is immutable
16 | it('should have an immutable initial state', () => {
17 | expect(Map.isMap(initialState)).toBe(true);
18 | });
19 |
20 | // Test to see if the object does contain the message
21 | it('should set the error message on SHOW_ERROR', () => {
22 | const previousState = initialState;
23 | const nextState = errorHandlerReducer(initialState,
24 | { type: ErrorHandlerActions.SHOW_ERROR, payload: 'Testing Error Message' });
25 |
26 | expect(previousState.getIn(['message'])).toBe('');
27 | expect(nextState.getIn(['message'])).toBe('Testing Error Message');
28 | });
29 |
30 | // Test to see if the object does not contain the message
31 | it('should remove error message on HIDE_ERROR', () => {
32 | // First SHOW_ERROR and check
33 | const nextState = errorHandlerReducer(initialState,
34 | { type: ErrorHandlerActions.SHOW_ERROR, payload: 'Testing Error Message' });
35 | expect(nextState.getIn(['message'])).toBe('Testing Error Message');
36 | // Then HIDE_ERROR and check
37 | const nextState2 = errorHandlerReducer(nextState,
38 | { type: ErrorHandlerActions.HIDE_ERROR });
39 | expect(nextState2.getIn(['message'])).toBe('');
40 | });
41 | });
42 |
--------------------------------------------------------------------------------
/client/redux/store/errorHandler/errorHandler.reducer.ts:
--------------------------------------------------------------------------------
1 | import { ErrorHandlerActions } from '../../actions/error/errorHandler.actions';
2 | import { reimmutifyError } from './errorHandler.transformers';
3 | import { IError } from './errorHandler.types';
4 |
5 | import { INITIAL_STATE } from './errorHandler.initial-state';
6 |
7 | // define the reducer for error attribute in store
8 | export function errorHandlerReducer(state: IError = INITIAL_STATE, action: any) {
9 | // Depending on the incoming state 'type' execute corresponding state change
10 | switch(action.type) {
11 | case ErrorHandlerActions.SHOW_ERROR:
12 | return state.updateIn(['message'], val => action.payload);
13 | case ErrorHandlerActions.HIDE_ERROR:
14 | return state.updateIn(['message'], val => '');
15 | default:
16 | return state;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/client/redux/store/errorHandler/errorHandler.transformers.ts:
--------------------------------------------------------------------------------
1 | import { Map, Record } from 'immutable';
2 | import { IError, IErrorItem } from './errorHandler.types';
3 |
4 | // functions to change the state of the data
5 | // either immutable -> mutable or mutable -> immutable
6 | export function deimmutifyError(state: IError): Object {
7 | return state.toJS();
8 | }
9 |
10 | export function reimmutifyError(plain): IError {
11 | return Map(plain ? plain : '');
12 | }
13 |
--------------------------------------------------------------------------------
/client/redux/store/errorHandler/errorHandler.types.ts:
--------------------------------------------------------------------------------
1 | import { Map } from 'immutable';
2 |
3 | // Interface describing the attributes
4 | // the corresponding reducer will need
5 | // to manipulate (immutably)
6 | export interface IErrorItem {
7 | message: string;
8 | }
9 |
10 | // Export type so reducer will understand
11 | export type IError = Map;
12 |
--------------------------------------------------------------------------------
/client/redux/store/errorHandler/index.ts:
--------------------------------------------------------------------------------
1 | import { errorHandlerReducer } from './errorHandler.reducer';
2 | import { IError } from './errorHandler.types';
3 | import { deimmutifyError, reimmutifyError } from './errorHandler.transformers';
4 |
5 | // This file is for convienience so only one import is required
6 | export {
7 | errorHandlerReducer,
8 | IError,
9 | deimmutifyError,
10 | reimmutifyError
11 | };
12 |
--------------------------------------------------------------------------------
/client/redux/store/index.ts:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | // import persistState from 'redux-localStorage';
3 | import * as error from './errorHandler/index';
4 | import * as userForm from './userForm/index';
5 | import * as user from './user/index';
6 | // DO NOT REMOVE: template store imports
7 |
8 | // IAppState is the applications store where all persistant data
9 | // should be stored
10 | export class IAppState {
11 | error?: error.IError;
12 | user?: user.IUser;
13 | userForm?: userForm.IUserForm;
14 | // DO NOT REMOVE: template store attributes
15 | };
16 |
17 | // Each reducer is connected to a coresponding store attribute
18 | // combineReducers() creates a root reducer while maintaining
19 | // this one-2-one relationship
20 | export const rootReducer = combineReducers({
21 | error: error.errorHandlerReducer,
22 | user: user.userReducer,
23 | userForm: userForm.userFormReducer,
24 | // DO NOT REMOVE: template reducers
25 | });
26 |
27 | // Redux plugins/enhancers go here
28 | export const enhancers = [
29 | // persistState('GOAT-stack', { key: 'GOAT-stack' })
30 | ];
31 |
--------------------------------------------------------------------------------
/client/redux/store/user/index.ts:
--------------------------------------------------------------------------------
1 | import { userReducer } from './user.reducer';
2 | import { IUser } from './user.types';
3 | import { deimmutifyUser, reimmutifyUser } from './user.transformers';
4 |
5 | // This file is for convienience so only one import is required
6 | export {
7 | userReducer,
8 | IUser,
9 | deimmutifyUser,
10 | reimmutifyUser
11 | };
12 |
--------------------------------------------------------------------------------
/client/redux/store/user/user.initial-state.ts:
--------------------------------------------------------------------------------
1 | import { reimmutifyUser } from './user.transformers';
2 | import { Map } from 'immutable';
3 |
4 | // Define the INITIAL_STATE of the user object
5 | export const INITIAL_STATE = reimmutifyUser({
6 | fetching: false
7 | });
8 |
--------------------------------------------------------------------------------
/client/redux/store/user/user.reducer.spec.ts:
--------------------------------------------------------------------------------
1 | import { Map } from 'immutable';
2 | import { userReducer } from './user.reducer';
3 | import { INITIAL_STATE } from './user.initial-state';
4 | import { UserActions } from '../../actions/user/user.actions';
5 |
6 | const testUser = {
7 | _id: '1234',
8 | created: 'today',
9 | userName: 'testUserName',
10 | firstName: 'testFirstName',
11 | lastName: 'testLastName',
12 | email: 'testEmail',
13 | role: 'testRole'
14 | };
15 |
16 | describe('User Reducer', () => {
17 | let initialState = INITIAL_STATE;
18 |
19 | beforeEach(() => {
20 | initialState = userReducer(undefined, { type: 'TEST_INIT' });
21 | });
22 |
23 | it('should have an immutable initial state', () => {
24 | expect(Map.isMap(initialState)).toBe(true);
25 | });
26 |
27 | it('should indicate didInvalidate when INVALIDATE_USER state change', () => {
28 | const previousState = initialState;
29 | const nextState = userReducer(previousState,
30 | { type: UserActions.INVALIDATE_USER, payload: {
31 | status: 400,
32 | statusText: 'Bad Request',
33 | url: 'test:7001',
34 | message: 'this is a error message test'
35 | }});
36 |
37 | expect(previousState.getIn(['fetching'])).toBe(false);
38 | expect(previousState.hasIn(['didInvalidate'])).toBe(false);
39 | expect(previousState.hasIn(['userItem'])).toBe(false);
40 |
41 | expect(nextState.getIn(['fetching'])).toBe(false);
42 | expect(nextState.hasIn(['userItem'])).toBe(false);
43 | expect(nextState.getIn(['didInvalidate', 'status'])).toBe(400);
44 | expect(nextState.getIn(['didInvalidate', 'statusText'])).toBe('Bad Request');
45 | expect(nextState.getIn(['didInvalidate', 'url'])).toBe('test:7001');
46 | expect(nextState.getIn(['didInvalidate', 'message'])).toBe('this is a error message test');
47 | });
48 |
49 | it('should indicated fetching when FETCH_USER state change', () => {
50 | const previousState = initialState;
51 | const nextState = userReducer(previousState, { type: UserActions.FETCH_USER });
52 |
53 | expect(previousState.getIn(['fetching'])).toBe(false);
54 | expect(previousState.hasIn(['didInvalidate'])).toBe(false);
55 | expect(previousState.hasIn(['userItem'])).toBe(false);
56 |
57 | expect(nextState.getIn(['fetching'])).toBe(true);
58 | expect(previousState.hasIn(['didInvalidate'])).toBe(false);
59 | expect(previousState.hasIn(['userItem'])).toBe(false);
60 |
61 | });
62 |
63 | it('should set user to user Object on LOGIN_USER', () => {
64 | const previousState = initialState;
65 | const nextState = userReducer(previousState,
66 | { type: UserActions.LOGIN_USER, payload: testUser });
67 |
68 | expect(previousState.hasIn(['userItem'])).toBe(false);
69 |
70 | expect(nextState.getIn(['userItem', '_id'])).toBe('1234');
71 | expect(nextState.getIn(['userItem', 'created'])).toBe('today');
72 | expect(nextState.getIn(['userItem', 'userName'])).toBe('testUserName');
73 | expect(nextState.getIn(['userItem', 'firstName'])).toBe('testFirstName');
74 | expect(nextState.getIn(['userItem', 'lastName'])).toBe('testLastName');
75 | expect(nextState.getIn(['userItem', 'email'])).toBe('testEmail');
76 | expect(nextState.getIn(['userItem', 'role'])).toBe('testRole');
77 | });
78 |
79 | it('should set user to user Object on REGISTER_USER', () => {
80 | const previousState = initialState;
81 | const nextState = userReducer(previousState,
82 | { type: UserActions.REGISTER_USER, payload: testUser });
83 |
84 | expect(previousState.hasIn(['userItem'])).toBe(false);
85 |
86 | expect(nextState.getIn(['userItem', '_id'])).toBe('1234');
87 | expect(nextState.getIn(['userItem', 'created'])).toBe('today');
88 | expect(nextState.getIn(['userItem', 'userName'])).toBe('testUserName');
89 | expect(nextState.getIn(['userItem', 'firstName'])).toBe('testFirstName');
90 | expect(nextState.getIn(['userItem', 'lastName'])).toBe('testLastName');
91 | expect(nextState.getIn(['userItem', 'email'])).toBe('testEmail');
92 | expect(nextState.getIn(['userItem', 'role'])).toBe('testRole');
93 | });
94 |
95 | it('should set user to empty Map on LOGOUT_USER', () => {
96 | const previousState = userReducer(initialState,
97 | { type: UserActions.LOGIN_USER, payload: testUser });
98 | const nextState = userReducer(previousState,
99 | { type: UserActions.LOGOUT_USER });
100 |
101 | expect(previousState.getIn(['userItem', '_id'])).toBe('1234');
102 | expect(previousState.getIn(['userItem', 'created'])).toBe('today');
103 | expect(previousState.getIn(['userItem', 'userName'])).toBe('testUserName');
104 | expect(previousState.getIn(['userItem', 'firstName'])).toBe('testFirstName');
105 | expect(previousState.getIn(['userItem', 'lastName'])).toBe('testLastName');
106 | expect(previousState.getIn(['userItem', 'email'])).toBe('testEmail');
107 | expect(previousState.getIn(['userItem', 'role'])).toBe('testRole');
108 |
109 | expect(nextState.hasIn(['userItem'])).toBe(false);
110 | });
111 | });
112 |
--------------------------------------------------------------------------------
/client/redux/store/user/user.reducer.ts:
--------------------------------------------------------------------------------
1 | import { UserActions } from '../../actions/user/user.actions';
2 | import { IUser } from './user.types';
3 | import { reimmutifyUser, } from './user.transformers';
4 | import { INITIAL_STATE } from './user.initial-state';
5 |
6 | // Define the reducer that will initiate state changes for user
7 | export function userReducer(state: IUser = INITIAL_STATE, action: any) {
8 | // will determine proper state change based off the type
9 | switch (action.type) {
10 | case UserActions.INVALIDATE_USER:
11 | // Indead of return a new Map, have immutable manage
12 | // what happens to the old object by merging
13 | return state.mergeWith((prev, next) => next, reimmutifyUser({
14 | fetching: false,
15 | didInvalidate: action.payload
16 | }));
17 | case UserActions.FETCH_USER:
18 | return state
19 | .updateIn(['fetching'], val => true)
20 | .deleteIn(['didInvalidate']);
21 | case UserActions.LOGIN_USER:
22 | case UserActions.REGISTER_USER:
23 | // Indead of return a new Map, have immutable manage
24 | // what happens to the old object by merging
25 | return state.mergeWith((prev, next) => next, reimmutifyUser({
26 | fetching: false,
27 | userItem: action.payload
28 | }));
29 | case UserActions.LOGOUT_USER:
30 | return state.clear()
31 | .updateIn(['fetching'], val => false)
32 | .deleteIn(['userItem']);
33 | default:
34 | return state;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/client/redux/store/user/user.transformers.ts:
--------------------------------------------------------------------------------
1 | import { Map } from 'immutable';
2 | import { IUser, IUserBaseItem, IUserItem, IInvalidateItem } from './user.types';
3 |
4 | // functions to change the state of the data
5 | // either immutable -> mutable or mutable -> immutable
6 | export function deimmutifyUser(state: IUser): Object {
7 | return state.toJS();
8 | }
9 |
10 | export function reimmutifyUser(plain): IUser {
11 | if (plain.userItem) {
12 | plain.userItem = Map(plain.userItem);
13 | }
14 | if (plain.didInvalidate) {
15 | plain.didInvalidate = Map(plain.didInvalidate);
16 | }
17 |
18 | return Map(plain ? plain : {});
19 | }
20 |
--------------------------------------------------------------------------------
/client/redux/store/user/user.types.ts:
--------------------------------------------------------------------------------
1 | import { Map } from 'immutable';
2 |
3 | // Define an interface of the object that will be saved
4 | export interface IUserItem {
5 | _id: string;
6 | created: string;
7 | userName: string;
8 | firstName: string;
9 | lastName: string;
10 | email: string;
11 | role: string;
12 | }
13 | export interface IInvalidateItem {
14 | status: number;
15 | statusText: string;
16 | url: string;
17 | message: string;
18 | }
19 | export interface IUserBaseItem {
20 | fetching: boolean;
21 | didInvalidate: Map;
22 | userItem: Map;
23 | }
24 |
25 | // Export the type so the reducer and store will understand
26 | export type IUser = Map;
27 |
--------------------------------------------------------------------------------
/client/redux/store/userForm/index.ts:
--------------------------------------------------------------------------------
1 | import { userFormReducer } from './userForm.reducer';
2 | import { IUserForm } from './userForm.types';
3 | import { deimmutifyUserForm, reimmutifyUserForm } from './userForm.transformers';
4 |
5 | // This file is for convienience so only one import is required
6 | export {
7 | userFormReducer,
8 | IUserForm,
9 | deimmutifyUserForm,
10 | reimmutifyUserForm
11 | };
12 |
--------------------------------------------------------------------------------
/client/redux/store/userForm/userForm.initial-state.ts:
--------------------------------------------------------------------------------
1 | import { reimmutifyUserForm } from './userForm.transformers';
2 |
3 | // Define the initial state of userForm object
4 | export const INITIAL_STATE = reimmutifyUserForm({
5 | userSigning: false,
6 | userSignup: false
7 | });
8 |
--------------------------------------------------------------------------------
/client/redux/store/userForm/userForm.reducer.spec.ts:
--------------------------------------------------------------------------------
1 | import { Map } from 'immutable';
2 | import { userFormReducer } from './userForm.reducer';
3 | import { INITIAL_STATE } from './userForm.initial-state';
4 | import { UserFormActions } from '../../actions/userForm/userForm.actions';
5 |
6 | describe('UserForm Reducer', () => {
7 | let initialState = INITIAL_STATE;
8 |
9 | beforeEach(() => {
10 | initialState = userFormReducer(undefined, { type: 'TEST_INIT' });
11 | });
12 |
13 | it('should have an immutable initial state', () => {
14 | expect(Map.isMap(initialState)).toBe(true);
15 | });
16 |
17 | it('should set userSigning to true on LOGIN_FORM_IN', () => {
18 | const previousState = initialState;
19 | const nextState = userFormReducer(previousState,
20 | { type: UserFormActions.LOGIN_FORM_IN });
21 |
22 | expect(previousState.getIn(['userSigning'])).toBe(false);
23 | expect(previousState.getIn(['userSignup'])).toBe(false);
24 |
25 | expect(nextState.getIn(['userSigning'])).toBe(true);
26 | expect(nextState.getIn(['userSignup'])).toBe(false);
27 | });
28 |
29 | it('should set userSigning to false on LOGIN_FORM_OUT', () => {
30 | const previousState = userFormReducer(initialState,
31 | { type: UserFormActions.LOGIN_FORM_IN });
32 | const nextState = userFormReducer(previousState,
33 | { type: UserFormActions.LOGIN_FORM_OUT });
34 |
35 | expect(previousState.getIn(['userSigning'])).toBe(true);
36 | expect(previousState.getIn(['userSignup'])).toBe(false);
37 |
38 | expect(nextState.getIn(['userSigning'])).toBe(false);
39 | expect(nextState.getIn(['userSignup'])).toBe(false);
40 | });
41 |
42 | it('should set userSignup to true on REGISTER_FORM_IN', () => {
43 | const previousState = initialState;
44 | const nextState = userFormReducer(previousState,
45 | { type: UserFormActions.REGISTER_FORM_IN });
46 |
47 | expect(previousState.getIn(['userSigning'])).toBe(false);
48 | expect(previousState.getIn(['userSignup'])).toBe(false);
49 |
50 | expect(nextState.getIn(['userSigning'])).toBe(false);
51 | expect(nextState.getIn(['userSignup'])).toBe(true);
52 | });
53 |
54 | it('should set userSignup to false on REGISTER_FORM_OUT', () => {
55 | const previousState = userFormReducer(initialState,
56 | { type: UserFormActions.REGISTER_FORM_IN });
57 | const nextState = userFormReducer(previousState,
58 | { type: UserFormActions.REGISTER_FORM_OUT });
59 |
60 | expect(previousState.getIn(['userSigning'])).toBe(false);
61 | expect(previousState.getIn(['userSignup'])).toBe(true);
62 |
63 | expect(nextState.getIn(['userSigning'])).toBe(false);
64 | expect(nextState.getIn(['userSignup'])).toBe(false);
65 | });
66 |
67 | it('should set swap userSigning and userSignup on REGISTER_FORM_IN', () => {
68 | const previousState = userFormReducer(initialState,
69 | { type: UserFormActions.LOGIN_FORM_IN });
70 | const nextState = userFormReducer(previousState,
71 | { type: UserFormActions.REGISTER_FORM_IN });
72 |
73 | expect(previousState.getIn(['userSigning'])).toBe(true);
74 | expect(previousState.getIn(['userSignup'])).toBe(false);
75 |
76 | expect(nextState.getIn(['userSigning'])).toBe(false);
77 | expect(nextState.getIn(['userSignup'])).toBe(true);
78 | });
79 |
80 | it('should set swap userSignup and userSigning on LOGIN_FORM_IN', () => {
81 | const previousState = userFormReducer(initialState,
82 | { type: UserFormActions.REGISTER_FORM_IN });
83 | const nextState = userFormReducer(previousState,
84 | { type: UserFormActions.LOGIN_FORM_IN });
85 |
86 | expect(previousState.getIn(['userSigning'])).toBe(false);
87 | expect(previousState.getIn(['userSignup'])).toBe(true);
88 |
89 | expect(nextState.getIn(['userSigning'])).toBe(true);
90 | expect(nextState.getIn(['userSignup'])).toBe(false);
91 | });
92 | });
93 |
--------------------------------------------------------------------------------
/client/redux/store/userForm/userForm.reducer.ts:
--------------------------------------------------------------------------------
1 | import { UserFormActions } from '../../actions/userForm/userForm.actions';
2 | import { reimmutifyUserForm } from './userForm.transformers';
3 | import { IUserForm } from './userForm.types';
4 | import { INITIAL_STATE } from './userForm.initial-state';
5 |
6 | // Define the reducer that will initiate state changes for userForm
7 | export function userFormReducer(state: IUserForm = INITIAL_STATE, action: any) {
8 | // will decide what state change is necessary based off the type
9 | switch (action.type) {
10 | case UserFormActions.LOGIN_FORM_IN:
11 | return state
12 | .updateIn(['userSigning'], val => true)
13 | .updateIn(['userSignup'], val => false);
14 | case UserFormActions.REGISTER_FORM_IN:
15 | return state
16 | .updateIn(['userSigning'], val => false)
17 | .updateIn(['userSignup'], val => true);
18 | case UserFormActions.LOGIN_FORM_OUT:
19 | case UserFormActions.REGISTER_FORM_OUT:
20 | return state
21 | .updateIn(['userSignup'], val => false)
22 | .updateIn(['userSigning'], val => false);
23 | default:
24 | return state;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/client/redux/store/userForm/userForm.transformers.ts:
--------------------------------------------------------------------------------
1 | import { Map } from 'immutable';
2 | import { IUserForm, IUserFormItem } from './userForm.types';
3 |
4 | // functions to change the state of the data
5 | // either immutable -> mutable or mutable -> immutable
6 | export function deimmutifyUserForm(state: IUserForm): Object {
7 | return state.toJS();
8 | }
9 |
10 | export function reimmutifyUserForm(plain): IUserForm {
11 | return Map(plain ? plain : {});
12 | }
13 |
--------------------------------------------------------------------------------
/client/redux/store/userForm/userForm.types.ts:
--------------------------------------------------------------------------------
1 | import { Map } from 'immutable';
2 |
3 | // Define an interface of the object that will be saved
4 | export interface IUserFormItem {
5 | userSigning: boolean;
6 | userSignup: boolean;
7 | }
8 |
9 | // Export the type so the reducer and store will understand
10 | export type IUserForm = Map;
11 |
--------------------------------------------------------------------------------
/client/styles.scss:
--------------------------------------------------------------------------------
1 | /* latin */
2 | @font-face {
3 | font-family: 'Fredoka One';
4 | font-style: normal;
5 | font-weight: 400;
6 | src: local('Fredoka One'), local('FredokaOne-Regular'), url('/public/fonts/Fredoka_One.woff2') format('woff2');
7 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
8 | }
9 |
10 |
11 | /*
12 | ==========================================================================
13 | * Reset browser default:
14 | * CSS so cross browser styling is more predictable
15 | ==========================================================================
16 | */
17 |
18 |
19 | /* http://meyerweb.com/eric/tools/css/reset/
20 | v2.0 | 20110126
21 | License: none (public domain)
22 | */
23 |
24 | applet,
25 | html,
26 | body,
27 | div,
28 | span,
29 | object,
30 | iframe,
31 | h1,
32 | h2,
33 | h3,
34 | h4,
35 | h5,
36 | h6,
37 | p,
38 | blockquote,
39 | pre,
40 | a,
41 | abbr,
42 | acronym,
43 | address,
44 | big,
45 | cite,
46 | code,
47 | del,
48 | dfn,
49 | em,
50 | img,
51 | ins,
52 | kbd,
53 | q,
54 | s,
55 | samp,
56 | small,
57 | strike,
58 | strong,
59 | sub,
60 | sup,
61 | tt,
62 | var,
63 | b,
64 | u,
65 | i,
66 | center,
67 | dl,
68 | dt,
69 | dd,
70 | ol,
71 | ul,
72 | li,
73 | fieldset,
74 | form,
75 | label,
76 | legend,
77 | table,
78 | caption,
79 | tbody,
80 | tfoot,
81 | thead,
82 | tr,
83 | th,
84 | td,
85 | article,
86 | aside,
87 | canvas,
88 | details,
89 | embed,
90 | figure,
91 | figcaption,
92 | footer,
93 | header,
94 | hgroup,
95 | menu,
96 | nav,
97 | output,
98 | ruby,
99 | section,
100 | summary,
101 | time,
102 | mark,
103 | audio,
104 | video,
105 | button {
106 | margin: 0;
107 | padding: 0;
108 | border: 0;
109 | font-size: 100%;
110 | font: inherit;
111 | font-family: 'Fredoka One', Arial;
112 | vertical-align: baseline;
113 | }
114 |
115 |
116 | /* HTML5 display-role reset for older browsers */
117 |
118 | article,
119 | aside,
120 | details,
121 | figcaption,
122 | figure,
123 | footer,
124 | header,
125 | hgroup,
126 | menu,
127 | nav,
128 | section {
129 | display: block;
130 | }
131 |
132 | ol,
133 | ul,
134 | li {
135 | list-style: none;
136 | }
137 |
138 | blockquote,
139 | q {
140 | quotes: none;
141 | }
142 |
143 | blockquote:before,
144 | blockquote:after,
145 | q:before,
146 | q:after {
147 | content: none;
148 | }
149 |
150 | table {
151 | border-collapse: collapse;
152 | border-spacing: 0;
153 | }
154 |
155 | label {
156 | height: 100%;
157 | }
158 |
159 | input:-webkit-autofill {
160 | -webkit-box-shadow: 0 0 0 1000px white inset;
161 | -moz-box-shadow: 0 0 0 1000px white inset;
162 | box-shadow: 0 0 0 1000px white inset;
163 | }
164 |
165 | html {
166 | position: relative;
167 | overflow-y: scroll;
168 | }
169 |
170 |
171 | /*
172 | --------------------------------------------------------------------------
173 | * End Reset browser default
174 | --------------------------------------------------------------------------
175 | */
--------------------------------------------------------------------------------
/client/vendor.ts:
--------------------------------------------------------------------------------
1 | /*
2 | ==================================================================================
3 | -- Vendor packages for webpack ---------------------------------------------------
4 | ==================================================================================
5 | ** This is where all vendor resources will be imported **
6 | ** This file is used by webpack to include stable vendor packages **
7 | ** The webpack file is located here: GOATstack/config/webpack/webpack.common.js **
8 | ==================================================================================
9 | */
10 |
11 | // Angular
12 | import '@angular/platform-browser';
13 | import '@angular/platform-browser-dynamic';
14 | import '@angular/core';
15 | import '@angular/common';
16 | import '@angular/common/http';
17 | import '@angular/router';
18 |
19 | import 'hammerjs/hammer';
20 |
21 | // RxJS
22 | import 'rxjs';
23 |
24 | import '@angular-redux/store';
25 | import 'lodash';
26 | import 'ng2-cookies/ng2-cookies';
27 |
28 | // Other vendors for example jQuery, Lodash or Bootstrap
29 | // You can import js, ts, css, sass, ...
30 |
31 | require('./styles');
32 | require('./loader');
--------------------------------------------------------------------------------
/config/env/default.ts:
--------------------------------------------------------------------------------
1 | /*
2 | ==============================================================================================
3 | These configuration settings get called no matter what Node's process.env.NODE_ENV is set to.
4 | ==============================================================================================
5 | */
6 |
7 | export const defaultConfig = {
8 | // Change to use https
9 | https_secure: false,
10 | // You will need to generate a self signed ssl certificate
11 | // using the generator in ./scripts or use a trusted certificate
12 | cert_loc: './server/sslcerts/cert.pem',
13 | key_loc: './server/sslcerts/key.pem',
14 |
15 | port: process.env.PORT || 5000,
16 | host: process.env.HOST || '0.0.0.0',
17 | // Session Cookie settings
18 | sessionCookie: {
19 | // session expiration is set by default to 24 hours
20 | maxAge: 24 * (60 * 60 * 1000),
21 | // httpOnly flag makes sure the cookie is only accessed
22 | // through the HTTP protocol and not JS/browser
23 | httpOnly: true,
24 | // secure cookie should be turned to true to provide additional
25 | // layer of security so that the cookie is set only when working
26 | // in HTTPS mode.
27 | secure: false
28 | },
29 | // sessionSecret should be changed for security measures and concerns
30 | sessionSecret: process.env.SESSION_SECRET || 'APP',
31 | // sessionKey is set to the generic sessionId key used by PHP applications
32 | // for obsecurity reasons
33 | sessionKey: 'sessionId',
34 | sessionCollection: 'sessions',
35 | userRoles: ['guest', 'user', 'admin']
36 | };
37 |
--------------------------------------------------------------------------------
/config/env/development.ts:
--------------------------------------------------------------------------------
1 | /*
2 | ===============================================
3 | Used when process.env.NODE_ENV = 'development'
4 | ===============================================
5 | //This file adds config settings and overwrites config settings in the ./default.ts file
6 | //process.env.NODE_ENV is utilized in config/config.ts
7 | */
8 |
9 | export const devEnv = {
10 | mongo: {
11 | uri: 'mongodb://localhost/dev',
12 | options: {
13 | useMongoClient: true
14 | },
15 | // Enable mongoose debug mode
16 | debug: process.env.MONGODB_DEBUG || false
17 | },
18 | cassandra: {
19 | contactPoints: ['127.0.0.1'],
20 | protocolOptions: { port: 9042 },
21 | queryOptions: { consistency: 1 },
22 | keyspace: 'dev'
23 | },
24 | sql: {
25 | // uri: 'postgres://postgres:postgres@localhost:5432/GOATstack'
26 | database: 'dev',
27 | username: 'postgres',
28 | password: 'postgres',
29 | options: {
30 | host: 'localhost',
31 | dialect: 'postgres'||'mysql'||'mariadb'||'sqlite'||'mssql',
32 | logging: false,
33 | }
34 | },
35 | seedDB: true
36 | };
37 |
--------------------------------------------------------------------------------
/config/env/production.ts:
--------------------------------------------------------------------------------
1 | /*
2 | ===============================================
3 | Used when process.env.NODE_ENV = 'production'
4 | ===============================================
5 | //This file adds config settings and overwrites config settings in the ./default.ts file
6 | //process.env.NODE_ENV is utilized in config/config.ts
7 | */
8 |
9 | export const prodEnv = {
10 | port: process.env.PORT || 8443,
11 | // Binding to 127.0.0.1 is safer in production.
12 | host: process.env.HOST || '0.0.0.0',
13 | mongo: {
14 | uri: process.env.DB_URI || 'mongodb://localhost/prod',
15 | options: {
16 | useMongoClient: true,
17 | user: process.env.DB_USER || '',
18 | pass: process.env.DB_PW || ''
19 | },
20 | // Enable mongoose debug mode
21 | debug: process.env.MONGODB_DEBUG || false
22 | },
23 | cassandra: {
24 | contactPoints: ['127.0.0.1'],
25 | protocolOptions: { port: 9042 },
26 | queryOptions: { consistency: 1 },
27 | keyspace: 'prod'
28 | },
29 | sql: {
30 | // uri: 'postgres://postgres:postgres@localhost:5432/GOATstack'
31 | database: 'prod',
32 | username: 'postgres',
33 | password: 'postgres',
34 | options: {
35 | host: 'localhost',
36 | dialect: 'postgres'||'mysql'||'mariadb'||'sqlite'||'mssql',
37 | logging: false,
38 | }
39 | },
40 | seedDB: true
41 | };
42 |
--------------------------------------------------------------------------------
/config/env/test.ts:
--------------------------------------------------------------------------------
1 | /*
2 | ======================================================================================
3 | Used when process.env.NODE_ENV is equal to 'test'
4 | ======================================================================================
5 | //This file adds config settings and overwrites config settings in the ./default.ts file
6 | //process.env.NODE_ENV is utilized in config/config.ts
7 | */
8 |
9 | export const testEnv = {
10 | port: process.env.PORT || 7001,
11 | mongo: {
12 | uri: 'mongodb://localhost/test',
13 | options: {
14 | useMongoClient: true,
15 | user: '',
16 | pass: ''
17 | },
18 | // Enable mongoose debug mode
19 | debug: process.env.MONGODB_DEBUG || false
20 | },
21 | cassandra: {
22 | contactPoints: ['127.0.0.1'],
23 | protocolOptions: { port: 9042 },
24 | queryOptions: { consistency: 1 },
25 | keyspace: 'test'
26 | },
27 | sql: {
28 | // uri: 'postgres://postgres:postgres@localhost:5432/GOATstack'
29 | database: 'test',
30 | username: 'postgres',
31 | password: 'postgres',
32 | options: {
33 | host: 'localhost',
34 | dialect: 'postgres'||'mysql'||'mariadb'||'sqlite'||'mssql',
35 | logging: false,
36 | }
37 | },
38 | seedDB: true
39 | };
40 |
--------------------------------------------------------------------------------
/config/helpers.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var del = require('del');
3 |
4 | var client = [
5 | 'client/**/**/**/**/**/*.css*',
6 | 'client/**/**/**/**/**/*.js*',
7 | 'client/**/**/**/**/**/*.shim*',
8 | 'client/**/**/**/**/**/*.ngfactory.ts',
9 | 'client/**/**/**/**/**/*.ngstyle.ts',
10 | 'client/**/**/**/**/**/*.ngsummary.json',
11 | 'ngc-aot/**',
12 | '.com*/**',
13 | '.org*/**'
14 | ];
15 |
16 | var all = client.concat([
17 | 'dist/**',
18 | 'dist/.git/**'
19 | ]);
20 |
21 | var _root = path.resolve(process.cwd());
22 |
23 | function root(args) {
24 | args = Array.prototype.slice.call(arguments, 0);
25 | return path.join.apply(path, [_root].concat(args));
26 | }
27 |
28 | function cleanup(option) {
29 | switch (option) {
30 | case 'client':
31 | return del.sync(client);
32 | break;
33 | default:
34 | return del.sync(all);
35 | break;
36 | }
37 | }
38 |
39 | exports.root = root;
40 | exports.cleanup = cleanup;
41 |
--------------------------------------------------------------------------------
/config/index.ts:
--------------------------------------------------------------------------------
1 | import * as _ from 'lodash';
2 |
3 | import {defaultConfig} from './env/default';
4 | import {devEnv} from './env/development';
5 | import {prodEnv} from './env/production';
6 | import {testEnv} from './env/test';
7 |
8 | function mergeConfig(): any {
9 |
10 | // Depending on the environment we will merge
11 | // the default assets and config to corresponding
12 | // environment files
13 | const environmentConfig = process.env.NODE_ENV === 'development' ? devEnv :
14 | process.env.NODE_ENV === 'test' ? testEnv : prodEnv;
15 |
16 | // Merge config files
17 | return _.merge(defaultConfig, environmentConfig);
18 | };
19 |
20 | const config = mergeConfig();
21 | export default config;
22 |
--------------------------------------------------------------------------------
/config/other/.sass-lint.yml:
--------------------------------------------------------------------------------
1 | rules:
2 | single-line-per-selector: 0
3 | space-after-colon: 0
4 | space-before-brace: 0
5 | property-sort-order: 0
6 | empty-args: 0
7 | indentation: 0
8 | empty-line-between-blocks: 0
9 | force-pseudo-nesting: 0
10 | pseudo-element: 0
11 | no-css-comments: 0
12 | no-empty-rulesets: 0
13 | no-important: 0
14 | no-vendor-prefixes: 0
15 | no-color-literals: 0
16 | no-color-keywords: 0
17 | no-qualifying-elements: 0
18 | no-trailing-whitespace: 0
19 | quotes: 0
20 | final-newline: 0
21 | force-element-nesting: 0
22 | no-ids: 0
23 | leading-zero: 0
24 | space-after-comma: 0
25 | space-around-operator: 0
26 | space-before-bang: 0
--------------------------------------------------------------------------------
/config/other/generate-ssl-certs.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | if [ ! -e ../../server/server.ts ]
3 | then
4 | echo "Error: could not find main application server.js file"
5 | echo "You should run the generate-ssl-certs.sh script from the main MEAN application root directory"
6 | echo "i.e: bash scripts/generate-ssl-certs.sh"
7 | exit -1
8 | fi
9 | echo "Generating self-signed certificates..."
10 | mkdir -p ../config/sslcerts
11 | openssl genrsa -out ../config/sslcerts/key.pem 4096
12 | openssl req -new -key ../config/sslcerts/key.pem -out ../config/sslcerts/csr.pem
13 | openssl x509 -req -days 365 -in ../config/sslcerts/csr.pem -signkey ../config/sslcerts/key.pem -out ../config/sslcerts/cert.pem
14 | rm ../config/sslcerts/csr.pem
15 | chmod 600 ./config/sslcerts/key.pem ../config/sslcerts/cert.pem
16 |
--------------------------------------------------------------------------------
/config/other/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rulesDirectory": [
3 | "node_modules/codelyzer"
4 | ],
5 | "rules": {
6 | "class-name": false,
7 | "eofline": false,
8 | "forin": false,
9 | "label-position": true,
10 | "member-access": false,
11 | "member-ordering": [
12 | false,
13 | "static-before-instance",
14 | "variables-before-functions"
15 | ],
16 | "no-arg": true,
17 | "no-bitwise": true,
18 | "no-console": [
19 | true,
20 | "debug",
21 | "info",
22 | "time",
23 | "timeEnd",
24 | "trace"
25 | ],
26 | "no-construct": true,
27 | "no-debugger": true,
28 | "no-duplicate-variable": true,
29 | "no-empty": false,
30 | "no-eval": true,
31 | "no-inferrable-types": false,
32 | "no-shadowed-variable": false,
33 | "no-string-literal": false,
34 | "no-switch-case-fall-through": true,
35 | "no-trailing-whitespace": false,
36 | "no-unused-expression": true,
37 | "no-use-before-declare": true,
38 | "no-var-keyword": true,
39 | "object-literal-sort-keys": false,
40 | "radix": true,
41 | "semicolon": [
42 | "always"
43 | ],
44 | "triple-equals": [
45 | true,
46 | "allow-null-check"
47 | ],
48 | "typedef-whitespace": [
49 | true,
50 | {
51 | "call-signature": "nospace",
52 | "index-signature": "nospace",
53 | "parameter": "nospace",
54 | "property-declaration": "nospace",
55 | "variable-declaration": "nospace"
56 | }
57 | ],
58 | "variable-name": false,
59 |
60 | "use-input-property-decorator": true,
61 | "use-output-property-decorator": true,
62 | "use-host-property-decorator": true,
63 | "use-life-cycle-interface": false,
64 | "use-pipe-transform-interface": true
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/config/test-libs/karma-test-shim.js:
--------------------------------------------------------------------------------
1 | Error.stackTraceLimit = Infinity;
2 |
3 | require('core-js/es6');
4 | require('core-js/es7/reflect');
5 |
6 | require('zone.js/dist/zone');
7 | require('zone.js/dist/long-stack-trace-zone');
8 | require('zone.js/dist/proxy');
9 | require('zone.js/dist/sync-test');
10 | require('zone.js/dist/jasmine-patch');
11 | require('zone.js/dist/async-test');
12 | require('zone.js/dist/fake-async-test');
13 |
14 | var appContext = require.context('../../client', true, /\.spec\.ts/);
15 |
16 | appContext.keys().forEach(appContext);
17 |
18 | var testing = require('@angular/core/testing');
19 | var browser = require('@angular/platform-browser-dynamic/testing');
20 |
21 | testing.TestBed.initTestEnvironment(browser.BrowserDynamicTestingModule, browser.platformBrowserDynamicTesting());
--------------------------------------------------------------------------------
/config/test-libs/karma.config.js:
--------------------------------------------------------------------------------
1 | var webpackConfig = require('../webpack/webpack.common')({ env: 'karma' });
2 |
3 | module.exports = function (config) {
4 |
5 | var _config = {
6 | basePath: '../../',
7 |
8 | frameworks: ['jasmine'],
9 |
10 | plugins: [
11 | require('karma-jasmine'),
12 | require('karma-webpack'),
13 | require('karma-sourcemap-loader'),
14 | require('karma-chrome-launcher'),
15 | require('karma-mocha-reporter'),
16 | require('karma-jasmine-html-reporter'), // click "Debug" in browser to see it
17 | require('karma-htmlfile-reporter') // crashing w/ strange socket error
18 | ],
19 |
20 | customLaunchers: {
21 | // From the CLI. Not used here but interesting
22 | // chrome setup for travis CI using chromium
23 | Chrome_travis_ci: {
24 | base: 'Chrome',
25 | flags: ['--no-sandbox']
26 | }
27 | },
28 |
29 | files: [
30 | {pattern: './config/test-libs/karma-test-shim.js', watched: false}
31 | ],
32 |
33 | preprocessors: {
34 | './config/test-libs/karma-test-shim.js': ['webpack', 'sourcemap']
35 | },
36 |
37 | webpack: webpackConfig,
38 |
39 | webpackMiddleware: {
40 | stats: "none"
41 | },
42 |
43 | webpackServer: {
44 | noInfo: true
45 | },
46 |
47 | reporters: ['kjhtml', 'mocha'],
48 | port: 9876,
49 | colors: true,
50 | logLevel: config.LOG_INFO,
51 | autoWatch: false,
52 | browsers: ['Chrome'],
53 | singleRun: true
54 | };
55 |
56 | config.set(_config);
57 | };
--------------------------------------------------------------------------------
/config/test-libs/protractor.config.js:
--------------------------------------------------------------------------------
1 | // FIRST TIME ONLY- run:
2 | // ./node_modules/.bin/webdriver-manager update
3 | //
4 | // Try: `npm run webdriver:update`
5 | //
6 | // AND THEN EVERYTIME ...
7 | // 1. Compile with `tsc`
8 | // 2. Make sure the test server (e.g., http-server: localhost:8080) is running.
9 | // 3. ./node_modules/.bin/protractor protractor.config.js
10 | //
11 | // To do all steps, try: `npm run e2e`
12 |
13 | var helpers = require('../helpers');
14 |
15 |
16 | exports.config = {
17 | directConnect: true,
18 |
19 | // For angular tests
20 | useAllAngular2AppRoots: true,
21 |
22 | // Base URL for application server
23 | baseUrl: 'http://localhost:7001',
24 |
25 | // Spec patterns are relative to this config file
26 | specs: [
27 | helpers.root('e2e/*.e2e-spec.js')
28 | ],
29 |
30 | // Capabilities to be passed to the webdriver instance.
31 | capabilities: {
32 | 'browserName': 'chrome'
33 | },
34 |
35 | // Framework to use. Jasmine is recommended.
36 | framework: 'jasmine',
37 |
38 | allScriptsTimeout: 110000,
39 |
40 | onPrepare: function () {
41 | browser.ignoreSynchronization = true;
42 | browser.get('');
43 |
44 | // SpecReporter
45 | var SpecReporter = require('jasmine-spec-reporter').SpecReporter;
46 | jasmine.getEnv().addReporter(new SpecReporter({displayStacktrace: 'all'}));
47 | },
48 |
49 | jasmineNodeOpts: {
50 | showTiming: true,
51 | showColors: true,
52 | isVerbose: false,
53 | includeStackTrace: false,
54 | defaultTimeoutInterval: 40000,
55 | print: function() {}
56 | }
57 | };
58 |
--------------------------------------------------------------------------------
/config/test-libs/server.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var exec = require('child_process').exec;
4 | var glob = require('glob');
5 |
6 | var Jasmine = require('jasmine');
7 | var jasmine = new Jasmine();
8 | var JasmineReporter = require('jasmine-spec-reporter').SpecReporter;
9 |
10 | process.env.NODE_ENV = 'test';
11 |
12 | // Server SPEC tests
13 | glob('server/**/api/**', function(er, files) {
14 | exec('tsc ' + files.join(' ') + ' --outDir dist', () => {
15 |
16 | jasmine.loadConfig({
17 | spec_dir: 'dist',
18 | spec_files: [
19 | 'server/mongo-db/api/**/*.spec.js',
20 | 'server/mongo-db/api/user/user.integration.js',
21 | 'server/mongo-db/api/**/*.integration.js'
22 | ]
23 | });
24 |
25 | jasmine.env.clearReporters();
26 | jasmine.addReporter(new JasmineReporter());
27 |
28 | jasmine.execute();
29 |
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/config/webpack/webpack.client.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack');
2 | var webpackMerge = require('webpack-merge');
3 | var ExtractTextPlugin = require('extract-text-webpack-plugin');
4 | var WebpackShellPlugin = require('webpack-shell-plugin');
5 | var CopyWebpackPlugin = require('copy-webpack-plugin');
6 | var commonConfig = require('./webpack.common.js');
7 | var chalk = require('chalk');
8 |
9 | const helpers = require('../helpers');
10 |
11 | const cmd = require('../scripts').cmd;
12 |
13 | process.noDeprecation = true;
14 |
15 | const generalConfig = {
16 |
17 | // Specify descriptions for all webpack environments
18 | devtool: {
19 | dev: 'cheap-module-eval-source-map',
20 | prod: 'source-map',
21 | test: 'cheap-module-eval-source-map'
22 | },
23 |
24 | output: {
25 | dev: {
26 | path: helpers.root('dist/client'),
27 | publicPath: 'http://localhost:1701/',
28 | filename: '[name].js',
29 | chunkFilename: '[id].chunk.js'
30 | },
31 | prod: {
32 | path: helpers.root('dist/client'),
33 | filename: '[name].js',
34 | chunkFilename: '[id].chunk.js'
35 | },
36 | test: {
37 | path: helpers.root('dist/client'),
38 | publicPath: 'http://localhost:7001/',
39 | filename: '[name].js',
40 | chunkFilename: '[id].chunk.js'
41 | }
42 | },
43 |
44 | devServer: {
45 | dev: {
46 | port: 1701,
47 | historyApiFallback: {
48 | index: 'http://localhost:1701/index.html'
49 | },
50 | proxy: [{
51 | context: ['/api', '/auth', '/socket.io-client'],
52 | target: 'http://localhost:5000/',
53 | secure: false
54 | }],
55 | stats: {
56 | chunks: false
57 | }
58 | },
59 | prod: {},
60 | test: {}
61 | },
62 |
63 | stats: {
64 | dev: {},
65 | prod: {},
66 | test: 'none'
67 | }
68 | };
69 |
70 | module.exports = function(options) {
71 |
72 |
73 | return webpackMerge(commonConfig(options), {
74 | devtool: generalConfig.devtool[options.env],
75 | output: generalConfig.output[options.env],
76 | devServer: generalConfig.devServer[options.env],
77 | stats: generalConfig.stats[options.env],
78 |
79 | plugins: options.env === 'dev' ? [
80 | new ExtractTextPlugin('styles.css'),
81 | new WebpackShellPlugin({
82 | onBuildStart:[`${cmd.webpack} --hide-modules true --env server:dev --watch`],
83 | onBuildEnd:[`${cmd.nodemon} dist --watch dist`]
84 | })
85 | ] : options.env === 'test' ? [
86 | new ExtractTextPlugin('styles.css')
87 | ] : [
88 | new webpack.NoEmitOnErrorsPlugin(),
89 | new webpack.optimize.UglifyJsPlugin({
90 | mangle: {
91 | keep_fnames: true
92 | }
93 | }),
94 | new ExtractTextPlugin('styles.css'),
95 | new WebpackShellPlugin({
96 | onBuildStart:[`${cmd.webpack} --hide-modules true --env server:prod${ options.e2e ? ':e2e' : '' }`]
97 | }),
98 | new CopyWebpackPlugin([
99 | {
100 | from: helpers.root('package.json'),
101 | to: helpers.root('dist'),
102 | transform: (content, path) => {
103 | return content.toString().replace(/npm run dev/, 'node index');
104 | }
105 | },
106 | {
107 | from: helpers.root('public'),
108 | to: helpers.root('dist/public')
109 | }
110 | ])
111 | ]
112 | });
113 | }
--------------------------------------------------------------------------------
/config/webpack/webpack.common.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack');
2 | var HtmlWebpackPlugin = require('html-webpack-plugin');
3 | var ExtractTextPlugin = require('extract-text-webpack-plugin');
4 | var nodeExternals = require('webpack-node-externals');
5 |
6 | var helpers = require('../helpers');
7 |
8 | module.exports = function(options) {
9 | const prod = options.env === 'prod';
10 |
11 | var config = {
12 | entry: {
13 | 'polyfills': './client/polyfills.ts',
14 | 'vendor': './client/vendor.ts',
15 | 'app': './client/app.ts'
16 | },
17 |
18 | module: {
19 | rules: [
20 | {
21 | test: /component\.ts/,
22 | loader: 'string-replace-loader',
23 | query: {
24 | search: '.css',
25 | replace: '.scss'
26 | }
27 | },
28 | {
29 | test: /\.ts$/,
30 | use: ['awesome-typescript-loader', 'angular2-template-loader']
31 | },
32 | {
33 | test: /\.html$/,
34 | loader: 'html-loader?-attrs'
35 | },
36 | {
37 | test: /\.(png|svg|jpg)$/,
38 | loader: 'file-loader',
39 | query: {
40 | 'name': 'public/assets/[name].[ext]'
41 | }
42 | },
43 | {
44 | test: /\.scss/,
45 | include: [helpers.root('client/styles.scss'), helpers.root('client/loader.scss')],
46 | loader: ExtractTextPlugin.extract({
47 | fallback: 'style-loader',
48 | use: 'css-loader?sourceMap!sass-loader?sourceMap'
49 | })
50 | },
51 | {
52 | test: /\.scss/,
53 | exclude: [helpers.root('client/styles.scss'), helpers.root('client/loader.scss')],
54 | loader: 'to-string-loader!css-loader?sourceMap!sass-loader?sourceMap'
55 | },
56 | ]
57 | },
58 |
59 | resolve: {
60 | extensions: ['.ts', '.js', '.scss']
61 | },
62 |
63 | plugins: [
64 | new webpack.optimize.CommonsChunkPlugin({
65 | name: ['app', 'vendor', 'polyfills']
66 | }),
67 |
68 | new HtmlWebpackPlugin({
69 | template: 'client/index.html'
70 | }),
71 |
72 | new webpack.ContextReplacementPlugin( // fixes angular linker WARNING
73 | /angular(\\|\/)core(\\|\/)@angular/,
74 | helpers.root('src')
75 | )
76 | ]
77 | };
78 |
79 | if (prod) {
80 | config.entry.app = './client/app-aot.ts';
81 |
82 | config.module.rules[5] = {
83 | test: /\.css$/,
84 | exclude: [helpers.root('client/styles.css'), helpers.root('client/loader.css')],
85 | loader: 'raw-loader'
86 | };
87 |
88 | config.module.rules.splice(0,1);
89 | }
90 |
91 | if (options.env === 'karma') {
92 | delete config.entry;
93 | // delete config.entry.polyfills;
94 | delete config.plugins;
95 | config.devtool = 'inline-source-map';
96 | config.stats = { warnings: false };
97 |
98 | config.module.rules[3].loader = 'null-loader';
99 | config.module.rules[4].loader = 'null-loader';
100 | }
101 |
102 | return config;
103 | }
--------------------------------------------------------------------------------
/config/webpack/webpack.server.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack');
2 | var nodeExternals = require('webpack-node-externals');
3 | var WebpackShellPlugin = require('webpack-shell-plugin');
4 |
5 | const helpers = require('../helpers');
6 | const cmd = require('../scripts').cmd;
7 |
8 | module.exports = function(options) {
9 |
10 | const ENV = process.env.ENV = process.env.NODE_ENV = options.env === 'dev' ? 'development' :
11 | options.env === 'prod' ? 'production' : 'test';
12 | const METADATA = {
13 | ENV: ENV,
14 | };
15 |
16 | return {
17 | entry: {
18 | 'server': './server/server.ts',
19 | },
20 |
21 | output: {
22 | path: helpers.root('dist'),
23 | filename: 'index.js'
24 | },
25 |
26 | stats: 'none',
27 | target: 'node',
28 | externals: [nodeExternals()],
29 |
30 | module: {
31 | rules: [
32 | {
33 | test: /\.ts$/,
34 | loader: 'awesome-typescript-loader'
35 | }
36 | ]
37 | },
38 |
39 | resolve: {
40 | extensions: ['.ts', '.js']
41 | },
42 |
43 | plugins: options.env === 'dev' ? [
44 | // Dev Plugins
45 | new webpack.DefinePlugin({
46 | 'ENV': JSON.stringify(METADATA.ENV),
47 | 'process.env': {
48 | 'ENV': JSON.stringify(METADATA.ENV),
49 | 'NODE_ENV': JSON.stringify(METADATA.ENV)
50 | }
51 | }),
52 | new WebpackShellPlugin({
53 | // onBuildEnd:[`${cmd.webpackDevServer} --inline --env dev`]
54 | })
55 | ] : options.env === 'test' ? [
56 | // Test Plugins
57 | new webpack.DefinePlugin({
58 | 'ENV': JSON.stringify(METADATA.ENV),
59 | 'process.env': {
60 | 'ENV': JSON.stringify(METADATA.ENV),
61 | 'NODE_ENV': JSON.stringify(METADATA.ENV)
62 | }
63 | })
64 | ] : options.env === 'prod' ? [
65 | // Prod Plugins
66 | new webpack.optimize.UglifyJsPlugin({
67 | mangle: {
68 | keep_fnames: true
69 | }
70 | }),
71 | ] : [
72 | new webpack.optimize.UglifyJsPlugin({
73 | mangle: {
74 | keep_fnames: true
75 | }
76 | }),
77 | new webpack.DefinePlugin({
78 | 'ENV': JSON.stringify(METADATA.ENV),
79 | 'process.env': {
80 | 'ENV': JSON.stringify(METADATA.ENV),
81 | 'NODE_ENV': JSON.stringify(METADATA.ENV)
82 | }
83 | })
84 | ]
85 | };
86 | }
--------------------------------------------------------------------------------
/e2e/app.e2e-spec.js:
--------------------------------------------------------------------------------
1 | describe('App E2E Tests', function () {
2 |
3 | var EC = protractor.ExpectedConditions;
4 | var defaultConfig = eval(require('typescript')
5 | .transpile(require('graceful-fs')
6 | .readFileSync('./config/env/default.ts')
7 | .toString()));
8 |
9 | it('should contain correct title tag', function () {
10 | expect(browser.getTitle()).toEqual("GOATstack");
11 | });
12 |
13 | it('should contain correct favicon', function () {
14 | expect(element(by.id('favicon')).getAttribute('href'))
15 | .toEqual('http://localhost:7001/public/assets/favicon.png');
16 | });
17 |
18 | it('should contain correct meta description', function () {
19 | expect(element(by.name('description')).getAttribute('content')).toEqual("The Greatest of All Time Stack!");
20 | });
21 |
22 | it('should contain correct meta keywords', function () {
23 | expect(element(by.name('keywords')).getAttribute('content'))
24 | .toEqual("redux, node, mongo, express, angular2, ng2, jasmine, karma, protractor");
25 | });
26 |
27 | });
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "goat-stack",
3 | "version": "4.3.0",
4 | "description": "A MEAN stack boilerplate using Angular 2",
5 | "engines": {
6 | "node": "8.10.1"
7 | },
8 | "scripts": {
9 | "start": "npm run dev",
10 | "test": "node -e \"require('./config/scripts').startTest()\"",
11 | "dev": "node -e \"require('./config/scripts').startDev()\"",
12 | "prod": "node -e \"require('./config/scripts').startProd()\"",
13 | "e2e": "node -e \"require('./config/scripts').startE2E()\"",
14 | "e2e:prod": "node -e \"require('./config/scripts').startProdE2E()\"",
15 | "deploy:heroku": "node -e \"require('./config/scripts').herokuPrompt()\"",
16 | "cleanup": "node -e \"require('./config/helpers').cleanup()\""
17 | },
18 | "dependencies": {
19 | "@angular-redux/store": "^6.5.7",
20 | "@angular/animations": "^5.0.1",
21 | "@angular/cdk": "^2.0.0-beta.12",
22 | "@angular/common": "^5.0.1",
23 | "@angular/compiler": "^5.0.1",
24 | "@angular/compiler-cli": "^5.0.1",
25 | "@angular/core": "^5.0.1",
26 | "@angular/http": "^5.0.1",
27 | "@angular/forms": "^5.0.1",
28 | "@angular/platform-browser": "^5.0.1",
29 | "@angular/platform-browser-dynamic": "^5.0.1",
30 | "@angular/platform-server": "^5.0.1",
31 | "@angular/router": "^5.0.1",
32 | "body-parser": "^1.18.2",
33 | "bootstrap": "3.3.7",
34 | "cassandra-driver": "^3.3.0",
35 | "chalk": "2.0.1",
36 | "composable-middleware": "0.3.0",
37 | "connect-mongo": "2.0.0",
38 | "cookie-parser": "1.4.3",
39 | "core-js": "2.5.1",
40 | "express": "^4.16.2",
41 | "express-jwt": "^5.3.0",
42 | "express-session": "^1.15.6",
43 | "graceful-fs": "4.1.11",
44 | "hammerjs": "2.0.8",
45 | "immutable": "3.8.2",
46 | "jsonwebtoken": "^8.1.0",
47 | "lodash": "4.17.4",
48 | "method-override": "^2.3.10",
49 | "mongodb": "^2.2.33",
50 | "mongoose": "^4.12.5",
51 | "morgan": "^1.9.0",
52 | "ng2-cookies": "^1.0.12",
53 | "passport": "0.4.0",
54 | "passport-facebook": "2.1.1",
55 | "passport-google-oauth20": "1.0.0",
56 | "passport-local": "1.0.0",
57 | "pg": "^7.3.0",
58 | "preboot": "^5.1.7",
59 | "redux": "^3.7.2",
60 | "redux-localstorage": "0.4.1",
61 | "reflect-metadata": "^0.1.10",
62 | "rxjs": "^5.5.2",
63 | "sequelize": "4.20.1",
64 | "serialize-javascript": "^1.4.0",
65 | "socket.io": "^2.0.4",
66 | "socket.io-client": "^2.0.4",
67 | "socketio-jwt": "4.5.0",
68 | "typescript": "2.6.1",
69 | "zone.js": "^0.8.18"
70 | },
71 | "devDependencies": {
72 | "@types/body-parser": "^1.16.7",
73 | "@types/cassandra-driver": "^3.2.1",
74 | "@types/chalk": "2.2.0",
75 | "@types/cookie-parser": "^1.4.1",
76 | "@types/core-js": "^0.9.43",
77 | "@types/express": "^4.0.39",
78 | "@types/express-session": "^1.15.5",
79 | "@types/glob": "5.0.33",
80 | "@types/graceful-fs": "4.1.1",
81 | "@types/hammerjs": "2.0.35",
82 | "@types/jasmine": "^2.6.2",
83 | "@types/jsonwebtoken": "^7.2.3",
84 | "@types/lodash": "^4.14.80",
85 | "@types/method-override": "^0.0.31",
86 | "@types/mongodb": "^2.2.15",
87 | "@types/mongoose": "^4.7.24",
88 | "@types/morgan": "^1.7.35",
89 | "@types/node": "^8.0.47",
90 | "@types/passport": "^0.3.5",
91 | "@types/passport-local": "^1.0.32",
92 | "@types/proxyquire": "1.3.28",
93 | "@types/sequelize": "^4.0.78",
94 | "@types/sinon": "^2.3.7",
95 | "@types/socket.io": "^1.4.31",
96 | "@types/socket.io-client": "1.4.31",
97 | "@types/supertest": "^2.0.3",
98 | "angular2-template-loader": "^0.6.2",
99 | "awesome-typescript-loader": "^3.2.3",
100 | "canonical-path": "0.0.2",
101 | "concurrently": "^3.5.0",
102 | "copy-webpack-plugin": "4.2.0",
103 | "css-loader": "^0.28.7",
104 | "del": "^3.0.0",
105 | "extract-loader": "1.0.1",
106 | "extract-text-webpack-plugin": "^3.0.2",
107 | "file-loader": "^1.1.5",
108 | "glob": "^7.1.2",
109 | "html-loader": "^0.5.1",
110 | "html-webpack-plugin": "^2.30.1",
111 | "inquirer": "^3.3.0",
112 | "jasmine": "^2.8.0",
113 | "jasmine-core": "^2.8.0",
114 | "jasmine-sinon": "0.4.0",
115 | "jasmine-spec-reporter": "^4.2.1",
116 | "karma": "^1.7.1",
117 | "karma-chrome-launcher": "^2.2.0",
118 | "karma-cli": "1.0.1",
119 | "karma-htmlfile-reporter": "0.3.5",
120 | "karma-jasmine": "1.1.0",
121 | "karma-jasmine-html-reporter": "0.2.2",
122 | "karma-mocha-reporter": "^2.2.5",
123 | "karma-remap-istanbul": "^0.6.0",
124 | "karma-sourcemap-loader": "0.3.7",
125 | "karma-webpack": "^2.0.5",
126 | "node-sass": "^4.5.3",
127 | "nodemon": "1.12.1",
128 | "null-loader": "0.1.1",
129 | "protractor": "^5.2.0",
130 | "proxyquire": "^1.8.0",
131 | "raw-loader": "0.5.1",
132 | "redux-logger": "^3.0.6",
133 | "sass-lint": "1.12.1",
134 | "sass-loader": "^6.0.6",
135 | "sinon": "^4.0.2",
136 | "string-replace-loader": "^1.3.0",
137 | "style-loader": "^0.19.0",
138 | "supertest": "^3.0.0",
139 | "to-string-loader": "1.1.5",
140 | "webpack": "^3.8.1",
141 | "webpack-dev-server": "^2.9.3",
142 | "webpack-merge": "^4.1.0",
143 | "webpack-node-externals": "^1.6.0",
144 | "webpack-shell-plugin": "0.5.0"
145 | },
146 | "repository": {
147 | "type": "git",
148 | "url": "https://github.com/projectSHAI/GOAT-stack"
149 | },
150 | "keywords": [
151 | "node",
152 | "heroku",
153 | "express",
154 | "mongodb",
155 | "OAuth",
156 | "GOAT-stack"
157 | ],
158 | "license": "MIT"
159 | }
160 |
--------------------------------------------------------------------------------
/public/assets/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projectSHAI/GOATstack/d6bee4b8f6efb0c521a6a83a7ff10aace8a7643e/public/assets/favicon.png
--------------------------------------------------------------------------------
/public/assets/footer.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projectSHAI/GOATstack/d6bee4b8f6efb0c521a6a83a7ff10aace8a7643e/public/assets/footer.jpg
--------------------------------------------------------------------------------
/public/assets/loader/fire-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projectSHAI/GOATstack/d6bee4b8f6efb0c521a6a83a7ff10aace8a7643e/public/assets/loader/fire-1.png
--------------------------------------------------------------------------------
/public/assets/loader/fire-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projectSHAI/GOATstack/d6bee4b8f6efb0c521a6a83a7ff10aace8a7643e/public/assets/loader/fire-2.png
--------------------------------------------------------------------------------
/public/assets/loader/space-goat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projectSHAI/GOATstack/d6bee4b8f6efb0c521a6a83a7ff10aace8a7643e/public/assets/loader/space-goat.png
--------------------------------------------------------------------------------
/public/assets/loader/star.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projectSHAI/GOATstack/d6bee4b8f6efb0c521a6a83a7ff10aace8a7643e/public/assets/loader/star.png
--------------------------------------------------------------------------------
/public/assets/loader/star.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/public/assets/octocat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projectSHAI/GOATstack/d6bee4b8f6efb0c521a6a83a7ff10aace8a7643e/public/assets/octocat.png
--------------------------------------------------------------------------------
/public/fonts/Fredoka_One.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/projectSHAI/GOATstack/d6bee4b8f6efb0c521a6a83a7ff10aace8a7643e/public/fonts/Fredoka_One.woff2
--------------------------------------------------------------------------------
/server/cassandra-db/api/user/prepared.statements.ts:
--------------------------------------------------------------------------------
1 | ////////////////////////
2 | // Prepared statements//
3 | ////////////////////////
4 | const Uuid = require('cassandra-driver').types.Uuid;
5 |
6 | class UserStmts {
7 |
8 | // create tables
9 | public userTable: string = `CREATE TABLE IF NOT EXISTS users (
10 | id uuid,
11 | email text,
12 | created timestamp,
13 | password text,
14 | salt text,
15 | facebook text,
16 | firstname text,
17 | github text,
18 | google text,
19 | lastname text,
20 | middlename text,
21 | role text,
22 | username text,
23 | PRIMARY KEY (email)
24 | );`;
25 |
26 | // delete tables
27 | public truncateUserTable: string = `TRUNCATE users`;
28 |
29 | ///////////////////////
30 | // Seeding ////////////
31 | ///////////////////////
32 | // seed statements
33 | public seedUserTable: Array<{ query: string, params: Array }> = [{
34 | query: 'INSERT INTO users (id, email, created, password, salt, role, username ) VALUES (?, ?, ?, ?, ?, ?, ?)',
35 | params: [Uuid.random(), 'admin@admin.com', Date.now(), 'fUnz3sNJaiLSotLsOX0kqBuYD9MH9lotMyAdBtbCyPBnFToAABMPqxv4kZ/E16gk/zp6/rtBEOnQZsPSnS1LmQ==', 'Lv1oeSSHMut0kKRFFDyk5g==', 'admin', 'AdMiN']
36 | },
37 | {
38 | query: 'INSERT INTO users (id, email, created, password, salt, role, username ) VALUES (?, ?, ?, ?, ?, ?, ?)',
39 | params: [Uuid.random(), 'test@test.com', Date.now(), 'JOe+CGVaNXUK2wZuOLzhpiCfXO8K/18R5mhoE5ji5MGcxMF/otA3QaLeMa9ELw0W8zyr0VvQbW9NHpA350MGbg==', '61DynVS8QOWjMy7bRdkUtw==', 'test', 'test']
40 | }];
41 |
42 | ////////////
43 | // queries//
44 | ////////////
45 |
46 | // create
47 | public insertRow: string = `INSERT INTO users (id, email, created, password, salt, role, username ) VALUES (?, ?, ?, ?, ?, ?, ?)`;
48 | // read
49 | public findByEmail: string = 'SELECT email, firstname, lastname, middlename, role, username FROM users WHERE email = ?';
50 | public allRows: string = 'SELECT email, firstname, lastname, middlename, role, username FROM users';
51 | // update - NA
52 | // delete - NA
53 |
54 | }
55 |
56 | export default new UserStmts;
--------------------------------------------------------------------------------
/server/cassandra-db/api/user/user.controller.ts:
--------------------------------------------------------------------------------
1 | import UserModel from './user.model';
2 | import { client } from '../../../cassandra-db';
3 | import config from '../../../../config';
4 | import * as jwt from 'jsonwebtoken';
5 |
6 | // Handles status codes and error message json
7 | // specificity: error
8 | function handleError(res, err) {
9 | if (err) {
10 | res.status(500).json(err);
11 | }
12 | }
13 |
14 | function validationError(res, err) {
15 | if (err) {
16 | res.status(422).json(err);
17 | }
18 | }
19 |
20 | export function index(req, res) {
21 | let users = [];
22 | return UserModel.allUsers()
23 | .then(result => {
24 | users = result.rows
25 | res.json(users);
26 | })
27 | .catch(err => {
28 | handleError(res, err)
29 | });
30 | }
31 |
32 | export function show(req, res, next) {
33 | const userEmail = req.params.email;
34 |
35 | return UserModel.userByEmail(userEmail)
36 | .then(result => {
37 | if (!result) {
38 | return res.status(404).end();
39 | }
40 | res.json({
41 | username: result.rows[0].username,
42 | firstname: result.rows[0].firstname,
43 | lastname: result.rows[0].lastname
44 | });
45 | })
46 | .catch(err => {
47 | handleError(res, err)
48 | });;
49 | }
50 |
51 | export function changePassword(req, res) {
52 | const userEmail = req.user.email;
53 | const oldPass = String(req.body.oldPassword);
54 | const newPass = String(req.body.newPassword);
55 |
56 | return UserModel.updatePassword(userEmail, oldPass, newPass, res)
57 | .then((result) => {
58 | res.status(204).end();
59 | })
60 | .catch(err => {
61 | validationError(res, err);
62 | });
63 | }
64 |
65 | export function create(req, res, next) {
66 | const user = req.body;
67 | return UserModel.userByEmail(user.email).then(result => {
68 | if (result.rows[0] === undefined) {
69 | return UserModel.insertUser(user.email, user.username, user.password)
70 | .then(result => {
71 | const token = jwt.sign(
72 | {
73 | email: user.email,
74 | role: user.role
75 | },
76 | config.sessionSecret,
77 | { expiresIn: 60 * 60 * 5 });
78 |
79 | req.headers.token = token;
80 | req.user = user;
81 | next();
82 |
83 | })
84 | .catch(err => {
85 | validationError(res, err)
86 | });
87 | }
88 | else {
89 | const duplicate: object = { message: 'Email is already in use!' };
90 |
91 | validationError(res, duplicate);
92 | return res.status(403).json(duplicate);
93 | }
94 | }).catch();
95 |
96 | }
97 |
98 | export function me(req, res, next) {
99 | const token = req.headers.token;
100 | const userEmail = req.user.email;
101 |
102 | return UserModel.userByEmail(userEmail)
103 | .then(result => {
104 | const user = result.rows[0];
105 | if (!user) return res.status(401).json({ message: 'User does not exist' });
106 |
107 | if (token) res.json({ token, user });
108 | else res.json(user);
109 | })
110 | .catch(err => next(err));
111 | }
--------------------------------------------------------------------------------
/server/cassandra-db/api/user/user.integration.ts:
--------------------------------------------------------------------------------
1 | import app from '../../../server';
2 | import request = require('supertest');
3 | import { client } from '../../../cassandra-db';
4 | import DbModel from '../../db.model';
5 | import UserModel from './user.model';
6 |
7 | // User Endpoint testing
8 | describe('User API:', function () {
9 | let user;
10 | let token;
11 |
12 | // users are cleared from DB seeding
13 | // add a new testing user
14 | beforeAll(done => {
15 | UserModel.userByEmail('test@test.com')
16 | .then(result => {
17 | user = result.rows[0];
18 | done();
19 | })
20 | .catch(err => {
21 | expect(err).not.toBeDefined();
22 | done();
23 | });
24 | });
25 |
26 |
27 | // Encapsolate GET me enpoint
28 | describe('GET /api/users/me cassandra', function () {
29 |
30 | // before every 'it' get new OAuth token representing the user
31 | beforeAll(function (done) {
32 | setTimeout(() => request(app)
33 | .post('/auth/local')
34 | .send({
35 | email: 'test@test.com',
36 | password: 'test1'
37 | })
38 | .expect(200)
39 | .end((err, res) => {
40 | if (err) {
41 | done.fail(err);
42 | } else {
43 | token = res.body.token;
44 | done();
45 | }
46 | }), 2000);
47 | });
48 |
49 | // If the token was properly set inside the header of the request
50 | // it should respond with a 200 status code with the user json
51 | it('should respond with a user profile when authenticated', function (done) {
52 | request(app)
53 | .get('/api/users/me')
54 | .set('authorization', 'Bearer ' + token)
55 | .expect(200)
56 | .expect('Content-Type', /json/)
57 | .end((err, res) => {
58 | if (err) {
59 | done.fail(err);
60 | } else {
61 | expect(res.body.username).toEqual(user.username);
62 | expect(res.body.email).toEqual(user.email);
63 | done();
64 | }
65 | });
66 | });
67 |
68 | // If the token was improperly / not set to the header
69 | // status code 401 should be thrown
70 | it('should respond with a 401 when not authenticated', function (done) {
71 | request(app)
72 | .get('/api/users/me')
73 | .expect(401)
74 | .end((err, res) => {
75 | if (err) {
76 | done.fail(err);
77 | } else {
78 | done();
79 | }
80 | });
81 | });
82 | });
83 | });
84 |
--------------------------------------------------------------------------------
/server/cassandra-db/api/user/user.model.ts:
--------------------------------------------------------------------------------
1 | import { client } from '../../../cassandra-db';
2 | import UserStmts from './prepared.statements';
3 |
4 | const Uuid = require('cassandra-driver').types.Uuid,
5 | crypto = require('crypto');
6 | // Define Prepared Statments
7 | const allRows: string = UserStmts.allRows,
8 | findByEmail: string = UserStmts.findByEmail,
9 | insertRow: string = UserStmts.insertRow;
10 |
11 |
12 | class UserModel {
13 |
14 | private password: string;
15 | private queryOptions: object = { prepared: true };
16 | private updatePw: string = 'UPDATE users SET password = ?, salt = ? WHERE email = ?';
17 | private credentials: string = 'SELECT email, firstname, lastname, middlename, role, username, password, salt FROM users WHERE email = ?'
18 |
19 | /*
20 | Auth
21 | */
22 |
23 | makeSalt(byteSize?: number): any {
24 | let defaultByteSize = 16;
25 | if (!byteSize) {
26 | byteSize = defaultByteSize;
27 | }
28 | return crypto.randomBytes(byteSize).toString('base64');
29 | }
30 |
31 | encryptPassword(password: string, salt: string): any {
32 | const saltBuffer = new Buffer(salt, 'base64');
33 | const defaultIterations = 10000;
34 | const defaultKeyLength = 64;
35 |
36 | return crypto.pbkdf2Sync(password, saltBuffer, defaultIterations, defaultKeyLength, 'sha512').toString('base64');
37 |
38 | };
39 |
40 | randNum(min, max) {
41 | min = Math.ceil(min);
42 | max = Math.floor(max);
43 | return Math.floor(Math.random() * (max - min)) + min; //The maximum is exclusive and the minimum is inclusive
44 | }
45 |
46 | authenticate(dbPassword: string, dbSalt: string, providedPassword: string) {
47 | return this.encryptPassword(providedPassword, dbSalt) === dbPassword;
48 | };
49 |
50 | /*
51 | Queries
52 | */
53 | allUsers(): Promise {
54 | return client.execute(allRows, undefined, this.queryOptions);
55 | }
56 |
57 | userByEmail(email: string): Promise {
58 | return client.execute(findByEmail, [email], this.queryOptions);
59 | }
60 |
61 | getCredentials(email: string): Promise {
62 | return client.execute(this.credentials, [email], this.queryOptions);
63 | }
64 |
65 | updatePassword(email: string, oldPW: string, newPass: string, res): Promise {
66 |
67 | return this.getCredentials(email).then(result => {
68 | const dbPW: string = result.rows[0].password;
69 | const dbSalt: string = result.rows[0].salt;
70 | if (this.authenticate(dbPW, dbSalt, oldPW)) {
71 | const byteSize: number = 16;
72 | const newSalt: string = this.makeSalt(byteSize);
73 | const newHashedPW = this.encryptPassword(newPass, newSalt);
74 | return client.execute(this.updatePw, [newHashedPW, newSalt, email], this.queryOptions);
75 | }
76 | else {
77 | res.status(403).end();
78 | }
79 | }).catch(err => console.error(err));
80 |
81 | }
82 |
83 | insertUser(email: string, username: string, password: string): Promise {
84 |
85 | const byteSize: number = 16;
86 | const id: string = String(Uuid.random());
87 | const salt: string = this.makeSalt(byteSize);
88 | const newHashedPW = this.encryptPassword(password, salt);
89 | const queryOptions = {
90 | prepared: true
91 | };
92 | return client.execute(insertRow, [id, email, Date.now(), newHashedPW, salt, 'user', username], queryOptions);
93 | }
94 |
95 | }
96 |
97 | export default new UserModel;
--------------------------------------------------------------------------------
/server/cassandra-db/api/user/user.router.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * GET /api/user -> allUsers
3 | * POST /api/user -> create
4 | * GET /api/user/:email -> show
5 | * PUT /api/user/:email/password -> changePassword
6 | * DELETE /api/user/:email -> destroy
7 | */
8 |
9 | let express = require('express');
10 | import * as auth from '../../auth/auth.service';
11 | import * as UserController from './user.controller';
12 |
13 | let router = express.Router();
14 |
15 | router.get('/', auth.hasRole('admin'), UserController.index);
16 | router.put('/:email/password', auth.isAuthenticated(), UserController.changePassword);
17 |
18 | router.post('/', UserController.create, UserController.me);
19 | router.get('/me', auth.isAuthenticated(), UserController.me);
20 | router.get('/:email', auth.isAuthenticated(), UserController.show);
21 |
22 | export { router as userRoutes };
23 |
--------------------------------------------------------------------------------
/server/cassandra-db/api/user/user.spec.ts:
--------------------------------------------------------------------------------
1 | import proxyquire = require('proxyquire');
2 | let pq = proxyquire.noPreserveCache();
3 | import sinon = require('sinon');
4 |
5 | // userCtrlStub is used to mimic the router
6 | let userCtrlStub = {
7 | index: 'userCtrl.index',
8 | me: 'userCtrl.me',
9 | changePassword: 'userCtrl.changePassword',
10 | show: 'userCtrl.show',
11 | create: 'userCtrl.create'
12 | };
13 |
14 | // mimic teh auth service
15 | let authServiceStub = {
16 | isAuthenticated() {
17 | return 'authService.isAuthenticated';
18 | },
19 | hasRole(role) {
20 | return 'authService.hasRole.' + role;
21 | }
22 | };
23 |
24 | // routerStub spys on http RESTFUL requests
25 | let routerStub = {
26 | get: sinon.spy(),
27 | put: sinon.spy(),
28 | post: sinon.spy()
29 | };
30 |
31 | // require the index with our stubbed out modules
32 | // proxyquire simulates the request
33 | // initialize proxyquire
34 | let userIndex = pq('./user.router.js', {
35 | 'express': {
36 | Router() {
37 | return routerStub;
38 | }
39 | },
40 | './user.controller': userCtrlStub,
41 | '../../auth/auth.service': authServiceStub
42 | });
43 |
44 | describe('User API Router:', function() {
45 |
46 | // expects the prozyquire routes to equal the routes it was assigned to
47 | it('should return an express router instance', function() {
48 | expect(userIndex.userRoutes).toEqual(routerStub);
49 | });
50 |
51 | describe('GET /api/users', function() {
52 |
53 | // expect with each request the approapriate endpoint was called
54 | it('should verify admin role and route to user.controller.index', function() {
55 | expect(routerStub.get.withArgs('/', 'authService.hasRole.admin', 'userCtrl.index').calledOnce)
56 | .toBe(true);
57 | });
58 |
59 | });
60 |
61 | describe('GET /api/users/me', function() {
62 |
63 | it('should be authenticated and route to user.controller.me', function() {
64 | expect(routerStub.get.withArgs('/me', 'authService.isAuthenticated', 'userCtrl.me').calledOnce)
65 | .toBe(true);
66 | });
67 |
68 | });
69 |
70 | describe('PUT /api/users/:email/password', function() {
71 |
72 | it('should be authenticated and route to user.controller.changePassword', function() {
73 | expect(routerStub.put.withArgs('/:email/password', 'authService.isAuthenticated', 'userCtrl.changePassword').calledOnce)
74 | .toBe(true);
75 | });
76 |
77 | });
78 |
79 | describe('GET /api/users/:email', function() {
80 |
81 | it('should be authenticated and route to user.controller.show', function() {
82 | expect(routerStub.get.withArgs('/:email', 'authService.isAuthenticated', 'userCtrl.show').calledOnce)
83 | .toBe(true);
84 | });
85 |
86 | });
87 |
88 | describe('POST /api/users', function() {
89 |
90 | it('should route to user.controller.create', function() {
91 | expect(routerStub.post.withArgs('/', 'userCtrl.create').calledOnce)
92 | .toBe(true);
93 | });
94 |
95 | });
96 |
97 | });
98 |
--------------------------------------------------------------------------------
/server/cassandra-db/auth/auth.router.ts:
--------------------------------------------------------------------------------
1 | let express = require('express');
2 |
3 | import UserModel from '../api/user/user.model';
4 |
5 | import config from '../../../config';
6 | import { localRoutes } from './local/local.router';
7 | import { localSetup } from './local/local.passport';
8 |
9 | // Passport configuration
10 | localSetup(UserModel, config);
11 |
12 | let router = express.Router();
13 |
14 | // Import routes here
15 | // this will setup the passport configuration from the *.passport file
16 | router.use('/local', localRoutes);
17 |
18 | // export the es6 way
19 | export { router as authRoutes };
20 |
--------------------------------------------------------------------------------
/server/cassandra-db/auth/auth.service.ts:
--------------------------------------------------------------------------------
1 | import UserModel from '../api/user/user.model';
2 |
3 | import config from '../../../config';
4 |
5 | import * as jwt from 'jsonwebtoken';
6 |
7 | let expressJwt = require('express-jwt');
8 | let compose = require('composable-middleware');
9 |
10 | let validateJwt = expressJwt({
11 | secret: config.sessionSecret
12 | });
13 |
14 | /**
15 | * Attaches the user object to the request if authenticated
16 | * Otherwise returns 403
17 | */
18 | export function isAuthenticated() {
19 | return compose()
20 | // Validate jwt
21 | .use((req, res, next) => {
22 | // allow access_token to be passed through query parameter as well
23 | if (req.query && req.query.hasOwnProperty('access_token')) {
24 | req.headers.authorization = 'Bearer ' + req.query.access_token;
25 | }
26 | return validateJwt(req, res, next);
27 | })
28 | // Attach user to request
29 | .use((req, res, next) => {
30 | let user;
31 | return UserModel.userByEmail(req.user.email)
32 | .then(result => {
33 | user = result.rows[0];
34 | if (!user || user > 1)
35 | res.status(401).json({ message: 'Invalid Token' });
36 |
37 | req.user = user;
38 | next();
39 | })
40 | .catch(err => next(err));
41 | });
42 | }
43 |
44 | /**
45 | * Checks if the user role meets the minimum requirements of the route
46 | */
47 | export function hasRole(roleRequired) {
48 | if (!roleRequired) {
49 | throw new Error('Required role needs to be set');
50 | }
51 |
52 | return compose()
53 | .use(isAuthenticated())
54 | .use(function meetsRequirements(req, res, next) {
55 | if (config.userRoles.indexOf(req.user.role) >=
56 | config.userRoles.indexOf(roleRequired)) {
57 | next();
58 | } else {
59 | res.status(403).send('Forbidden');
60 | }
61 | });
62 | }
63 |
64 | /**
65 | * Returns a jwt token signed by the app secret
66 | */
67 | export function signToken(id, email, role) {
68 | return jwt.sign({
69 | id: id,
70 | email: email,
71 | role: role
72 | }, config.sessionSecret, {
73 | expiresIn: 60 * 60 * 5
74 | });
75 | }
76 |
77 | /**
78 | * Set token cookie directly for oAuth strategies
79 | */
80 | export function setTokenCookie(req, res) {
81 | if (!req.user) {
82 | return res.status(404).send('It looks like you aren\'t logged in, please try again.');
83 | }
84 | let token = signToken(req.user.id, req.user.email, req.user.role);
85 | res.cookie('token', token);
86 | res.redirect('/');
87 | }
88 |
--------------------------------------------------------------------------------
/server/cassandra-db/auth/local/local.passport.ts:
--------------------------------------------------------------------------------
1 | import * as passport from 'passport';
2 | import { Strategy as LocalStrategy } from 'passport-local';
3 |
4 | // This is the authentication process that happens in passport before the
5 | // router callback function.
6 | // When done is called the items will be passed to the callback function in
7 | // local.router.ts
8 | function localAuthenticate(UserModel, email, password, done) {
9 | let user;
10 |
11 | UserModel.getCredentials(email).then(result => {
12 |
13 | if (Object.keys(result.rows).length > 1) {
14 | //TODO send an email to admin account notifying multiple users with the same credentials
15 | return done(null, false, { message: 'There was more than one user!' });
16 | }
17 | else if (Object.keys(result.rows).length < 1) {
18 | return done(null, false, { message: 'Account does not exist!' });
19 | }
20 | else {
21 | const dbPW: string = result.rows[0].password;
22 | const dbSalt: string = result.rows[0].salt;
23 | if (UserModel.authenticate(dbPW, dbSalt, password)) {
24 | user = result.rows[0];
25 | delete user.password;
26 | delete user.salt;
27 | return done(null, user);
28 | }
29 | else {
30 | return done(null, false, { message: 'This password is not correct!' });
31 | }
32 | }
33 |
34 |
35 | })
36 | .catch(err => {
37 | console.error('This email is not registered!', email);
38 | done(null, false, { message: 'This email is not registered!' + email });
39 | });
40 |
41 |
42 | }
43 |
44 | function setup(UserModel, config) {
45 | passport.use(new LocalStrategy({
46 | usernameField: 'email',
47 | passwordField: 'password' // this is the virtual field on the model
48 | }, function (email, password, done) {
49 | return localAuthenticate(UserModel, email, password, done);
50 | }));
51 | }
52 |
53 | export { setup as localSetup };
54 |
--------------------------------------------------------------------------------
/server/cassandra-db/auth/local/local.router.ts:
--------------------------------------------------------------------------------
1 | let express = require('express');
2 |
3 | import { signToken, isAuthenticated } from '../auth.service';
4 | import { me } from '../../api/user/user.controller';
5 | import * as passport from 'passport';
6 |
7 | let router = express.Router();
8 |
9 | function pp(req, res, next) {
10 | passport.authenticate('local', function (err, user, info) {
11 | let error = err || info;
12 | if (error) {
13 | res.status(401).json(error);
14 | return null;
15 | }
16 | if (!user) {
17 | res.status(404).json({ message: 'Something went wrong, please try again' });
18 | return null;
19 | }
20 |
21 | let token = signToken(user.id, user.email, user.role);
22 | req.headers.token = token;
23 | req.user = user;
24 | next();
25 |
26 | })(req, res, next);
27 | }
28 |
29 | // Only one route is necessary
30 | // When local authentication is required the 'local' hook is known from setup
31 | // in .passport.ts file
32 | router.post('/', pp, me);
33 |
34 | export { router as localRoutes };
35 |
--------------------------------------------------------------------------------
/server/cassandra-db/db.model.ts:
--------------------------------------------------------------------------------
1 | import { client } from '../cassandra-db';
2 |
3 | class DbModel {
4 |
5 | /////////////////
6 | //seed function//
7 | /////////////////
8 | public keyspace(keyspace: string): Promise {
9 | return client.execute(keyspace);
10 | }
11 |
12 | public seed(table: string, truncate: string, queries: Array<{ query: string, params: Array }>, queryOptions: object): Promise {
13 | return client.execute(table).then(result => {
14 | return client.execute(truncate).then(result => {
15 | return this.batch(queries, queryOptions).then(() => console.log('Batching succesful!')).catch(err => console.error(err));
16 | }).catch(err => console.error(err));
17 | }).catch(err => console.error(err));
18 | }
19 |
20 | public batch(queries: Array<{ query: string, params: Array }>, queryOptions: object) {
21 | return client.batch(queries, queryOptions);
22 | }
23 |
24 | }
25 |
26 |
27 |
28 | export default new DbModel;
--------------------------------------------------------------------------------
/server/cassandra-db/index.ts:
--------------------------------------------------------------------------------
1 | const cassandra = require('cassandra-driver');
2 | import config from '../../config';
3 |
4 | import * as Rx from 'rxjs';
5 |
6 | export const client = new cassandra.Client(config.cassandra);
7 |
8 | // Initialize Express-Cassandra
9 | export function cassandraConnect(): Rx.Observable {
10 | return Rx.Observable.create(observer => {
11 |
12 | client.connect(function (err) {
13 | if (err) {
14 | observer.error(err);
15 | }
16 | observer.next();
17 | observer.complete();
18 | });
19 |
20 | });
21 | };
22 |
23 | export function cassandraDisconnect() {
24 | client.shutdown(() => {
25 | console.log('Cassandra DB is now shutdown.');
26 | });
27 | };
28 |
29 |
--------------------------------------------------------------------------------
/server/cassandra-db/prepared.statements.ts:
--------------------------------------------------------------------------------
1 | class DbStmts {
2 |
3 | //keyspaces
4 | public devKeyspace: string = `CREATE KEYSPACE IF NOT EXISTS dev WITH REPLICATION = {
5 | 'class' : 'SimpleStrategy',
6 | 'replication_factor' : 1
7 | };`;
8 | public testKeyspace: string = `CREATE KEYSPACE IF NOT EXISTS dev WITH REPLICATION = {
9 | 'class' : 'SimpleStrategy',
10 | 'replication_factor' : 1
11 | };`;
12 |
13 | }
14 |
15 | export default new DbStmts;
--------------------------------------------------------------------------------
/server/cassandra-db/seed.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Populate DB with sample data on server start
3 | * to disable, edit config/environment/index.js, and set `seedDB: false`
4 | */
5 | import { client } from '../cassandra-db';
6 | import DbModel from './db.model';
7 | import DbStmts from './prepared.statements';
8 | import UserStmts from './api/user/prepared.statements';
9 |
10 | // Define Prepared Statments
11 | const devKeyspace: string = DbStmts.devKeyspace;
12 | const testKeyspace: string = DbStmts.testKeyspace;
13 | const userTable: string = UserStmts.userTable;
14 | const truncateUserTable: string = UserStmts.truncateUserTable;
15 | const seedUserTable: Array<{ query: string, params: Array }> = UserStmts.seedUserTable;
16 | const quertOptions: object = {prepared: true};
17 |
18 |
19 | export default function cassandraSeed(env?: string): void {
20 |
21 | // Insert seeds below
22 | switch (env) {
23 | case "development":
24 | DbModel.keyspace(devKeyspace)
25 | .then(result => {
26 | console.log('Dev keyspace ready to seed!');
27 | // list all your batch queries here by table
28 | DbModel.seed(userTable, truncateUserTable, seedUserTable, this.queryOptions)
29 | .then(result => console.log('User Table seeded succesfully!'))
30 | .catch(err => console.error(err));
31 |
32 | })
33 | .catch(err => console.error(err));
34 | break;
35 | case "test":
36 | DbModel.keyspace(testKeyspace)
37 | .then(result => {
38 | console.log('Test keyspace ready to seed!');
39 |
40 | // list all your batch queries here by table
41 | DbModel.seed(userTable, truncateUserTable, seedUserTable, this.queryOptions)
42 | .then(result => console.log('User Table seeded succesfully!'))
43 | .catch(err => console.error(err));
44 |
45 | })
46 | .catch(err => console.error(err));
47 | break;
48 | default:
49 | // code... for production and others
50 | break;
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/server/db-connect.ts:
--------------------------------------------------------------------------------
1 | import { mongoConnect, mongoDisconnect } from './mongo-db';
2 | import { cassandraConnect, cassandraDisconnect } from './cassandra-db';
3 | import { sequelizeConnect, sequelizeDisconnect } from './sql-db';
4 |
5 | import * as Rx from 'rxjs';
6 |
7 | export function connect(): Rx.Observable {
8 | let obs = [];
9 | obs.push(mongoConnect());
10 | obs.push(cassandraConnect());
11 | obs.push(sequelizeConnect());
12 |
13 | return obs.length > 1 ? Rx.Observable.merge.apply(this, obs) : obs[0];
14 | }
15 |
16 | export function disconnect() {
17 | mongoDisconnect();
18 | cassandraDisconnect();
19 | sequelizeDisconnect();
20 | }
21 |
--------------------------------------------------------------------------------
/server/express.ts:
--------------------------------------------------------------------------------
1 | // importing modules the es6 way
2 | import routes from './routes';
3 | import config from '../config';
4 |
5 | import * as mongoose from 'mongoose';
6 | import * as path from 'path';
7 | import * as passport from 'passport';
8 | import * as express from 'express';
9 | import * as fs from 'graceful-fs';
10 | import * as chalk from 'chalk';
11 | import * as morgan from 'morgan';
12 | import * as bodyParser from 'body-parser';
13 | import * as methodOverride from 'method-override';
14 | import * as cookieParser from 'cookie-parser';
15 |
16 | import * as session from 'express-session';
17 | import * as connectMongo from 'connect-mongo';
18 |
19 | let MongoStore = connectMongo(session);
20 |
21 | // function to initialize the express app
22 | function expressInit(app) {
23 |
24 | //aditional app Initializations
25 | app.use(bodyParser.urlencoded({
26 | extended: false
27 | }));
28 | app.use(bodyParser.json());
29 | app.use(methodOverride());
30 | app.use(cookieParser());
31 | // Initialize passport and passport session
32 | app.use(passport.initialize());
33 |
34 | //initialize morgan express logger
35 | // NOTE: all node and custom module requests
36 | if (process.env.NODE_ENV !== 'test') {
37 | app.use(morgan('dev', {
38 | skip: function (req, res) { return res.statusCode < 400 }
39 | }));
40 | }
41 |
42 | // app.use(session({
43 | // secret: config.sessionSecret,
44 | // saveUninitialized: true,
45 | // resave: false,
46 | // store: new MongoStore({
47 | // mongooseConnection: mongoose.connection
48 | // })
49 | // }));
50 |
51 | //sets the routes for all the API queries
52 | routes(app);
53 |
54 | const dist = fs.existsSync('dist');
55 |
56 | //exposes the client and node_modules folders to the client for file serving when client queries "/"
57 | app.use('/node_modules', express.static('node_modules'));
58 | app.use('/custom_modules', express.static('custom_modules'));
59 | app.use(express.static(`${dist ? 'dist/client' : 'client'}`));
60 | app.use('/public', express.static('public'));
61 |
62 | //exposes the client and node_modules folders to the client for file serving when client queries anything, * is a wildcard
63 | app.use('*', express.static('node_modules'));
64 | app.use('*', express.static('custom_modules'));
65 | app.use('*', express.static(`${dist ? 'dist/client' : 'client'}`));
66 | app.use('*', express.static('public'));
67 |
68 | // starts a get function when any directory is queried (* is a wildcard) by the client,
69 | // sends back the index.html as a response. Angular then does the proper routing on client side
70 | if (process.env.NODE_ENV !== 'development')
71 | app.get('*', function (req, res) {
72 | res.sendFile(path.join(process.cwd(), `/${dist ? 'dist/client' : 'client'}/index.html`));
73 | });
74 |
75 | return app;
76 |
77 | };
78 |
79 | export default expressInit;
80 |
--------------------------------------------------------------------------------
/server/mongo-db/api/user/user.controller.ts:
--------------------------------------------------------------------------------
1 | import User from './user.model';
2 | import config from '../../../../config';
3 |
4 | import * as jwt from 'jsonwebtoken';
5 |
6 | // Handles status codes and error message json
7 | // specificity: validation
8 | function validationError(res, statusCode = null) {
9 | console.log('duuude');
10 | statusCode = statusCode || 422;
11 | return function(err) {
12 | res.status(statusCode).json(err);
13 | return null;
14 | };
15 | }
16 |
17 | // Handles status codes and error message json
18 | // specificity: error
19 | function handleError(res, statusCode = null) {
20 | console.log('duuwowude');
21 | statusCode = statusCode || 500;
22 | return function(err) {
23 | res.status(statusCode).send(err);
24 | return null;
25 | };
26 | }
27 |
28 | /**
29 | * Change a users password endpoint
30 | */
31 | export function changePassword(req, res, next) {
32 | let userId = req.user._id;
33 | let oldPass = String(req.body.oldPassword);
34 | let newPass = String(req.body.newPassword);
35 |
36 | return User.findById(userId).exec()
37 | .then(user => {
38 | if (user.authenticate(oldPass)) {
39 | user.password = newPass;
40 | return user.save()
41 | .then(() => {
42 | res.status(204).end();
43 | })
44 | .catch(validationError(res));
45 | } else {
46 | return res.status(403).end();
47 | }
48 | });
49 | }
50 |
51 | /**
52 | * Get list of users
53 | * restriction: 'admin'
54 | */
55 | export function index(req, res) {
56 | return User.find({}, '-salt -password').exec()
57 | .then(users => {
58 | res.status(200).json(users);
59 | })
60 | .catch(handleError(res));
61 | }
62 |
63 | /**
64 | * Creates a new user endpoint
65 | */
66 | export function create(req, res, next) {
67 | console.log('duuudaaaaae');
68 | let newUser = new User(req.body);
69 | newUser.provider = 'local';
70 | newUser.role = 'user';
71 | return newUser.save()
72 | .then(user => {
73 | let token = jwt.sign(
74 | { _id: user._id },
75 | config.sessionSecret,
76 | { expiresIn: 60 * 60 * 5 }
77 | );
78 |
79 | req.headers.token = token;
80 | req.user = user;
81 | next();
82 |
83 | return null;
84 | })
85 | .catch(validationError(res));
86 | }
87 |
88 | /**
89 | * Deletes a user
90 | * restriction: 'admin'
91 | */
92 | export function destroy(req, res) {
93 | return User.findByIdAndRemove(req.params.id).exec()
94 | .then(function() {
95 | res.status(204).end();
96 | })
97 | .catch(handleError(res));
98 | }
99 |
100 | /**
101 | * Get a single user
102 | */
103 | export function show(req, res, next) {
104 | let userId = req.params.id;
105 |
106 | return User.findById(userId).exec()
107 | .then(user => {
108 | if (!user) {
109 | return res.status(404).end();
110 | }
111 | res.json(user.profile);
112 | })
113 | .catch(err => next(err));
114 | }
115 |
116 | /**
117 | * Get my info: all user information
118 | */
119 | export function me(req, res, next) {
120 | let userId = req.user._id;
121 | let token = req.headers.token;
122 |
123 | return User.findOne({
124 | _id: userId
125 | }, '-salt -password').exec()
126 | .then(user => { // don't ever give out the password or salt
127 | if (!user) {
128 | return res.status(401).json({ message: 'User does not exist' });
129 | }
130 |
131 | if (token) res.json({ token, user });
132 | else res.json(user);
133 |
134 | return null;
135 | })
136 | .catch(err => next(err));
137 | }
138 |
--------------------------------------------------------------------------------
/server/mongo-db/api/user/user.integration.ts:
--------------------------------------------------------------------------------
1 | import app from '../../../server';
2 | import request = require('supertest');
3 |
4 | import User from './user.model';
5 |
6 | // User Endpoint testing
7 | describe('User API:', function () {
8 | let user;
9 | let token;
10 |
11 | // users are cleared from DB seeding
12 | // add a new testing user
13 | beforeAll(done => {
14 | return User.remove({}).then(function () {
15 | user = new User();
16 | user.username = 'test';
17 | user.email = 'test@test.com';
18 | user.password = 'test';
19 | user.firstname = 'testFirst';
20 | user.lastname = 'testLast';
21 |
22 | return user.save().then(() => done())
23 | .catch(err => {
24 | expect(err).not.toBeDefined();
25 | done();
26 | });
27 | }).catch(err => {
28 | expect(err).not.toBeDefined();
29 | done();
30 | });
31 | });
32 |
33 | // Encapsolate GET me enpoint
34 | describe('GET /api/users/me', function () {
35 |
36 | // before every 'it' get new OAuth token representing the user
37 | beforeAll(done => {
38 | setTimeout(() => request(app)
39 | .post('/auth/local')
40 | .send({
41 | email: 'test@test.com',
42 | password: 'test'
43 | })
44 | .expect(200)
45 | .end((err, res) => {
46 | if (err) {
47 | done.fail(err);
48 | } else {
49 | token = res.body.token;
50 | done();
51 | }
52 | }), 2000);
53 | });
54 |
55 | // If the token was properly set inside the header of the request
56 | // it should respond with a 200 status code with the user json
57 | it('should respond with a user profile when authenticated', done => {
58 | request(app)
59 | .get('/api/users/me')
60 | .set('authorization', 'Bearer ' + token)
61 | .expect(200)
62 | .expect('Content-Type', /json/)
63 | .end((err, res) => {
64 | if (err) {
65 | done.fail(err);
66 | } else {
67 | expect(res.body.username).toEqual(user.username);
68 | expect(res.body.firstname).toEqual(user.firstname);
69 | expect(res.body.lastname).toEqual(user.lastname);
70 | expect(res.body.email).toEqual(user.email);
71 | done();
72 | }
73 | });
74 | });
75 |
76 | // If the token was improperly / not set to the header
77 | // status code 401 should be thrown
78 | it('should respond with a 401 when not authenticated', done => {
79 | request(app)
80 | .get('/api/users/me')
81 | .expect(401)
82 | .end((err, res) => {
83 | if (err) {
84 | done.fail(err);
85 | } else {
86 | done();
87 | }
88 | });
89 | });
90 | });
91 | });
92 |
--------------------------------------------------------------------------------
/server/mongo-db/api/user/user.router.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * GET /api/user -> allUsers
3 | * POST /api/user -> create
4 | * GET /api/user/:id -> show
5 | * PUT /api/user/:id/password -> changePassword
6 | * DELETE /api/user/:id -> destroy
7 | */
8 |
9 | let express = require('express');
10 | import * as auth from '../../auth/auth.service';
11 | import * as UserController from './user.controller';
12 |
13 | let router = express.Router();
14 |
15 | router.get('/', auth.hasRole('admin'), UserController.index);
16 | router.delete('/:id', auth.hasRole('admin'), UserController.destroy);
17 | router.put('/:id/password', auth.isAuthenticated(), UserController.changePassword);
18 |
19 | router.post('/', UserController.create, UserController.me);
20 | router.get('/me', auth.isAuthenticated(), UserController.me);
21 | router.get('/:id', auth.isAuthenticated(), UserController.show);
22 |
23 | export {router as userRoutes};
24 |
--------------------------------------------------------------------------------
/server/mongo-db/api/user/user.spec.ts:
--------------------------------------------------------------------------------
1 | import proxyquire = require('proxyquire');
2 | let pq = proxyquire.noPreserveCache();
3 | import sinon = require('sinon');
4 |
5 | // userCtrlStub is used to mimic the router
6 | let userCtrlStub = {
7 | index: 'userCtrl.index',
8 | destroy: 'userCtrl.destroy',
9 | me: 'userCtrl.me',
10 | changePassword: 'userCtrl.changePassword',
11 | show: 'userCtrl.show',
12 | create: 'userCtrl.create'
13 | };
14 |
15 | // mimic teh auth service
16 | let authServiceStub = {
17 | isAuthenticated() {
18 | return 'authService.isAuthenticated';
19 | },
20 | hasRole(role) {
21 | return 'authService.hasRole.' + role;
22 | }
23 | };
24 |
25 | // routerStub spys on http RESTFUL requests
26 | let routerStub = {
27 | get: sinon.spy(),
28 | put: sinon.spy(),
29 | post: sinon.spy(),
30 | delete: sinon.spy()
31 | };
32 |
33 | // require the index with our stubbed out modules
34 | // proxyquire simulates the request
35 | // initialize proxyquire
36 | let userIndex = pq('./user.router.js', {
37 | 'express': {
38 | Router() {
39 | return routerStub;
40 | }
41 | },
42 | './user.controller': userCtrlStub,
43 | '../../auth/auth.service': authServiceStub
44 | });
45 |
46 | describe('User API Router:', function() {
47 |
48 | // expects the prozyquire routes to equal the routes it was assigned to
49 | it('should return an express router instance', function() {
50 | expect(userIndex.userRoutes).toEqual(routerStub);
51 | });
52 |
53 | describe('GET /api/users', function() {
54 |
55 | // expect with each request the approapriate endpoint was called
56 | it('should verify admin role and route to user.controller.index', function() {
57 | expect(routerStub.get.withArgs('/', 'authService.hasRole.admin', 'userCtrl.index').calledOnce)
58 | .toBe(true);
59 | });
60 |
61 | });
62 |
63 | describe('DELETE /api/users/:id', function() {
64 |
65 | it('should verify admin role and route to user.controller.destroy', function() {
66 | expect(routerStub.delete.withArgs('/:id', 'authService.hasRole.admin', 'userCtrl.destroy').calledOnce)
67 | .toBe(true);
68 | });
69 |
70 | });
71 |
72 | describe('GET /api/users/me', function() {
73 |
74 | it('should be authenticated and route to user.controller.me', function() {
75 | expect(routerStub.get.withArgs('/me', 'authService.isAuthenticated', 'userCtrl.me').calledOnce)
76 | .toBe(true);
77 | });
78 |
79 | });
80 |
81 | describe('PUT /api/users/:id/password', function() {
82 |
83 | it('should be authenticated and route to user.controller.changePassword', function() {
84 | expect(routerStub.put.withArgs('/:id/password', 'authService.isAuthenticated', 'userCtrl.changePassword').calledOnce)
85 | .toBe(true);
86 | });
87 |
88 | });
89 |
90 | describe('GET /api/users/:id', function() {
91 |
92 | it('should be authenticated and route to user.controller.show', function() {
93 | expect(routerStub.get.withArgs('/:id', 'authService.isAuthenticated', 'userCtrl.show').calledOnce)
94 | .toBe(true);
95 | });
96 |
97 | });
98 |
99 | describe('POST /api/users', function() {
100 |
101 | it('should route to user.controller.create', function() {
102 | expect(routerStub.post.withArgs('/', 'userCtrl.create').calledOnce)
103 | .toBe(true);
104 | });
105 |
106 | });
107 |
108 | });
109 |
--------------------------------------------------------------------------------
/server/mongo-db/auth/auth.router.ts:
--------------------------------------------------------------------------------
1 | let express = require('express');
2 |
3 | import User from '../api/user/user.model';
4 | import config from '../../../config';
5 | import {localRoutes} from './local/local.router';
6 | import {localSetup} from './local/local.passport';
7 |
8 | // Passport configuration
9 | localSetup(User, config);
10 |
11 | let router = express.Router();
12 |
13 | // Import routes here
14 | // this will setup the passport configuration from the *.passport file
15 | router.use('/local', localRoutes);
16 |
17 | // export the es6 way
18 | export {router as authRoutes};
19 |
--------------------------------------------------------------------------------
/server/mongo-db/auth/auth.service.ts:
--------------------------------------------------------------------------------
1 | import User from '../api/user/user.model';
2 |
3 | import config from '../../../config';
4 |
5 | import * as jwt from 'jsonwebtoken';
6 |
7 | let expressJwt = require('express-jwt');
8 | let compose = require('composable-middleware');
9 |
10 | let validateJwt = expressJwt({
11 | secret: config.sessionSecret
12 | });
13 |
14 | /**
15 | * Attaches the user object to the request if authenticated
16 | * Otherwise returns 403
17 | */
18 | export function isAuthenticated() {
19 | return compose()
20 | // Validate jwt
21 | .use((req, res, next) => {
22 | // allow access_token to be passed through query parameter as well
23 | if (req.query && req.query.hasOwnProperty('access_token')) {
24 | req.headers.authorization = 'Bearer ' + req.query.access_token;
25 | }
26 | return validateJwt(req, res, next);
27 | })
28 | // Attach user to request
29 | .use((req, res, next) => {
30 | return User.findById(req.user._id)
31 | .then(user => {
32 | if (!user) {
33 | return res.status(401).json({ message: 'Invalid Token' });
34 | }
35 | req.user = user;
36 | next();
37 | // runnaway promise to remove node warning
38 | return null;
39 | })
40 | .catch(err => next(err));
41 | });
42 | }
43 |
44 | /**
45 | * Checks if the user role meets the minimum requirements of the route
46 | */
47 | export function hasRole(roleRequired) {
48 | if (!roleRequired) {
49 | throw new Error('Required role needs to be set');
50 | }
51 |
52 | return compose()
53 | .use(isAuthenticated())
54 | .use(function meetsRequirements(req, res, next) {
55 | if (config.userRoles.indexOf(req.user.role) >=
56 | config.userRoles.indexOf(roleRequired)) {
57 | next();
58 | } else {
59 | res.status(403).send('Forbidden');
60 | }
61 | });
62 | }
63 |
64 | /**
65 | * Returns a jwt token signed by the app secret
66 | */
67 | export function signToken(id, role) {
68 | return jwt.sign({
69 | _id: id,
70 | role: role
71 | }, config.sessionSecret, {
72 | expiresIn: 60 * 60 * 5
73 | });
74 | }
75 |
76 | /**
77 | * Set token cookie directly for oAuth strategies
78 | */
79 | export function setTokenCookie(req, res) {
80 | if (!req.user) {
81 | return res.status(404).send('It looks like you aren\'t logged in, please try again.');
82 | }
83 | let token = signToken(req.user._id, req.user.role);
84 | res.cookie('token', token);
85 | res.redirect('/');
86 | }
87 |
--------------------------------------------------------------------------------
/server/mongo-db/auth/local/local.passport.ts:
--------------------------------------------------------------------------------
1 | import * as passport from 'passport';
2 | import {Strategy as LocalStrategy} from 'passport-local';
3 |
4 | // This is the authentication process that happens in passport before the
5 | // router callback function.
6 | // When done is called the items will be passed to the callback function in
7 | // local.router.ts
8 | function localAuthenticate(User, email, password, done) {
9 | User.findOne({
10 | email: email.toLowerCase()
11 | }).exec()
12 | .then(user => {
13 | if (!user) {
14 | return done(null, false, {
15 | message: 'This email is not registered!'
16 | });
17 | }
18 | user.authenticate(password, (authError, authenticated) => {
19 | if (authError) {
20 | return done(authError);
21 | }
22 | if (!authenticated) {
23 | return done(null, false, {
24 | message: 'This password is not correct!'
25 | });
26 | } else {
27 | return done(null, user);
28 | }
29 | });
30 | })
31 | .catch(err => done(err));
32 | }
33 |
34 | function setup(User, config) {
35 | passport.use(new LocalStrategy({
36 | usernameField: 'email',
37 | passwordField: 'password' // this is the virtual field on the model
38 | }, function (email, password, done) {
39 | return localAuthenticate(User, email, password, done);
40 | }));
41 | }
42 |
43 | export {setup as localSetup};
44 |
--------------------------------------------------------------------------------
/server/mongo-db/auth/local/local.router.ts:
--------------------------------------------------------------------------------
1 | let express = require('express');
2 |
3 | import {signToken, isAuthenticated} from '../auth.service';
4 | import {me} from '../../api/user/user.controller';
5 | import * as passport from 'passport';
6 |
7 | let router = express.Router();
8 |
9 | // Only one route is necessary
10 | // When local authentication is required the 'local' hook is known from setup
11 | // in .passport.ts file
12 | router.post('/', function(req, res, next) {
13 | passport.authenticate('local', function(err, user, info) {
14 | let error = err || info;
15 | if (error) {
16 | res.status(401).json(error);
17 | return null;
18 | }
19 | if (!user) {
20 | res.status(404).json({ message: 'Something went wrong, please try again' });
21 | return null;
22 | }
23 |
24 | let token = signToken(user._id, user.role);
25 | req.headers.token = token;
26 | req.user = user;
27 | next();
28 |
29 | })(req, res, next);
30 | }, me);
31 |
32 | export {router as localRoutes};
33 |
--------------------------------------------------------------------------------
/server/mongo-db/index.ts:
--------------------------------------------------------------------------------
1 | let mongoose = require('mongoose');
2 | mongoose.Promise = Promise; // promise library plugin
3 |
4 | import * as chalk from 'chalk';
5 | import config from '../../config';
6 |
7 | import * as Rx from 'rxjs';
8 |
9 | // Initialize Mongoose
10 | export function mongoConnect(): Rx.Observable {
11 | return Rx.Observable.create(observer => {
12 |
13 | mongoose.connect(config.mongo.uri, config.mongo.options, function (err) {
14 | // Log Error
15 | if (err) {
16 | console.error(chalk.default.bold.red('Could not connect to MongoDB!'));
17 | observer.error(err);
18 | } else {
19 | // Enabling mongoose debug mode if required
20 | mongoose.set('debug', config.mongo.debug);
21 |
22 | observer.next();
23 | observer.complete();
24 | }
25 | });
26 |
27 | });
28 | };
29 |
30 | export function mongoDisconnect() {
31 | mongoose.disconnect(function (err) {
32 | console.log(chalk.default.bold.yellow('Disconnected from MongoDB.'));
33 | });
34 | };
--------------------------------------------------------------------------------
/server/mongo-db/seed.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Populate DB with sample data on server start
3 | * to disable, edit config/environment/index.js, and set `seedDB: false`
4 | */
5 | import User from './api/user/user.model';
6 |
7 | export default function mongoSeed(env?: string): void {
8 |
9 | // Insert seeds below
10 | switch (env) {
11 | case "development":
12 | User.find({}).remove().then(() => {
13 | User.create({
14 | username: 'AdMiN',
15 | firstname: 'admin',
16 | lastname: 'admin',
17 | email: 'admin@admin.com',
18 | password: 'admin1'
19 | }, {
20 | username: 'test',
21 | firstname: 'testFirst',
22 | lastname: 'testLast',
23 | email: 'test@test.com',
24 | password: 'test'
25 | });
26 | }).catch(error => console.log(error));
27 | break;
28 | case "test":
29 | User.find({}).remove().then(() => {
30 | User.create({
31 | username: 'test',
32 | firstname: 'testFirst',
33 | lastname: 'testLast',
34 | email: 'test@test.com',
35 | password: 'test'
36 | });
37 | }).catch(error => console.log(error));
38 | break;
39 | default:
40 | // code...
41 | break;
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/server/routes.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Main application routes
3 | */
4 | import { userRoutes } from './mongo-db/api/user/user.router';
5 | // DO NOT REMOVE: template route imports
6 | import { authRoutes } from './mongo-db/auth/auth.router';
7 |
8 | export default function routes(app) {
9 | // Insert routes below
10 | app.use('/api/users', userRoutes);
11 | // DO NOT REMOVE: template routes
12 | app.use('/auth', authRoutes);
13 | };
14 |
--------------------------------------------------------------------------------
/server/server.ts:
--------------------------------------------------------------------------------
1 | import * as express from 'express'
2 | import * as chalk from 'chalk';
3 | import * as fs from 'graceful-fs';
4 | import * as http from 'http';
5 | import * as https from 'https';
6 | import config from '../config';
7 |
8 | import socketInit from './socketio';
9 | import expressInit from './express';
10 |
11 | import cassandraSeed from './cassandra-db/seed';
12 | import mongoSeed from './mongo-db/seed';
13 | import sqlSeed from './sql-db/seed';
14 | import {connect, disconnect} from './db-connect';
15 |
16 | const isSecure = config.https_secure && (process.env.NODE_ENV === 'production' || !process.env.NODE_ENV);
17 |
18 | // Initialize express
19 | let app = express();
20 |
21 | // Initialize http server
22 | let server: any = http.createServer(app);
23 | // If specified in the default assets, https will be used
24 | if (isSecure) {
25 | let credentials = {
26 | key: fs.readFileSync(config.key_loc, 'utf8'),
27 | cert: fs.readFileSync(config.cert_loc, 'utf8')
28 | };
29 |
30 | server = https.createServer(credentials, app);
31 | }
32 | // Initialize the socketio with the respective server
33 | let socketio = require('socket.io')(server, {
34 | // serveClient: process.env.NODE_ENV !== 'production',
35 | path: '/socket.io-client'
36 | });
37 |
38 | connect().subscribe(
39 | x => {},
40 | err => console.log(err),
41 | () => {
42 | expressInit(app);
43 | socketInit(socketio);
44 |
45 | if (config.seedDB) {
46 | mongoSeed(process.env.NODE_ENV);
47 | cassandraSeed(process.env.NODE_ENV);
48 | // sqlSeed(process.env.NODE_ENV);
49 | }
50 |
51 | // Start the server on port / host
52 | server.listen(config.port, config.host, () => {
53 | let host = server.address().address;
54 | let port = server.address().port;
55 |
56 | if (process.env.NODE_ENV !== 'test') {
57 | console.log(
58 | chalk.default.bold.cyan(`\n\tEnvironment:\t\t\t ${ process.env.NODE_ENV || 'production' }\n`));
59 |
60 | console.log(
61 | chalk.default.bold.cyan(`\tSQL:`) +
62 | chalk.default.bold.cyan(`\n\t - URI:\t\t\t\t sql://${config.sql.username}:${config.sql.password}@localhost:5432/${config.sql.database}\n`));
63 |
64 | if (!process.env.NODE_ENV)
65 | console.log(
66 | chalk.default.bold.magenta(`\t${isSecure ? 'HTTPS': 'HTTP'} Server`) +
67 | chalk.default.bold.gray(`\n\tServer Address:\t\t\t ${isSecure ? 'https': 'http'}://localhost:${ port }\n`));
68 | else
69 | console.log(
70 | chalk.default.bold.magenta(`\tWebPack DevServer:`) +
71 | chalk.default.bold.gray(`\n\tServer Address:\t\t\t ${isSecure ? 'https': 'http'}://localhost:1701\n`));
72 | }
73 | });
74 | });
75 |
76 | // export express app for testing
77 | export default app;
78 |
--------------------------------------------------------------------------------
/server/socketio.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Socket.io configuration
3 | */
4 | import config from '../config';
5 |
6 | // Socket imports go here
7 | // DO NOT REMOVE: template socket imports
8 |
9 | // When the user disconnects.. perform this
10 | function onDisconnect(socket) {
11 | }
12 |
13 | // When the user connects.. perform this
14 | function onConnect(socket) {
15 | // When the client emits 'info', this listens and executes
16 | socket.on('info', data => {
17 | socket.log(JSON.stringify(data, null, 2));
18 | });
19 |
20 | // Insert sockets below
21 | // DO NOT REMOVE: template sockets
22 |
23 | }
24 |
25 | function socketInit(socketio) {
26 | // socket.io (v1.x.x) is powered by debug.
27 | // We can authenticate socket.io users and access their token through socket.decoded_token
28 | //
29 | // 1. You will need to send the token in `app/services/socketio/socketio.service.ts`
30 | //
31 | // 2. Require authentication here:
32 | // socketio.use(require('socketio-jwt').authorize({
33 | // secret: config.secrets.session,
34 | // handshake: true
35 | // }));
36 |
37 | socketio.on('connection', function (socket) {
38 | socket.address = socket.request.connection.remoteAddress +
39 | ':' + socket.request.connection.remotePort;
40 |
41 | socket.connectedAt = new Date();
42 |
43 | socket.log = function (...data) {
44 | console.log(`SocketIO ${socket.nsp.name} [${socket.address}]`, ...data);
45 | };
46 |
47 | // Call onDisconnect.
48 | socket.on('disconnect', () => {
49 | onDisconnect(socket);
50 | socket.log('DISCONNECTED');
51 | });
52 |
53 | // Call onConnect.
54 | onConnect(socket);
55 | socket.log('CONNECTED');
56 | });
57 | }
58 |
59 | export default socketInit;
60 |
--------------------------------------------------------------------------------
/server/sql-db/api/user/user.controller.ts:
--------------------------------------------------------------------------------
1 | import User from './user.model';
2 | import config from '../../../../config';
3 |
4 | import * as jwt from 'jsonwebtoken';
5 |
6 | // Handles status codes and error message json
7 | // specificity: validation
8 | function validationError(res, err, statusCode = null) {
9 |
10 | statusCode = statusCode || 422;
11 |
12 | res.status(statusCode).json(err.errors[0]);
13 | return null;
14 |
15 | }
16 |
17 | // Handles status codes and error message json
18 | // specificity: error
19 | function handleError(res, err, statusCode = null) {
20 | statusCode = statusCode || 500;
21 |
22 | res.status(statusCode).send(err.errors[0]);
23 | return null;
24 |
25 | }
26 |
27 | /**
28 | * Change a users password endpoint
29 | */
30 | export function changePassword(req, res, next) {
31 | let userId = req.user.id;
32 | let oldPass = String(req.body.oldPassword);
33 | let newPass = String(req.body.newPassword);
34 |
35 | return User.findById(userId).then(user => {
36 | if (user['authenticate'](oldPass)) {
37 | user['password'] = newPass;
38 | return user['save']()
39 | .then(() => {
40 | res.status(204).end();
41 | })
42 | .catch(err => validationError(res, err));
43 | } else {
44 | return res.status(403).end();
45 | }
46 | });
47 | }
48 |
49 | /**
50 | * Get list of users
51 | * restriction: 'admin'
52 | */
53 | export function index(req, res) {
54 | return User.findAll({ attributes: {exclude: ['salt', 'password'] } }).then(users => {
55 | res.status(200).json(users);
56 | })
57 | .catch(err => handleError(res, err));
58 | }
59 |
60 | /**
61 | * Creates a new user endpoint
62 | */
63 | export function create(req, res, next) {
64 |
65 | User.create({
66 | username: req.body.username,
67 | email: req.body.email,
68 | password: req.body.password,
69 | role: 'user'
70 | }).then(user => {
71 | let token = jwt.sign(
72 | { id: user['id'] },
73 | config.sessionSecret,
74 | { expiresIn: 60 * 60 * 5 }
75 | );
76 |
77 | req.headers.token = token;
78 | req.user = user;
79 | next();
80 |
81 | return null;
82 | })
83 | .catch(err => {
84 | validationError(res, err);
85 | });
86 | }
87 |
88 | /**
89 | * Deletes a user
90 | * restriction: 'admin'
91 | */
92 | export function destroy(req, res) {
93 | return User.destroy({where: {id: req.params.id}})
94 | .then(function() {
95 | res.status(204).end();
96 | })
97 | .catch(err => handleError(res, err));
98 | }
99 |
100 | /**
101 | * Get a single user
102 | */
103 | export function show(req, res, next) {
104 | let userId = req.params.id;
105 |
106 | return User.findById(userId).then(user => {
107 | if (!user) {
108 | return res.status(404).end();
109 | }
110 | res.json(user['profile']);
111 | })
112 | .catch(err => validationError(res, err));
113 | }
114 |
115 | /**
116 | * Get my info: all user information
117 | */
118 | export function me(req, res, next) {
119 | let userId = req.user.id;
120 | let token = req.headers.token;
121 |
122 | return User.findOne({ where: {id: userId}, attributes: {exclude: ['salt', 'password'] } }).then(user => { // don't ever give out the password or salt
123 | if (!user) {
124 | return res.status(401).json({ message: 'User does not exist' });
125 | }
126 |
127 | if (token) res.json({ token, user });
128 | else res.json(user);
129 |
130 | return null;
131 | })
132 | .catch(err => validationError(res, err));
133 | }
134 |
--------------------------------------------------------------------------------
/server/sql-db/api/user/user.integration.ts:
--------------------------------------------------------------------------------
1 | import app from '../../../server';
2 | import request = require('supertest');
3 |
4 | import User from './user.model';
5 |
6 | // User Endpoint testing
7 | describe('User API:', function () {
8 | let user;
9 | let token;
10 |
11 | // users are cleared from DB seeding
12 | // add a new testing user
13 | beforeAll(function (done) {
14 | return User.sync().then(() =>{
15 | User.destroy({truncate: true, cascade: true}).then(() => {
16 | User.create({
17 | username: 'test',
18 | firstname: 'testFirst',
19 | lastname: 'testLast',
20 | email: 'test@test.com',
21 | password: 'test'
22 | }).then(u => {
23 | user = u;
24 | done();
25 | }).catch(err => {
26 | expect(err).not.toBeDefined();
27 | done();
28 | });
29 |
30 | });
31 | }).catch(err => {
32 | expect(err).not.toBeDefined();
33 | done();
34 | });
35 |
36 | });
37 |
38 | // Encapsolate GET me enpoint
39 | describe('GET /api/users/me', function () {
40 |
41 | // before every 'it' get new OAuth token representing the user
42 | beforeAll(function (done) {
43 | setTimeout(() => request(app)
44 | .post('/auth/local')
45 | .send({
46 | email: 'test@test.com',
47 | password: 'test'
48 | })
49 | .expect(200)
50 | .end((err, res) => {
51 | if (err) {
52 | done.fail(err);
53 | } else {
54 | token = res.body.token;
55 | done();
56 | }
57 | }), 2000);
58 |
59 | });
60 |
61 | // If the token was properly set inside the header of the request
62 | // it should respond with a 200 status code with the user json
63 | it('should respond with a user profile when authenticated', function (done) {
64 | request(app)
65 | .get('/api/users/me')
66 | .set('authorization', 'Bearer ' + token)
67 | .expect(200)
68 | .expect('Content-Type', /json/)
69 | .end((err, res) => {
70 | if (err) {
71 | done.fail(err);
72 | } else {
73 | expect(res.body.username).toEqual(user.username);
74 | expect(res.body.firstname).toEqual(user.firstname);
75 | expect(res.body.lastname).toEqual(user.lastname);
76 | expect(res.body.email).toEqual(user.email);
77 | done();
78 | }
79 | });
80 | });
81 |
82 | // If the token was improperly / not set to the header
83 | // status code 401 should be thrown
84 | it('should respond with a 401 when not authenticated', function (done) {
85 | request(app)
86 | .get('/api/users/me')
87 | .expect(401)
88 | .end((err, res) => {
89 | if (err) {
90 | done.fail(err);
91 | } else {
92 | done();
93 | }
94 | });
95 | });
96 | });
97 | });
--------------------------------------------------------------------------------
/server/sql-db/api/user/user.model.ts:
--------------------------------------------------------------------------------
1 | import Sequelize from "sequelize";
2 | import sequelize from "../../../sql-db";
3 |
4 | const crypto = require('crypto');
5 |
6 | let User = sequelize.define("user", {
7 | username: {type: Sequelize.STRING,
8 | unique: true,
9 | validate: {
10 | notEmpty: {
11 | args: true,
12 | msg: 'A user needs at least a username'
13 | }
14 | }
15 | },
16 | firstname: {type: Sequelize.STRING},
17 | lastname: {type: Sequelize.STRING},
18 | email: {type: Sequelize.STRING,
19 | unique: true,
20 | validate: {
21 | notEmpty: {
22 | args: true,
23 | msg: 'Email cannot be blank'
24 | }
25 | }
26 | },
27 | password: {type: Sequelize.STRING,
28 | validate: {
29 | notEmpty: {
30 | args: true,
31 | msg: 'Password cannot be blank'
32 | }
33 | }},
34 | salt: {type: Sequelize.STRING}
35 | },{
36 | hooks: {
37 | beforeCreate: function(user, options, next) {
38 |
39 | // Make salt with a callback
40 | user['makeSalt']((saltErr, salt) => {
41 | if (saltErr) {
42 | return next(saltErr);
43 | }
44 | user['salt'] = salt;
45 | user['encryptPassword'](user['password'], (encryptErr, hashedPassword) => {
46 | if (encryptErr) {
47 | return next(encryptErr);
48 | }
49 | user['password'] = hashedPassword;
50 | next();
51 | });
52 | });
53 | }
54 | },
55 |
56 | instanceMethods: {
57 | /**
58 | * Authenticate - check if the passwords are the same
59 | *
60 | * @param {String} password
61 | * @param {Function} callback
62 | * @return {Boolean}
63 | * @api public
64 | */
65 | authenticate(password, callback) {
66 | if (!callback) {
67 | return this.password === this.encryptPassword(password);
68 | }
69 |
70 | this.encryptPassword(password, (err, pwdGen) => {
71 | if (err) {
72 | return callback(err);
73 | }
74 |
75 | if (this.password === pwdGen) {
76 | callback(null, true);
77 | } else {
78 | callback(null, false);
79 | }
80 | });
81 | },
82 |
83 | /**
84 | * Make salt
85 | *
86 | * @param {Number} byteSize Optional salt byte size, default to 16
87 | * @param {Function} callback
88 | * @return {String}
89 | * @api public
90 | */
91 | makeSalt(byteSize, callback): any {
92 | let defaultByteSize = 16;
93 |
94 | if (typeof arguments[0] === 'function') {
95 | callback = arguments[0];
96 | byteSize = defaultByteSize;
97 | } else if (typeof arguments[1] === 'function') {
98 | callback = arguments[1];
99 | }
100 |
101 | if (!byteSize) {
102 | byteSize = defaultByteSize;
103 | }
104 |
105 | if (!callback) {
106 | return crypto.randomBytes(byteSize).toString('base64');
107 | }
108 |
109 | return crypto.randomBytes(byteSize, (err, salt) => {
110 | if (err) {
111 | callback(err);
112 | } else {
113 | callback(null, salt.toString('base64'));
114 | }
115 | });
116 | },
117 |
118 | /**
119 | * Encrypt password
120 | *
121 | * @param {String} password
122 | * @param {Function} callback
123 | * @return {String}
124 | * @api public
125 | */
126 | encryptPassword(password, callback): any {
127 | if (!password || !this.salt) {
128 | if (!callback) {
129 | return null;
130 | } else {
131 | return callback('Missing password or salt');
132 | }
133 | }
134 |
135 | let defaultIterations = 10000;
136 | let defaultKeyLength = 64;
137 | let salt = new Buffer(this.salt, 'base64');
138 |
139 | if (!callback) {
140 | return crypto.pbkdf2Sync(password, salt, defaultIterations, defaultKeyLength, 'sha512')
141 | .toString('base64');
142 | }
143 |
144 | return crypto.pbkdf2(password, salt, defaultIterations, defaultKeyLength, 'sha512', (err, key) => {
145 | if (err) {
146 | callback(err);
147 | } else {
148 | callback(null, key.toString('base64'));
149 | }
150 | });
151 | }
152 | }
153 | });
154 |
155 | export default User;
156 |
--------------------------------------------------------------------------------
/server/sql-db/api/user/user.router.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * GET /api/user -> allUsers
3 | * POST /api/user -> create
4 | * GET /api/user/:id -> show
5 | * PUT /api/user/:id/password -> changePassword
6 | * DELETE /api/user/:id -> destroy
7 | */
8 |
9 | let express = require('express');
10 | import * as auth from '../../auth/auth.service';
11 | import * as UserController from './user.controller';
12 |
13 | let router = express.Router();
14 |
15 | router.get('/', auth.hasRole('admin'), UserController.index);
16 | router.delete('/:id', auth.hasRole('admin'), UserController.destroy);
17 | router.put('/:id/password', auth.isAuthenticated(), UserController.changePassword);
18 |
19 | router.post('/', UserController.create, UserController.me);
20 | router.get('/me', auth.isAuthenticated(), UserController.me);
21 | router.get('/:id', auth.isAuthenticated(), UserController.show);
22 |
23 | export {router as userRoutes};
24 |
--------------------------------------------------------------------------------
/server/sql-db/api/user/user.spec.ts:
--------------------------------------------------------------------------------
1 | import proxyquire = require('proxyquire');
2 | let pq = proxyquire.noPreserveCache();
3 | import sinon = require('sinon');
4 |
5 | // userCtrlStub is used to mimic the router
6 | let userCtrlStub = {
7 | index: 'userCtrl.index',
8 | destroy: 'userCtrl.destroy',
9 | me: 'userCtrl.me',
10 | changePassword: 'userCtrl.changePassword',
11 | show: 'userCtrl.show',
12 | create: 'userCtrl.create'
13 | };
14 |
15 | // mimic teh auth service
16 | let authServiceStub = {
17 | isAuthenticated() {
18 | return 'authService.isAuthenticated';
19 | },
20 | hasRole(role) {
21 | return 'authService.hasRole.' + role;
22 | }
23 | };
24 |
25 | // routerStub spys on http RESTFUL requests
26 | let routerStub = {
27 | get: sinon.spy(),
28 | put: sinon.spy(),
29 | post: sinon.spy(),
30 | delete: sinon.spy()
31 | };
32 |
33 | // require the index with our stubbed out modules
34 | // proxyquire simulates the request
35 | // initialize proxyquire
36 | let userIndex = pq('./user.router.js', {
37 | 'express': {
38 | Router() {
39 | return routerStub;
40 | }
41 | },
42 | './user.controller': userCtrlStub,
43 | '../../auth/auth.service': authServiceStub
44 | });
45 |
46 | describe('User API Router:', function() {
47 |
48 | // expects the prozyquire routes to equal the routes it was assigned to
49 | it('should return an express router instance', function() {
50 | expect(userIndex.userRoutes).toEqual(routerStub);
51 | });
52 |
53 | describe('GET /api/users', function() {
54 |
55 | // expect with each request the approapriate endpoint was called
56 | it('should verify admin role and route to user.controller.index', function() {
57 | expect(routerStub.get.withArgs('/', 'authService.hasRole.admin', 'userCtrl.index').calledOnce)
58 | .toBe(true);
59 | });
60 |
61 | });
62 |
63 | describe('DELETE /api/users/:id', function() {
64 |
65 | it('should verify admin role and route to user.controller.destroy', function() {
66 | expect(routerStub.delete.withArgs('/:id', 'authService.hasRole.admin', 'userCtrl.destroy').calledOnce)
67 | .toBe(true);
68 | });
69 |
70 | });
71 |
72 | describe('GET /api/users/me', function() {
73 |
74 | it('should be authenticated and route to user.controller.me', function() {
75 | expect(routerStub.get.withArgs('/me', 'authService.isAuthenticated', 'userCtrl.me').calledOnce)
76 | .toBe(true);
77 | });
78 |
79 | });
80 |
81 | describe('PUT /api/users/:id/password', function() {
82 |
83 | it('should be authenticated and route to user.controller.changePassword', function() {
84 | expect(routerStub.put.withArgs('/:id/password', 'authService.isAuthenticated', 'userCtrl.changePassword').calledOnce)
85 | .toBe(true);
86 | });
87 |
88 | });
89 |
90 | describe('GET /api/users/:id', function() {
91 |
92 | it('should be authenticated and route to user.controller.show', function() {
93 | expect(routerStub.get.withArgs('/:id', 'authService.isAuthenticated', 'userCtrl.show').calledOnce)
94 | .toBe(true);
95 | });
96 |
97 | });
98 |
99 | describe('POST /api/users', function() {
100 |
101 | it('should route to user.controller.create', function() {
102 | expect(routerStub.post.withArgs('/', 'userCtrl.create').calledOnce)
103 | .toBe(true);
104 | });
105 |
106 | });
107 |
108 | });
--------------------------------------------------------------------------------
/server/sql-db/auth/auth.router.ts:
--------------------------------------------------------------------------------
1 | let express = require('express');
2 |
3 | import User from '../api/user/user.model';
4 | import config from '../../../config';
5 | import {localRoutes} from './local/local.router';
6 | import {localSetup} from './local/local.passport';
7 |
8 | // Passport configuration
9 | localSetup(User, config);
10 |
11 | let router = express.Router();
12 |
13 | // Import routes here
14 | // this will setup the passport configuration from the *.passport file
15 | router.use('/local', localRoutes);
16 |
17 | // export the es6 way
18 | export {router as authRoutes};
19 |
--------------------------------------------------------------------------------
/server/sql-db/auth/auth.service.ts:
--------------------------------------------------------------------------------
1 | import User from '../api/user/user.model';
2 |
3 | import config from '../../../config';
4 |
5 | import * as jwt from 'jsonwebtoken';
6 |
7 | let expressJwt = require('express-jwt');
8 | let compose = require('composable-middleware');
9 |
10 | let validateJwt = expressJwt({
11 | secret: config.sessionSecret
12 | });
13 |
14 | /**
15 | * Attaches the user object to the request if authenticated
16 | * Otherwise returns 403
17 | */
18 | export function isAuthenticated() {
19 | return compose()
20 | // Validate jwt
21 | .use((req, res, next) => {
22 | // allow access_token to be passed through query parameter as well
23 | if (req.query && req.query.hasOwnProperty('access_token')) {
24 | req.headers.authorization = 'Bearer ' + req.query.access_token;
25 | }
26 | return validateJwt(req, res, next);
27 | })
28 | // Attach user to request
29 | .use((req, res, next) => {
30 | return User.findById(req.user.id)
31 | .then(user => {
32 | if (!user) {
33 | return res.status(401).json({ message: 'Invalid Token' });
34 | }
35 | req.user = user;
36 | next();
37 | // runnaway promise to remove node warning
38 | return null;
39 | })
40 | .catch(err => next(err));
41 | });
42 | }
43 |
44 | /**
45 | * Checks if the user role meets the minimum requirements of the route
46 | */
47 | export function hasRole(roleRequired) {
48 | if (!roleRequired) {
49 | throw new Error('Required role needs to be set');
50 | }
51 |
52 | return compose()
53 | .use(isAuthenticated())
54 | .use(function meetsRequirements(req, res, next) {
55 | if (config.userRoles.indexOf(req.user.role) >=
56 | config.userRoles.indexOf(roleRequired)) {
57 | next();
58 | } else {
59 | res.status(403).send('Forbidden');
60 | }
61 | });
62 | }
63 |
64 | /**
65 | * Returns a jwt token signed by the app secret
66 | */
67 | export function signToken(id, role) {
68 | return jwt.sign({
69 | id: id,
70 | role: role
71 | }, config.sessionSecret, {
72 | expiresIn: 60 * 60 * 5
73 | });
74 | }
75 |
76 | /**
77 | * Set token cookie directly for oAuth strategies
78 | */
79 | export function setTokenCookie(req, res) {
80 | if (!req.user) {
81 | return res.status(404).send('It looks like you aren\'t logged in, please try again.');
82 | }
83 | let token = signToken(req.user._id, req.user.role);
84 | res.cookie('token', token);
85 | res.redirect('/');
86 | }
87 |
--------------------------------------------------------------------------------
/server/sql-db/auth/local/local.passport.ts:
--------------------------------------------------------------------------------
1 | import * as passport from 'passport';
2 | import {Strategy as LocalStrategy} from 'passport-local';
3 |
4 | // This is the authentication process that happens in passport before the
5 | // router callback function.
6 | // When done is called the items will be passed to the callback function in
7 | // local.router.ts
8 | function localAuthenticate(User, email, password, done) {
9 |
10 | User.findOne({ where: {email: email}}).then(user => {
11 | if (!user) {
12 | return done(null, false, {
13 | message: 'This email is not registered!'
14 | });
15 | }
16 |
17 | user.authenticate(password, (authError, authenticated) => {
18 | if (authError) {
19 | return done(authError);
20 | }
21 | if (!authenticated) {
22 | return done(null, false, {
23 | message: 'This password is not correct!'
24 | });
25 | } else {
26 | return done(null, user);
27 | }
28 | });
29 | })
30 | .catch(err => done(err));
31 | }
32 |
33 | function setup(User, config) {
34 | passport.use(new LocalStrategy({
35 | usernameField: 'email',
36 | passwordField: 'password' // this is the virtual field on the model
37 | }, function (email, password, done) {
38 | return localAuthenticate(User, email, password, done);
39 | }));
40 | }
41 |
42 | export {setup as localSetup};
43 |
--------------------------------------------------------------------------------
/server/sql-db/auth/local/local.router.ts:
--------------------------------------------------------------------------------
1 | let express = require('express');
2 |
3 | import {signToken, isAuthenticated} from '../auth.service';
4 | import {me} from '../../api/user/user.controller';
5 | import * as passport from 'passport';
6 |
7 | let router = express.Router();
8 |
9 | // Only one route is necessary
10 | // When local authentication is required the 'local' hook is known from setup
11 | // in .passport.ts file
12 | router.post('/', function(req, res, next) {
13 |
14 | passport.authenticate('local', function(err, user, info) {
15 |
16 | let error = err || info;
17 | if (error) {
18 | res.status(401).json(error);
19 | return null;
20 | }
21 | if (!user) {
22 | res.status(404).json({ message: 'Something went wrong, please try again' });
23 | return null;
24 | }
25 |
26 | let token = signToken(user.id, user.role);
27 | req.headers.token = token;
28 | req.user = user;
29 | next();
30 |
31 | })(req, res, next);
32 |
33 | }, me);
34 |
35 | export {router as localRoutes};
36 |
--------------------------------------------------------------------------------
/server/sql-db/index.ts:
--------------------------------------------------------------------------------
1 | import config from "../../config";
2 | import Sequelize from "sequelize";
3 |
4 | import * as Rx from 'rxjs';
5 |
6 | //initilize the database
7 | let sequelize = new Sequelize(config.sql.database, config.sql.username, config.sql.password, config.sql.options);
8 |
9 | export default sequelize;
10 |
11 | // Initialize sequelize
12 | export function sequelizeConnect() {
13 | return Rx.Observable.create(observer => {
14 | sequelize.sync().then(function() {
15 |
16 | observer.next();
17 | observer.complete();
18 |
19 | }).catch(err => observer.error(err));
20 | });
21 | };
22 |
23 | export function sequelizeDisconnect() {
24 |
25 | };
26 |
--------------------------------------------------------------------------------
/server/sql-db/seed.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Populate DB with sample data on server start
3 | * to disable, edit config/environment/index.js, and set `seedDB: false`
4 | */
5 | import User from './api/user/user.model';
6 |
7 | export default function sqlSeed(env?: string): void {
8 |
9 | // Insert seeds below
10 | switch (env) {
11 | case "development":
12 | User.sync().then(() =>{
13 | User.destroy({truncate: true, cascade: true}).then(() => {
14 | User.create({
15 | username: 'AdMiN',
16 | firstname:'admin',
17 | lastname: 'admin',
18 | email: 'admin@admin.com',
19 | role: 'admin',
20 | password: 'admin1'
21 | }).then(() => {
22 | User.create({
23 | username: 'test',
24 | firstname:'testFirst',
25 | lastname: 'testLast',
26 | email: 'test@test.com',
27 | role: 'user',
28 | password: 'test'
29 | }).catch(() => {});
30 | }).catch(() => {});
31 | }).catch(err => console.log(err.message));
32 | }).catch(err => console.log(err.message));
33 |
34 | break;
35 | case "test":
36 | User.sync().then(() =>{
37 | User.destroy({truncate: true, cascade: true}).then(() => {
38 | User.create({
39 | username: 'test',
40 | firstname:'testFirst',
41 | lastname: 'testLast',
42 | email: 'test@test.com',
43 | role: 'user',
44 | password: 'test'
45 | }).catch(() => {});
46 | }).catch(err => console.log(err.message));
47 | }).catch(err => console.log(err.message));
48 | break;
49 | default:
50 | // code...
51 | break;
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/tsconfig-aot.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "declaration": false,
4 | "target": "es5",
5 | "module": "es2015",
6 | "moduleResolution": "node",
7 | "sourceMap": true,
8 | "allowSyntheticDefaultImports": true,
9 | "emitDecoratorMetadata": true,
10 | "noImplicitAny": false,
11 | "suppressImplicitAnyIndexErrors": true,
12 | "experimentalDecorators": true,
13 | "lib": ["es6", "dom"],
14 | "types": [
15 | "graceful-fs",
16 | "immutable",
17 | "lodash",
18 | "core-js",
19 | "socket.io-client"
20 | ]
21 | },
22 | "files": [
23 | "client/modules/app.module.ts",
24 | "client/modules/app.server.module.ts",
25 | "client/app.ts",
26 | "client/app-aot.ts"
27 | ],
28 | "angularCompilerOptions": {
29 | "genDir": "ngc-aot",
30 | "skipMetadataEmit" : true,
31 | "entryModule": "client/app.module#MainModule"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/tsconfig.browser.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "angularCompilerOptions": {
4 | "entryModule": "./client/modules/browser-app.module#BrowserAppModule"
5 | },
6 | "exclude": [
7 | "./main.server.aot.ts"
8 | ]
9 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "declaration": false,
4 | "target": "es5",
5 | "module": "commonjs",
6 | "moduleResolution": "node",
7 | "sourceMap": false,
8 | "emitDecoratorMetadata": true,
9 | "experimentalDecorators": true,
10 | "removeComments": false,
11 | "noImplicitAny": false,
12 | "allowSyntheticDefaultImports": true,
13 | "suppressImplicitAnyIndexErrors": true,
14 | "rootDir": "./",
15 | "outDir": "dist",
16 | "lib": ["es2016", "dom"],
17 | "typeRoots": [
18 | "./node_modules/@types"
19 | ]
20 | },
21 | "compileOnSave": false,
22 | "exclude": [
23 | "./dist",
24 | "./node_modules",
25 | "./config/gulp",
26 | "./client/app-aot.ts"
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/tsconfig.server.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "angularCompilerOptions": {
4 | "entryModule": "./client/modules/server-app.module#ServerAppModule"
5 | },
6 | "exclude": []
7 | }
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | // Look in ./config/webpack folder for webpack.client.x.js
2 |
3 | module.exports = function(env) {
4 | // use webpack --env to change environment
5 | switch (env) {
6 | case 'server:prod:e2e':
7 | return require('./config/webpack/webpack.server')({ env: 'prod:e2e' });
8 | break;
9 | case 'prod:e2e':
10 | return require('./config/webpack/webpack.client')({ env: 'prod', e2e: true });
11 | break;
12 | case 'server:prod':
13 | return require('./config/webpack/webpack.server')({ env: 'prod' });
14 | break;
15 | case 'prod':
16 | case 'production':
17 | return require('./config/webpack/webpack.client')({ env: 'prod' });
18 | break;
19 | case 'server:test':
20 | return require('./config/webpack/webpack.server')({ env: 'test' });
21 | break;
22 | case 'test':
23 | case 'testing':
24 | return require('./config/webpack/webpack.client')({ env: 'test' });
25 | break;
26 | case 'server:dev':
27 | return require('./config/webpack/webpack.server')({ env: 'dev' });
28 | break;
29 | case 'dev':
30 | case 'development':
31 | default:
32 | return require('./config/webpack/webpack.client')({ env: 'dev' });
33 | }
34 | }
35 |
--------------------------------------------------------------------------------