diff --git a/controllers/base.ts b/controllers/base.ts
new file mode 100644
index 00000000..4f00760e
--- /dev/null
+++ b/controllers/base.ts
@@ -0,0 +1,68 @@
+abstract class BaseCtrl {
+
+ abstract model: any;
+
+ // Get all
+ getAll = async (req, res) => {
+ try {
+ console.log('GET ALL')
+ const all = await this.model.find({});
+ res.status(200).json(all);
+ } catch (err) {
+ return res.status(400).json({ error: err.message });
+ }
+ }
+
+ // Count all
+ count = async (req, res) => {
+ try {
+ const count = await this.model.count();
+ res.status(200).json(count);
+ } catch (err) {
+ return res.status(400).json({ error: err.message });
+ }
+ }
+
+ // Insert
+ insert = async (req, res) => {
+ try {
+ console.log('show US THIS DAMN REQ BODY '+req.body)
+ const obj = await new this.model(req.body).save();
+ res.status(201).json(obj);
+ } catch (err) {
+ return res.status(400).json({ error: err.message });
+ }
+ }
+
+ // Get by id
+ get = async (req, res) => {
+ try {
+ const obj = await this.model.findOne({ _id: req.params.id });
+ res.status(200).json(obj);
+ } catch (err) {
+ return res.status(500).json({ error: err.message });
+ }
+ }
+
+ // Update by id
+ update = async (req, res) => {
+ try {
+ await this.model.findOneAndUpdate({ _id: req.params.id }, req.body);
+ res.sendStatus(200);
+ } catch (err) {
+ return res.status(400).json({ error: err.message });
+ }
+ }
+
+ // Delete by id
+ delete = async (req, res) => {
+ try {
+ await this.model.findOneAndRemove({ _id: req.params.id });
+ res.sendStatus(200);
+ } catch (err) {
+ return res.status(400).json({ error: err.message });
+ }
+ }
+ }
+
+ export default BaseCtrl;
\ No newline at end of file
diff --git a/controllers/cat.ts b/controllers/cat.ts
new file mode 100644
index 00000000..b9166660
--- /dev/null
+++ b/controllers/cat.ts
@@ -0,0 +1,8 @@
+import Cat from '../models/cat';
+import BaseCtrl from './base';
+
+class CatCtrl extends BaseCtrl {
+ model = Cat;
+}
+
+export default CatCtrl;
\ No newline at end of file
diff --git a/controllers/user.ts b/controllers/user.ts
new file mode 100644
index 00000000..fe27b738
--- /dev/null
+++ b/controllers/user.ts
@@ -0,0 +1,22 @@
+import * as jwt from 'jsonwebtoken';
+
+import User from '../models/user';
+import BaseCtrl from './base';
+
+class UserCtrl extends BaseCtrl {
+ model = User;
+
+ login = (req, res) => {
+ this.model.findOne({ email: req.body.email }, (err, user) => {
+ if (!user) { return res.sendStatus(403); }
+ user.comparePassword(req.body.password, (error, isMatch) => {
+ if (!isMatch) { return res.sendStatus(403); }
+ const token = jwt.sign({ user }, process.env.SECRET_TOKEN); // , { expiresIn: 10 } seconds
+ res.status(200).json({ token });
+ });
+ });
+ }
+
+}
+
+export default UserCtrl;
\ No newline at end of file
diff --git a/models/cat.ts b/models/cat.ts
new file mode 100644
index 00000000..6e9cebef
--- /dev/null
+++ b/models/cat.ts
@@ -0,0 +1,11 @@
+import * as mongoose from 'mongoose';
+
+const catSchema = new mongoose.Schema({
+ name: String,
+ weight: Number,
+ age: Number
+});
+
+const Cat = mongoose.model('Cat', catSchema);
+
+export default Cat;
\ No newline at end of file
diff --git a/models/user.ts b/models/user.ts
new file mode 100644
index 00000000..1bb16550
--- /dev/null
+++ b/models/user.ts
@@ -0,0 +1,42 @@
+import * as bcrypt from 'bcryptjs';
+import * as mongoose from 'mongoose';
+
+const userSchema = new mongoose.Schema({
+ username: String,
+ email: { type: String, unique: true, lowercase: true, trim: true },
+ password: String,
+ role: String
+});
+
+// Before saving the user, hash the password
+userSchema.pre('save', function(next): void {
+ const user = this;
+ if (!user.isModified('password')) { return next(); }
+ bcrypt.genSalt(10, (err, salt) => {
+ if (err) { return next(err); }
+ bcrypt.hash(user.password, salt, (error, hash) => {
+ if (error) { return next(error); }
+ user.password = hash;
+ next();
+ });
+ });
+});
+
+userSchema.methods.comparePassword = function(candidatePassword, callback): void {
+ bcrypt.compare(candidatePassword, this.password, (err, isMatch) => {
+ if (err) { return callback(err); }
+ callback(null, isMatch);
+ });
+};
+
+// Omit the password when returning a user
+userSchema.set('toJSON', {
+ transform: (doc, ret, options) => {
+ delete ret.password;
+ return ret;
+ }
+});
+
+const User = mongoose.model('User', userSchema);
+
+export default User;
\ No newline at end of file
diff --git a/mongo.ts b/mongo.ts
new file mode 100644
index 00000000..b5f0ec5e
--- /dev/null
+++ b/mongo.ts
@@ -0,0 +1,15 @@
+import * as mongoose from 'mongoose';
+
+async function setMongo() {
+ const mongodbURI = 'mongodb://localhost:27017/angularfullstack'
+ mongoose.Promise = global.Promise;
+ mongoose.set('useCreateIndex', true);
+ mongoose.set('useNewUrlParser', true);
+ mongoose.set('useFindAndModify', false);
+ mongoose.set('useUnifiedTopology', true);
+ // Connect to MongoDB using Mongoose
+ await mongoose.connect(mongodbURI);
+ console.log('Connected to MongoDB');
+}
+
+export default setMongo;
\ No newline at end of file
diff --git a/routes.ts b/routes.ts
new file mode 100644
index 00000000..95fa1494
--- /dev/null
+++ b/routes.ts
@@ -0,0 +1,24 @@
+import * as express from 'express';
+
+import CatCtrl from './controllers/cat';
+import UserCtrl from './controllers/user';
+function setRoutes(app) {
+ const router = express.Router();
+ const catCtrl = new CatCtrl();
+ const userCtrl = new UserCtrl();
+
+ // Cats
+ router.route('/cats').get(catCtrl.getAll);
+
+ // Users
+ router.route('/login').post(userCtrl.login);
+ router.route('/users').get(userCtrl.getAll);
+ router.route('/user').post(userCtrl.insert);
+ router.route('/user/:id').get(userCtrl.get);
+
+ // Apply the routes to our application with the prefix /api
+ app.use('/api', router);
+
+}
+
+export default setRoutes;
\ No newline at end of file
diff --git a/server.ts b/server.ts
new file mode 100644
index 00000000..f773c644
--- /dev/null
+++ b/server.ts
@@ -0,0 +1,84 @@
+import 'zone.js/dist/zone-node';
+
+import { ngExpressEngine } from '@nguniversal/express-engine';
+import * as express from 'express';
+import { join } from 'path';
+
+import { AppServerModule } from './src/main.server';
+import { APP_BASE_HREF } from '@angular/common';
+import { existsSync } from 'fs';
+
+import setRoutes from './routes';
+import setMongo from './mongo';
+import * as dotenv from 'dotenv';
+// add to use POST ROUTES
+const bodyParser = require("body-parser");
+// The Express app is exported so that it can be used by serverless Functions.
+export function app() {
+ const server = express();
+ dotenv.config();
+ const distFolder = join(process.cwd(), 'dist/browser');
+ const indexHtml = existsSync(join(distFolder, 'index.original.html'))
+ ? 'index.original.html'
+ : 'index';
+
+ // Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
+ server.engine(
+ 'html',
+ ngExpressEngine({
+ bootstrap: AppServerModule,
+ })
+ );
+
+ server.set('view engine', 'html');
+ server.set('views', distFolder);
+ server.use(bodyParser.urlencoded({ extended: false }))
+ server.use(bodyParser.json())
+ // TODO: implement data requests securely
+ /* server.get('/api/**', (req, res) => {
+ res.status(404).send('data requests are not yet supported');
+ });*/
+ setMongo();
+ setRoutes(server)
+
+ // Serve static files from /browser
+ server.get(
+ '*.*',
+ express.static(distFolder, {
+ maxAge: '1y',
+ })
+ );
+
+ // All regular routes use the Universal engine
+ server.get('*', (req, res) => {
+ console.log(req.url);
+ res.render(indexHtml, {
+ req,
+ providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }],
+ });
+ });
+
+ return server;
+}
+
+function run() {
+ const port = process.env.PORT || 4000;
+
+ // Start up the Node server
+ const server = app();
+ server.listen(port, () => {
+ console.log(`Node Express server listening on http://localhost:${port}`);
+ });
+}
+
+// Webpack will replace 'require' with '__webpack_require__'
+// '__non_webpack_require__' is a proxy to Node 'require'
+// The below code is to ensure that the server is run only when not requiring the bundle.
+declare const __non_webpack_require__: NodeRequire;
+const mainModule = __non_webpack_require__.main;
+const moduleFilename = (mainModule && mainModule.filename) || '';
+if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
+ run();
+}
+
+export * from './src/main.server';
diff --git a/src/app/about/about.component.html b/src/app/about/about.component.html
new file mode 100644
index 00000000..b6f0bdb6
--- /dev/null
+++ b/src/app/about/about.component.html
@@ -0,0 +1,53 @@
+
\ No newline at end of file
diff --git a/src/app/about/about.component.scss b/src/app/about/about.component.scss
new file mode 100644
index 00000000..e69de29b
diff --git a/src/app/about/about.component.spec.ts b/src/app/about/about.component.spec.ts
new file mode 100644
index 00000000..c5102a05
--- /dev/null
+++ b/src/app/about/about.component.spec.ts
@@ -0,0 +1,32 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { By } from '@angular/platform-browser';
+
+import { AboutComponent } from './about.component';
+
+describe('Component: About', () => {
+ let component: AboutComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ AboutComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(AboutComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+
+ it('should display the page header text', () => {
+ const el = fixture.debugElement.query(By.css('h4')).nativeElement;
+ expect(el.textContent).toContain('About');
+ });
+
+});
\ No newline at end of file
diff --git a/src/app/about/about.component.ts b/src/app/about/about.component.ts
new file mode 100644
index 00000000..9c7ea801
--- /dev/null
+++ b/src/app/about/about.component.ts
@@ -0,0 +1,12 @@
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'app-about',
+ templateUrl: './about.component.html',
+ styleUrls: ['./about.component.scss']
+})
+export class AboutComponent {
+
+ constructor() { }
+
+}
\ No newline at end of file
diff --git a/src/app/account/account.component.html b/src/app/account/account.component.html
new file mode 100644
index 00000000..26c1e7f0
--- /dev/null
+++ b/src/app/account/account.component.html
@@ -0,0 +1,44 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/app/account/account.component.spec.ts b/src/app/account/account.component.spec.ts
new file mode 100644
index 00000000..c219f541
--- /dev/null
+++ b/src/app/account/account.component.spec.ts
@@ -0,0 +1,74 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { By } from '@angular/platform-browser';
+import { FormsModule } from '@angular/forms';
+
+import { ToastComponent } from '../shared/toast/toast.component';
+import { AuthService } from '../services/auth.service';
+import { UserService } from '../services/user.service';
+import { AccountComponent } from './account.component';
+import { of, Observable } from 'rxjs';
+
+class AuthServiceMock { }
+
+class UserServiceMock {
+ mockUser = {
+ username: 'Test user',
+ email: 'test@example.com',
+ role: 'user'
+ };
+ getUser(): Observable