├── .docker ├── node.development.dockerfile ├── node.production.dockerfile └── useful-commands.md ├── .gitignore ├── .vscode └── settings.json ├── README.md ├── app ├── app.component.ts ├── app.module.ts ├── app.routing.ts ├── customers │ ├── customers.component.html │ └── customers.component.ts ├── main.ts └── shared │ ├── interfaces.ts │ └── services │ └── data.service.ts ├── config ├── config.development.json └── config.production.json ├── docker-compose.production.yml ├── docker-compose.yml ├── images ├── female.png ├── male.png └── people.png ├── index.html ├── lib ├── accessDB.js ├── configLoader.js └── dbSeeder.js ├── models ├── customer.js ├── settings.js └── state.js ├── package-lock.json ├── package.json ├── routes ├── api.js └── index.js ├── server.js ├── styles └── styles.css ├── systemjs-angular-loader.js ├── systemjs.config.js ├── tsconfig.json └── views └── index.pug /.docker/node.development.dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:latest 2 | 3 | MAINTAINER Dan Wahlin 4 | 5 | ENV CONTAINER_PATH /var/www/angularrestfulservice 6 | 7 | WORKDIR $CONTAINER_PATH 8 | 9 | RUN npm install nodemon -g 10 | 11 | EXPOSE 3000 12 | 13 | ENTRYPOINT ["nodemon", "server.js"] 14 | 15 | 16 | # Build: docker build -f node.dockerfile -t danwahlin/node . 17 | 18 | # Option 1 19 | # Start MongoDB and Node (link Node to MongoDB container with legacy linking) 20 | 21 | # docker run -d --name mongodb mongo 22 | # docker run -d -p 3000:3000 --link mongodb --name nodeapp danwahlin/node 23 | 24 | # Option 2: Create a custom bridge network and add containers into it 25 | 26 | # docker network create --driver bridge isolated_network 27 | # docker run -d --net=isolated_network --name mongodb mongo 28 | # docker run -d --net=isolated_network --name nodeapp -p 3000:3000 danwahlin/node 29 | 30 | # Option 3: Use Docker Compose 31 | 32 | # docker-compose build 33 | # docker-compose up 34 | 35 | # Seed the database with sample database 36 | # Run: docker exec nodeapp node lib/dbSeeder.js -------------------------------------------------------------------------------- /.docker/node.production.dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:latest 2 | 3 | MAINTAINER Dan Wahlin 4 | 5 | ENV CONTAINER_PATH /var/www/angularrestfulservice 6 | 7 | COPY . $CONTAINER_PATH 8 | WORKDIR $CONTAINER_PATH 9 | 10 | RUN npm install nodemon -g 11 | 12 | EXPOSE 3000 13 | 14 | ENTRYPOINT ["nodemon", "server.js"] 15 | 16 | # Build: docker build -f node.dockerfile -t danwahlin/node . 17 | 18 | # Option 1 19 | # Start MongoDB and Node (link Node to MongoDB container with legacy linking) 20 | 21 | # docker run -d --name mongodb mongo 22 | # docker run -d -p 3000:3000 --link mongodb --name nodeapp danwahlin/node 23 | 24 | # Option 2: Create a custom bridge network and add containers into it 25 | 26 | # docker network create --driver bridge isolated_network 27 | # docker run -d --net=isolated_network --name mongodb mongo 28 | # docker run -d --net=isolated_network --name nodeapp -p 3000:3000 danwahlin/node 29 | 30 | # Option 3: Use Docker Compose 31 | 32 | # docker-compose build 33 | # docker-compose up 34 | 35 | # Seed the database with sample database 36 | # Run: docker exec nodeapp node lib/dbSeeder.js -------------------------------------------------------------------------------- /.docker/useful-commands.md: -------------------------------------------------------------------------------- 1 | #Useful Docker Commands 2 | 3 | ##Docker Machine 4 | 5 | - `docker-machine start` - Start VM 6 | - `docker-machine stop` - Stop VM 7 | - `docker-machine env` - Display Docker client setup commands 8 | 9 | ##Docker Client 10 | 11 | - `docker --help` - Get help on a specific command 12 | - `docker pull ` - Pull image from Docker Hub 13 | - `docker images` - Show all images 14 | - `docker rmi ` - Remove specific images 15 | - `docker rmi $(docker images | grep "^" | awk "{print $3}")` - Remove untagged images - 16 | - `docker ps -a` - Show all containers 17 | - `docker rm ` -Remove specific container 18 | - `docker rm $(docker ps -a -q)` Remove all containers 19 | - `docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}'` - Formatted list of containers 20 | - `docker run -d --name -p : ` - Run a container 21 | - `docker build -f -t .` - Build an image from a Dockerfile 22 | - `docker login` - Login using your Docker credentials 23 | - `docker push ` - Push an image to Docker hub 24 | 25 | ## Docker Compose 26 | 27 | - `docker-compose build` - Build images based on docker-compose 28 | - `docker-compose up -d` - Start in daemon mode 29 | - `docker-compose logs` - Show logs from containers 30 | - `docker-compose up` - Start containers based on docker-compose 31 | - `docker-compose stop` - Stop containers 32 | - `docker-compose down` - Stop and remove containers 33 | 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | app/**/*.js 4 | app/**/*.js.map 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "**/app/**/*.js.map": true, 5 | "**/app/**/*.js": true 6 | } 7 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angular RESTful Service Example 2 | 3 | This project provides a simple look at getting started using Angular 4 | to call into a RESTful service. Simply clone the project or download and extract the .zip to get started. 5 | 6 | ## Angular Concepts Covered 7 | 8 | * Using TypeScript classes and modules 9 | * Modules are loaded with System.js 10 | * Using Custom Components 11 | * Using the Http object for Ajax calls along with RxJS observables 12 | * Performing GET and PUT requests to the server 13 | * Working with Angular service classes for Ajax calls 14 | * Using Angular databinding and built-in directives 15 | 16 | ## Software Requirements To Run Locally (there's a Docker option below as well) 17 | 18 | * Node.js 4.0.0 or higher 19 | * MongoDB 3.2 or higher 20 | 21 | ### Running the Application Locally 22 | 23 | 1. Install Node.js and MongoDB on your dev box 24 | 25 | 1. Execute 'mongod' to start the MongoDB daemon if it's not already running 26 | 27 | 1. Install Nodemon: `npm install nodemon -g` 28 | 29 | 1. Open `config/config.development.json` and change the host from `mongodb` to `localhost` 30 | 31 | 1. Run `npm install` to install app dependencies 32 | 33 | 1. Run `npm start` to compile the TypeScript and start the server 34 | 35 | 1. Browse to http://localhost:3000 36 | 37 | ## Running the Application with Docker 38 | 39 | 1. Install Docker for Mac/Windows or Docker Toolbox - https://www.docker.com/products/overview 40 | 41 | 1. Open a command prompt window 42 | 43 | 1. Run `npm install` 44 | 45 | 1. Run `npm run tsc:w` to compile TypeScript to JavaScript locally (leave the window running). This is only needed when in "dev" mode. 46 | 47 | 1. Open another command window and navigate to this application's root folder in the command window 48 | 49 | 1. Run `docker-compose build` to build the images 50 | 51 | 1. Run `docker-compose up` to run the containers 52 | 53 | 1. Navigate to http://localhost:3000 if using Docker for Mac/Windows or http://192.168.99.100:3000 if using Docker Toolbox in a browser 54 | 55 | 1. Live long and prosper 56 | 57 | -------------------------------------------------------------------------------- /app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | moduleId: module.id, 5 | selector: 'app-container', 6 | template: `` 7 | }) 8 | export class AppComponent { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 4 | import { HttpModule} from '@angular/http'; 5 | 6 | import { AppComponent } from './app.component'; 7 | import { CustomersComponent } from './customers/customers.component'; 8 | import { app_routing } from './app.routing'; 9 | import { DataService } from './shared/services/data.service'; 10 | 11 | @NgModule({ 12 | imports: [ BrowserModule, FormsModule, ReactiveFormsModule, HttpModule, app_routing ], 13 | declarations: [ AppComponent, CustomersComponent ], 14 | providers: [ DataService ], 15 | bootstrap: [ AppComponent ] 16 | }) 17 | export class AppModule { } -------------------------------------------------------------------------------- /app/app.routing.ts: -------------------------------------------------------------------------------- 1 | import { RouterModule, Routes } from '@angular/router'; 2 | 3 | import { CustomersComponent } from './customers/customers.component'; 4 | 5 | const app_routes: Routes = [ 6 | { path: '', pathMatch:'full', redirectTo: '/customers' }, 7 | { path: 'customers', component: CustomersComponent } 8 | ]; 9 | 10 | export const app_routing = RouterModule.forRoot(app_routes); -------------------------------------------------------------------------------- /app/customers/customers.component.html: -------------------------------------------------------------------------------- 1 |

Customers

2 |
3 | 4 | 5 | 19 | 20 |
    21 |
  • 22 | {{ customer.firstName }} {{ customer.lastName }} 23 |
  • 24 |
25 | 26 | 27 |
28 |
29 |
30 |
31 | {{ customer.firstName }} {{ customer.lastName }} 32 |
33 |
34 | Edit 36 |
37 |
38 |
39 | First:   40 | Last: 41 | Save  42 | Cancel 43 |
44 |
45 |
46 | 47 | {{ errorMessage }} 48 |
-------------------------------------------------------------------------------- /app/customers/customers.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | import { DataService } from '../shared/services/data.service'; 4 | import { ICustomer } from '../shared/interfaces'; 5 | 6 | @Component({ 7 | selector: 'customers', 8 | templateUrl: './customers.component.html' 9 | }) 10 | export class CustomersComponent implements OnInit { 11 | 12 | customers: ICustomer[] = []; 13 | editId: number = 0; 14 | errorMessage: string; 15 | 16 | constructor(private dataService: DataService) { } 17 | 18 | ngOnInit() { 19 | this.dataService.getCustomersSummary() 20 | .subscribe((data: ICustomer[]) => this.customers = data); 21 | } 22 | 23 | save(customer: ICustomer) { 24 | this.dataService.updateCustomer(customer) 25 | .subscribe((status: boolean) => { 26 | if (status) { 27 | this.editId = 0; 28 | } else { 29 | this.errorMessage = 'Unable to save customer'; 30 | } 31 | }) 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /app/main.ts: -------------------------------------------------------------------------------- 1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 2 | import { enableProdMode } from '@angular/core'; 3 | 4 | import { AppModule } from './app.module'; 5 | 6 | //enableProdMode(); //Uncomment for production 7 | platformBrowserDynamic().bootstrapModule(AppModule) 8 | .then((success: any) => console.log('App bootstrapped')) 9 | .catch((err: any) => console.error(err)); 10 | -------------------------------------------------------------------------------- /app/shared/interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface ICustomer { 2 | id: number; 3 | firstName: string; 4 | lastName: string; 5 | email: string; 6 | address: string; 7 | city: string; 8 | state: IState; 9 | zip: number; 10 | gender: string; 11 | latitude: number; 12 | longitude: number; 13 | orderCount?: number; 14 | orders?: IOrder[]; 15 | ordersTotal?: number; 16 | } 17 | 18 | export interface IState { 19 | abbreviation: string; 20 | name: string; 21 | } 22 | 23 | export interface IOrder { 24 | product: string; 25 | price: number; 26 | quantity: number; 27 | orderTotal?: number; 28 | } -------------------------------------------------------------------------------- /app/shared/services/data.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Http, Response, Headers, RequestOptions } from '@angular/http'; 3 | 4 | import 'rxjs/add/operator/map'; 5 | import 'rxjs/add/operator/catch'; 6 | import { Observable } from 'rxjs/Rx'; 7 | 8 | import { ICustomer } from '../interfaces'; 9 | 10 | @Injectable() 11 | export class DataService { 12 | 13 | private url: string = 'api/dataservice/'; 14 | 15 | constructor(private http: Http) { } 16 | 17 | getCustomersSummary() : Observable { 18 | return this.http.get(this.url + 'customers') 19 | .map((resp: Response) => resp.json()) 20 | .catch(this.handleError); 21 | } 22 | 23 | updateCustomer(customer: ICustomer) { 24 | return this.http.put(this.url + 'putCustomer/' + customer.id, customer) 25 | .map((response: Response) => response.json()) 26 | .catch(this.handleError); 27 | } 28 | 29 | handleError(error: any) { 30 | console.error(error); 31 | return Observable.throw(error.json().error || 'Server error'); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /config/config.development.json: -------------------------------------------------------------------------------- 1 | { 2 | "databaseConfig": { 3 | "host": "mongodb", 4 | "database": "customermanager" 5 | } 6 | } -------------------------------------------------------------------------------- /config/config.production.json: -------------------------------------------------------------------------------- 1 | { 2 | "databaseConfig": { 3 | "host": "mongodb", 4 | "database": "customermanager" 5 | } 6 | } -------------------------------------------------------------------------------- /docker-compose.production.yml: -------------------------------------------------------------------------------- 1 | # Run docker-compose -f docker-compose.production.yml build 2 | # Run docker-compose -f docker-compose.production.yml up 3 | # Live long and prosper 4 | 5 | version: '2' 6 | 7 | services: 8 | 9 | node: 10 | container_name: nodeapp 11 | image: nodeapp 12 | build: 13 | context: . 14 | dockerfile: .docker/node.production.dockerfile 15 | environment: 16 | - NODE_ENV=production 17 | ports: 18 | - "3000:3000" 19 | networks: 20 | - nodeapp-network 21 | 22 | #No authentication is provided here - just a demo! Read the Dockerfile 23 | #for more information about adding authentication. 24 | mongodb: 25 | container_name: mongodb 26 | image: mongo 27 | networks: 28 | - nodeapp-network 29 | 30 | networks: 31 | nodeapp-network: 32 | driver: bridge -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | # Run docker-compose build 2 | # Run docker-compose up 3 | # Live long and prosper 4 | 5 | version: '2' 6 | 7 | services: 8 | 9 | node: 10 | container_name: nodeapp 11 | image: nodeapp 12 | build: 13 | context: . 14 | dockerfile: .docker/node.development.dockerfile 15 | volumes: 16 | - .:/var/www/angularrestfulservice 17 | environment: 18 | - NODE_ENV=development 19 | ports: 20 | - "3000:3000" 21 | networks: 22 | - nodeapp-network 23 | 24 | #No authentication is provided here - just a demo! Read the Dockerfile 25 | #for more information about adding authentication. 26 | mongodb: 27 | container_name: mongodb 28 | image: mongo 29 | networks: 30 | - nodeapp-network 31 | 32 | networks: 33 | nodeapp-network: 34 | driver: bridge -------------------------------------------------------------------------------- /images/female.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanWahlin/Angular-RESTfulService/8d76a09b7c7396ebff6bcf4357aa5f6952ed0cb6/images/female.png -------------------------------------------------------------------------------- /images/male.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanWahlin/Angular-RESTfulService/8d76a09b7c7396ebff6bcf4357aa5f6952ed0cb6/images/male.png -------------------------------------------------------------------------------- /images/people.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanWahlin/Angular-RESTfulService/8d76a09b7c7396ebff6bcf4357aa5f6952ed0cb6/images/people.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Angular TypeScript App 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 24 | 25 | 26 | 27 | 41 | 42 |
43 | 44 | Loading... 45 | 46 |

47 |
48 | 49 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /lib/accessDB.js: -------------------------------------------------------------------------------- 1 | // Module dependencies 2 | var util = require('util'), 3 | mongoose = require('mongoose'), 4 | Schema = mongoose.Schema, 5 | Customer = require('../models/customer'), 6 | State = require('../models/state'), 7 | dbConfig = require('./configLoader').databaseConfig, 8 | connectionString = 'mongodb://' + dbConfig.host + '/' + dbConfig.database, 9 | connection = null; 10 | 11 | // connect to database 12 | module.exports = { 13 | // Define class variable 14 | myEventID: null, 15 | 16 | // initialize DB 17 | startup: function (callback) { 18 | mongoose.connect(connectionString, { useMongoClient: true }); 19 | connection = mongoose.connection; 20 | mongoose.Promise = global.Promise; 21 | mongoose.connection.on('open', function () { 22 | console.log('We have connected to mongodb'); 23 | callback(); 24 | }); 25 | 26 | }, 27 | 28 | // disconnect from database 29 | close: function () { 30 | connection.close(function () { 31 | console.log('Mongoose default connection disconnected through app termination'); 32 | process.exit(0); 33 | }); 34 | }, 35 | 36 | // get all the customers 37 | getCustomers: function (skip, top, callback) { 38 | console.log('*** accessDB.getCustomers'); 39 | Customer.count(function(err, custsCount) { 40 | var count = custsCount; 41 | console.log('Customers count: ' + count); 42 | 43 | Customer.find({}) 44 | /* 45 | //This stopped working (not sure if it's a mongo or mongoose change) so doing 2 queries now 46 | function (err, customers) { 47 | console.log('Customers count: ' + customers.length); 48 | count = customers.length; 49 | })*/ 50 | .skip(skip) 51 | .limit(top) 52 | .exec(function (err, customers) { 53 | callback(null, { 54 | count: count, 55 | customers: customers 56 | }); 57 | }); 58 | 59 | }); 60 | }, 61 | 62 | // get the customer summary 63 | getCustomersSummary: function (skip, top, callback) { 64 | console.log('*** accessDB.getCustomersSummary'); 65 | Customer.count(function(err, custsCount) { 66 | var count = custsCount; 67 | console.log('Customers count: ' + count); 68 | 69 | Customer.find({}, { '_id': 0, 'firstName': 1, 'lastName': 1, 'city': 1, 'state': 1, 'orderCount': 1, 'gender': 1, 'id': 1 }) 70 | /* 71 | //This stopped working (not sure if it's a mongo or mongoose change) so doing 2 queries now 72 | function (err, customersSummary) { 73 | console.log('Customers Summary count: ' + customersSummary.length); 74 | count = customersSummary.length; 75 | }) 76 | */ 77 | .skip(skip) 78 | .limit(top) 79 | .exec(function (err, customersSummary) { 80 | callback(null, { 81 | count: count, 82 | customersSummary: customersSummary 83 | }); 84 | }); 85 | 86 | }); 87 | }, 88 | 89 | // get a customer 90 | getCustomer: function (id, callback) { 91 | console.log('*** accessDB.getCustomer'); 92 | Customer.find({ 'id': id }, {}, function (err, customer) { 93 | callback(null, customer[0]); 94 | }); 95 | }, 96 | 97 | // insert a customer 98 | insertCustomer: function (req_body, state, callback) { 99 | console.log('*** accessDB.insertCustomer'); 100 | 101 | var customer = new Customer(); 102 | var s = { 'id': state[0].id, 'abbreviation': state[0].abbreviation, 'name': state[0].name } 103 | 104 | customer.firstName = req_body.firstName; 105 | customer.lastName = req_body.lastName; 106 | customer.email = req_body.email; 107 | customer.address = req_body.address; 108 | customer.city = req_body.city; 109 | customer.state = s; 110 | customer.stateId = state[0].id; 111 | customer.zip = req_body.zip; 112 | customer.gender = req_body.gender; 113 | customer.id = 1; // The id is calculated by the Mongoose pre 'save'. 114 | 115 | customer.save(function (err, customer) { 116 | if (err) { console.log('*** new customer save err: ' + err); return callback(err); } 117 | 118 | callback(null, customer.id); 119 | }); 120 | }, 121 | 122 | editCustomer: function (id, req_body, state, callback) { 123 | console.log('*** accessDB.editCustomer'); 124 | 125 | var s = { 'id': state[0].id, 'abbreviation': state[0].abbreviation, 'name': state[0].name } 126 | 127 | Customer.findOne({ 'id': id }, { '_id': 1, 'firstName': 1, 'lastName': 1, 'city': 1, 'state': 1, 'stateId': 1, 'gender': 1, 'id': 1 }, function (err, customer) { 128 | if (err) { return callback(err); } 129 | 130 | customer.firstName = req_body.firstName || customer.firstName; 131 | customer.lastName = req_body.lastName || customer.lastName; 132 | customer.email = req_body.email || customer.email; 133 | customer.address = req_body.address || customer.address; 134 | customer.city = req_body.city || customer.city; 135 | customer.state = s; 136 | customer.stateId = s.id; 137 | customer.zip = req_body.zip || customer.zip; 138 | customer.gender = req_body.gender || customer.gender; 139 | 140 | 141 | customer.save(function (err) { 142 | if (err) { console.log('*** accessDB.editCustomer err: ' + err); return callback(err); } 143 | 144 | callback(null); 145 | }); 146 | 147 | }); 148 | }, 149 | 150 | // delete a customer 151 | deleteCustomer: function (id, callback) { 152 | console.log('*** accessDB.deleteCustomer'); 153 | Customer.remove({ 'id': id }, function (err, customer) { 154 | callback(null); 155 | }); 156 | }, 157 | 158 | // get a customer's email 159 | checkUnique: function (id, property, value, callback) { 160 | console.log('*** accessDB.checkUnique'); 161 | console.log(id + ' ' + value) 162 | switch (property) { 163 | case 'email': 164 | Customer.findOne({ 'email': value, 'id': { $ne: id} }) 165 | .select('email') 166 | .exec(function (err, customer) { 167 | console.log(customer) 168 | var status = (customer) ? false : true; 169 | callback(null, {status: status}); 170 | }); 171 | break; 172 | } 173 | 174 | }, 175 | 176 | // get all the states 177 | getStates: function (callback) { 178 | console.log('*** accessDB.getStates'); 179 | State.find({}, {}, { sort: { name: 1 } }, function (err, states) { 180 | callback(null, states); 181 | }); 182 | }, 183 | 184 | // get a state 185 | getState: function (stateId, callback) { 186 | console.log('*** accessDB.getState'); 187 | State.find({ 'id': stateId }, {}, function (err, state) { 188 | callback(null, state); 189 | }); 190 | } 191 | 192 | 193 | } 194 | -------------------------------------------------------------------------------- /lib/configLoader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | if (!process.env.NODE_ENV) process.env.NODE_ENV = 'development'; 4 | 5 | var env = process.env.NODE_ENV; 6 | 7 | console.log('Node environment: ' + env); 8 | console.log('loading config.' + env + '.json'); 9 | 10 | module.exports = require('../config/config.' + env + '.json'); -------------------------------------------------------------------------------- /lib/dbSeeder.js: -------------------------------------------------------------------------------- 1 | // Module dependencies 2 | var mongoose = require('mongoose'), 3 | Schema = mongoose.Schema, 4 | Customer = require('../models/customer'), 5 | State = require('../models/state'), 6 | Settings = require('../models/settings'), 7 | util = require('util'), 8 | dbConfig = require('./configLoader').databaseConfig, 9 | connectionString = 'mongodb://' + dbConfig.host + '/' + dbConfig.database, 10 | connection = null; 11 | 12 | var dbSeeder = function() { 13 | 14 | var init = function() { 15 | // mongoose.connect(connectionString); 16 | // connection = mongoose.connection; 17 | // mongoose.connection.on('open', function () { 18 | 19 | // }); 20 | mongoose.connection.db.listCollections({name: 'customers'}) 21 | .next(function(err, collinfo) { 22 | if (!collinfo) { 23 | console.log('Starting dbSeeder...'); 24 | seed(); 25 | } 26 | }); 27 | }, 28 | 29 | seed = function() { 30 | 31 | console.log('Seeding data....'); 32 | 33 | //Customers 34 | var customerNames = 35 | [ 36 | "Marcus,HighTower,Male,acmecorp.com", 37 | "Jesse,Smith,Female,gmail.com", 38 | "Albert,Einstein,Male,outlook.com", 39 | "Dan,Wahlin,Male,yahoo.com", 40 | "Ward,Bell,Male,gmail.com", 41 | "Brad,Green,Male,gmail.com", 42 | "Igor,Minar,Male,gmail.com", 43 | "Miško,Hevery,Male,gmail.com", 44 | "Michelle,Avery,Female,acmecorp.com", 45 | "Heedy,Wahlin,Female,hotmail.com", 46 | "Thomas,Martin,Male,outlook.com", 47 | "Jean,Martin,Female,outlook.com", 48 | "Robin,Cleark,Female,acmecorp.com", 49 | "Juan,Paulo,Male,yahoo.com", 50 | "Gene,Thomas,Male,gmail.com", 51 | "Pinal,Dave,Male,gmail.com", 52 | "Fred,Roberts,Male,outlook.com", 53 | "Tina,Roberts,Female,outlook.com", 54 | "Cindy,Jamison,Female,gmail.com", 55 | "Robyn,Flores,Female,yahoo.com", 56 | "Jeff,Wahlin,Male,gmail.com", 57 | "Danny,Wahlin,Male,gmail.com", 58 | "Elaine,Jones,Female,yahoo.com", 59 | "John,Papa,Male,gmail.com" 60 | ]; 61 | var addresses = 62 | [ 63 | "1234 Anywhere St.", 64 | "435 Main St.", 65 | "1 Atomic St.", 66 | "85 Cedar Dr.", 67 | "12 Ocean View St.", 68 | "1600 Amphitheatre Parkway", 69 | "1604 Amphitheatre Parkway", 70 | "1607 Amphitheatre Parkway", 71 | "346 Cedar Ave.", 72 | "4576 Main St.", 73 | "964 Point St.", 74 | "98756 Center St.", 75 | "35632 Richmond Circle Apt B", 76 | "2352 Angular Way", 77 | "23566 Directive Pl.", 78 | "235235 Yaz Blvd.", 79 | "7656 Crescent St.", 80 | "76543 Moon Ave.", 81 | "84533 Hardrock St.", 82 | "5687534 Jefferson Way", 83 | "346346 Blue Pl.", 84 | "23423 Adams St.", 85 | "633 Main St.", 86 | "899 Mickey Way" 87 | ]; 88 | 89 | var citiesStates = 90 | [ 91 | "Phoenix,AZ,Arizona", 92 | "Encinitas,CA,California", 93 | "Seattle,WA,Washington", 94 | "Chandler,AZ,Arizona", 95 | "Dallas,TX,Texas", 96 | "Orlando,FL,Florida", 97 | "Carey,NC,North Carolina", 98 | "Anaheim,CA,California", 99 | "Dallas,TX,Texas", 100 | "New York,NY,New York", 101 | "White Plains,NY,New York", 102 | "Las Vegas,NV,Nevada", 103 | "Los Angeles,CA,California", 104 | "Portland,OR,Oregon", 105 | "Seattle,WA,Washington", 106 | "Houston,TX,Texas", 107 | "Chicago,IL,Illinois", 108 | "Atlanta,GA,Georgia", 109 | "Chandler,AZ,Arizona", 110 | "Buffalo,NY,New York", 111 | "Albuquerque,AZ,Arizona", 112 | "Boise,ID,Idaho", 113 | "Salt Lake City,UT,Utah", 114 | "Orlando,FL,Florida" 115 | ]; 116 | 117 | var citiesIds = [5, 9, 44, 5, 36, 17, 16, 9, 36, 14, 14, 6, 9, 24, 44, 36, 25, 19, 5, 14, 5, 23, 38, 17]; 118 | 119 | 120 | var zip = 85229; 121 | 122 | var orders = 123 | [ 124 | { "product": "Basket", "price": 29.99, "quantity": 1 }, 125 | { "product": "Yarn", "price": 9.99, "quantity": 1 }, 126 | { "product": "Needes", "price": 5.99, "quantity": 1 }, 127 | { "product": "Speakers", "price": 499.99, "quantity": 1 }, 128 | { "product": "iPod", "price": 399.99, "quantity": 1 }, 129 | { "product": "Table", "price": 329.99, "quantity": 1 }, 130 | { "product": "Chair", "price": 129.99, "quantity": 4 }, 131 | { "product": "Lamp", "price": 89.99, "quantity": 5 }, 132 | { "product": "Call of Duty", "price": 59.99, "quantity": 1 }, 133 | { "product": "Controller", "price": 49.99, "quantity": 1 }, 134 | { "product": "Gears of War", "price": 49.99, "quantity": 1 }, 135 | { "product": "Lego City", "price": 49.99, "quantity": 1 }, 136 | { "product": "Baseball", "price": 9.99, "quantity": 5 }, 137 | { "product": "Bat", "price": 19.99, "quantity": 1 } 138 | ]; 139 | 140 | Customer.remove({}); 141 | 142 | var l = customerNames.length, 143 | i, 144 | j, 145 | firstOrder, 146 | lastOrder, 147 | tempOrder, 148 | n = orders.length; 149 | 150 | for (i = 0; i < l; i++) { 151 | var nameGenderHost = customerNames[i].split(','); 152 | var cityState = citiesStates[i].split(','); 153 | var s = { 'id': citiesIds[i], 'abbreviation': cityState[1], 'name': cityState[2] }; 154 | var customer = new Customer({ 155 | 'firstName': nameGenderHost[0] 156 | , 'lastName': nameGenderHost[1] 157 | , 'email': nameGenderHost[0] + '.' + nameGenderHost[1] + '@' + nameGenderHost[3] 158 | , 'address': addresses[i] 159 | , 'city': cityState[0] 160 | , 'state': s 161 | , 'stateId': citiesIds[i] 162 | , 'zip': zip + i 163 | , 'gender': nameGenderHost[2] 164 | , 'id': i + 1 165 | , 'orderCount': 0 166 | }); 167 | firstOrder = Math.floor(Math.random() * orders.length); 168 | lastOrder = Math.floor(Math.random() * orders.length); 169 | if (firstOrder > lastOrder) { 170 | tempOrder = firstOrder; 171 | firstOrder = lastOrder; 172 | lastOrder = tempOrder; 173 | } 174 | 175 | customer.orders = []; 176 | //console.log('firstOrder: ' + firstOrder + ", lastOrder: " + lastOrder); 177 | for (j = firstOrder; j <= lastOrder && j < n; j++) { 178 | var today = new Date(); 179 | var tomorrow = new Date(); 180 | tomorrow.setDate(today.getDate() + (Math.random() * 100)); 181 | 182 | var o = { 183 | "product": orders[j].product, 184 | "price": orders[j].price, 185 | "quantity": orders[j].quantity, 186 | "date": tomorrow 187 | }; 188 | customer.orders.push(o); 189 | } 190 | customer.orderCount = customer.orders.length; 191 | 192 | customer.save(function (err, cust) { 193 | if (err) { 194 | console.log(err); 195 | } else { 196 | console.log('inserted customer: ' + cust.firstName + ' ' + cust.lastName); 197 | } 198 | }); 199 | } 200 | 201 | 202 | //Settings 203 | Settings.remove({}); 204 | var settings = new Settings ({ 'nextSeqNumber': customerNames.length, 'collectionName': "customers" }); 205 | settings.save(); 206 | 207 | //States 208 | var states = [ 209 | { "name": "Alabama", "abbreviation": "AL" }, 210 | { "name": "Montana", "abbreviation": "MT" }, 211 | { "name": "Alaska", "abbreviation": "AK" }, 212 | { "name": "Nebraska", "abbreviation": "NE" }, 213 | { "name": "Arizona", "abbreviation": "AZ" }, 214 | { "name": "Nevada", "abbreviation": "NV" }, 215 | { "name": "Arkansas", "abbreviation": "AR" }, 216 | { "name": "New Hampshire", "abbreviation": "NH" }, 217 | { "name": "California", "abbreviation": "CA" }, 218 | { "name": "New Jersey", "abbreviation": "NJ" }, 219 | { "name": "Colorado", "abbreviation": "CO" }, 220 | { "name": "New Mexico", "abbreviation": "NM" }, 221 | { "name": "Connecticut", "abbreviation": "CT" }, 222 | { "name": "New York", "abbreviation": "NY" }, 223 | { "name": "Delaware", "abbreviation": "DE" }, 224 | { "name": "North Carolina", "abbreviation": "NC" }, 225 | { "name": "Florida", "abbreviation": "FL" }, 226 | { "name": "North Dakota", "abbreviation": "ND" }, 227 | { "name": "Georgia", "abbreviation": "GA" }, 228 | { "name": "Ohio", "abbreviation": "OH" }, 229 | { "name": "Hawaii", "abbreviation": "HI" }, 230 | { "name": "Oklahoma", "abbreviation": "OK" }, 231 | { "name": "Idaho", "abbreviation": "ID" }, 232 | { "name": "Oregon", "abbreviation": "OR" }, 233 | { "name": "Illinois", "abbreviation": "IL" }, 234 | { "name": "Pennsylvania", "abbreviation": "PA" }, 235 | { "name": "Indiana", "abbreviation": "IN" }, 236 | { "name": "Rhode Island", "abbreviation": "RI" }, 237 | { "name": "Iowa", "abbreviation": "IA" }, 238 | { "name": "South Carolina", "abbreviation": "SC" }, 239 | { "name": "Kansas", "abbreviation": "KS" }, 240 | { "name": "South Dakota", "abbreviation": "SD" }, 241 | { "name": "Kentucky", "abbreviation": "KY" }, 242 | { "name": "Tennessee", "abbreviation": "TN" }, 243 | { "name": "Louisiana", "abbreviation": "LA" }, 244 | { "name": "Texas", "abbreviation": "TX" }, 245 | { "name": "Maine", "abbreviation": "ME" }, 246 | { "name": "Utah", "abbreviation": "UT" }, 247 | { "name": "Maryland", "abbreviation": "MD" }, 248 | { "name": "Vermont", "abbreviation": "VT" }, 249 | { "name": "Massachusetts", "abbreviation": "MA" }, 250 | { "name": "Virginia", "abbreviation": "VA" }, 251 | { "name": "Michigan", "abbreviation": "MI" }, 252 | { "name": "Washington", "abbreviation": "WA" }, 253 | { "name": "Minnesota", "abbreviation": "MN" }, 254 | { "name": "West Virginia", "abbreviation": "WV" }, 255 | { "name": "Mississippi", "abbreviation": "MS" }, 256 | { "name": "Wisconsin", "abbreviation": "WI" }, 257 | { "name": "Missouri", "abbreviation": "MO" }, 258 | { "name": "Wyoming", "abbreviation": "WY" } 259 | ]; 260 | 261 | var l = states.length, 262 | i; 263 | 264 | State.remove({}); 265 | 266 | for (i = 0; i < l; i++) { 267 | var state = new State ({ 'id': i + 1, 'name': states[i].name, 'abbreviation': states[i].abbreviation }); 268 | state.save(); 269 | } 270 | 271 | // if (process.platform === "win32") { 272 | // require("readline").createInterface({ 273 | // input: process.stdin, 274 | // output: process.stdout 275 | // }).on("SIGINT", function () { 276 | // console.log('SIGINT: Closing MongoDB connection'); 277 | // connection.close(); 278 | // }); 279 | // } 280 | 281 | // process.on('SIGINT', function() { 282 | // console.log('SIGINT: Closing MongoDB connection'); 283 | // connection.close(); 284 | // }); 285 | }; 286 | 287 | return { 288 | init: init, 289 | seed: seed 290 | } 291 | }(); 292 | 293 | module.exports = dbSeeder; 294 | 295 | 296 | 297 | 298 | -------------------------------------------------------------------------------- /models/customer.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'), 2 | Schema = mongoose.Schema, 3 | ObjectId = Schema.ObjectId, 4 | Settings = require('./settings'); 5 | 6 | 7 | var OrderSchema = new Schema({ 8 | product : { 9 | type : String, required: true, trim: true 10 | }, 11 | price : { 12 | type : Number, 13 | }, 14 | quantity : { 15 | type : Number, 16 | } 17 | }); 18 | 19 | var CustomerSchema = new Schema({ 20 | firstName : { 21 | type : String, required: true, trim: true 22 | }, 23 | lastName : { 24 | type : String, required: true, trim: true 25 | }, 26 | email : { 27 | type : String, required: true, trim: true 28 | }, 29 | address : { 30 | type : String, required: true, trim: true 31 | }, 32 | city : { 33 | type : String, required: true, trim: true 34 | }, 35 | stateId : { 36 | type : Number, required: true 37 | }, 38 | state : { 39 | id : { 40 | type : Number 41 | }, 42 | abbreviation : { 43 | type : String, required: true, trim: true 44 | }, 45 | name : { 46 | type : String, required: true, trim: true 47 | } 48 | }, 49 | zip : { 50 | type : Number, required: true 51 | }, 52 | gender : { 53 | type : String, 54 | }, 55 | id : { 56 | type : Number, required: true, unique: true 57 | }, 58 | orderCount : { 59 | type : Number, 60 | }, 61 | orders: [OrderSchema], 62 | }); 63 | 64 | CustomerSchema.index({ id: 1, type: 1 }); // schema level 65 | 66 | 67 | CustomerSchema.pre('save', function(next) { 68 | var doc = this; 69 | // Calculate the next id on new Customers only. 70 | if (this.isNew) { 71 | Settings.findOneAndUpdate( {"collectionName": "customers"}, { $inc: { nextSeqNumber: 1 } }, function (err, settings) { 72 | if (err) next(err); 73 | doc.id = settings.nextSeqNumber - 1; // substract 1 because I need the 'current' sequence number, not the next 74 | next(); 75 | }); 76 | } else { 77 | next(); 78 | } 79 | }); 80 | 81 | exports.CustomerSchema = CustomerSchema; 82 | module.exports = mongoose.model('Customer', CustomerSchema, 'customers'); 83 | -------------------------------------------------------------------------------- /models/settings.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose') 2 | , Schema = mongoose.Schema 3 | , ObjectId = Schema.ObjectId; 4 | 5 | var SettingsSchema = new Schema({ 6 | collectionName : { 7 | type : String, required: true, trim: true, default: 'customers' 8 | }, 9 | nextSeqNumber: { 10 | type: Number, default: 1 11 | } 12 | }); 13 | 14 | exports.SettingsSchema = SettingsSchema; 15 | module.exports = mongoose.model('Settings', SettingsSchema); -------------------------------------------------------------------------------- /models/state.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose') 2 | , Schema = mongoose.Schema 3 | , ObjectId = Schema.ObjectId; 4 | 5 | var StateSchema = new Schema({ 6 | id : { 7 | type : Number, required: true 8 | }, 9 | abbreviation : { 10 | type : String, required: true, trim: true 11 | }, 12 | name : { 13 | type : String, required: true, trim: true 14 | } 15 | }); 16 | 17 | exports.StateSchema = StateSchema; 18 | module.exports = mongoose.model('State', StateSchema); 19 | 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-node-restfulservice", 3 | "author": "Dan Wahlin", 4 | "version": "1.0.0", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/danwahlin/Angular-RestfulService" 8 | }, 9 | "scripts": { 10 | "tsc": "tsc", 11 | "tsc:w": "tsc -w", 12 | "server": "node server.js", 13 | "start": "tsc && concurrently \"tsc -w\" \"nodemon server.js\"" 14 | }, 15 | "license": "ISC", 16 | "dependencies": { 17 | "@angular/common": "4.3.6", 18 | "@angular/compiler": "4.3.6", 19 | "@angular/core": "11.0.5", 20 | "@angular/forms": "4.3.6", 21 | "@angular/http": "4.3.6", 22 | "@angular/platform-browser": "4.3.6", 23 | "@angular/platform-browser-dynamic": "4.3.6", 24 | "@angular/router": "4.3.6", 25 | "@angular/upgrade": "4.3.6", 26 | "@angular/compiler-cli": "4.3.6", 27 | "@angular/platform-server": "4.3.6", 28 | "@angular/tsc-wrapped": "4.3.6", 29 | "@angular/animations": "4.3.6", 30 | "systemjs": "0.20.18", 31 | "core-js": "2.5.0", 32 | "rxjs": "5.4.3", 33 | "zone.js": "0.8.17", 34 | "express": "^4.17.3", 35 | "pug": "^3.0.1", 36 | "mongoose": "^5.7.5", 37 | "cookie-parser": "^1.4.3", 38 | "body-parser": "^1.17.2", 39 | "express-session": "^1.15.5", 40 | "errorhandler": "^1.5.0", 41 | "csurf": "^1.9.0" 42 | }, 43 | "devDependencies": { 44 | "concurrently": "3.5.0", 45 | "lite-server": "2.3.0", 46 | "typescript": "2.4.2", 47 | "@types/node": "8.0.25" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /routes/api.js: -------------------------------------------------------------------------------- 1 | var db = require('../lib/accessDB') 2 | , util = require('util'); 3 | 4 | // GET 5 | 6 | exports.customer = function (req, res) { 7 | console.log('*** customer'); 8 | 9 | db.getCustomer(req.params.id, function (err, customer) { 10 | if (err) { 11 | console.log('*** customer err'); 12 | res.json({ 13 | customer: customer 14 | }); 15 | } else { 16 | console.log('*** customer ok'); 17 | res.json(customer); 18 | } 19 | }); 20 | }; 21 | 22 | exports.addCustomer = function (req, res) { 23 | console.log('*** addCustomer'); 24 | db.getState(req.body.stateId, function (err, state) { 25 | if (err) { 26 | console.log('*** getState err'); 27 | res.json({ 'status': false }); 28 | } else { 29 | db.insertCustomer(req.body, state, function (err) { 30 | if (err) { 31 | console.log('*** addCustomer err'); 32 | res.json(false); 33 | } else { 34 | console.log('*** addCustomer ok'); 35 | res.json(req.body); 36 | } 37 | }); 38 | } 39 | }); 40 | }; 41 | 42 | exports.editCustomer = function (req, res) { 43 | console.log('*** editCustomer'); 44 | console.log('*** req.body'); 45 | console.log(req.body); 46 | 47 | if (!req.body || !req.body.stateId) { 48 | throw new Error('Customer and associated stateId required'); 49 | } 50 | 51 | db.getState(req.body.stateId, function (err, state) { 52 | if (err) { 53 | console.log('*** getState err'); 54 | res.json({ 'status': false }); 55 | } else { 56 | db.editCustomer(req.params.id, req.body, state, function (err) { 57 | if (err) { 58 | console.log('*** editCustomer err' + util.inspect(err)); 59 | res.json({ 'status': false }); 60 | } else { 61 | console.log('*** editCustomer ok'); 62 | res.json({ 'status': true }); 63 | } 64 | }); 65 | } 66 | }); 67 | }; 68 | 69 | exports.deleteCustomer = function (req, res) { 70 | console.log('*** deleteCustomer'); 71 | 72 | db.deleteCustomer(req.params.id, function (err) { 73 | if (err) { 74 | console.log('*** deleteCustomer err'); 75 | res.json({ 'status': false }); 76 | } else { 77 | console.log('*** deleteCustomer ok'); 78 | res.json({ 'status': true }); 79 | } 80 | }); 81 | }; 82 | 83 | // GET 84 | exports.states = function (req, res) { 85 | console.log('*** states'); 86 | db.getStates(function (err, states) { 87 | if (err) { 88 | console.log('*** states err'); 89 | res.json({ 90 | states: states 91 | }); 92 | } else { 93 | console.log('*** states ok'); 94 | res.json(states); 95 | } 96 | }); 97 | }; 98 | 99 | exports.customers = function (req, res) { 100 | console.log('*** customers'); 101 | var topVal = req.query.$top, 102 | skipVal = req.query.$skip, 103 | top = (isNaN(topVal)) ? 10 : parseInt(req.query.$top, 10), 104 | skip = (isNaN(skipVal)) ? 0 : parseInt(req.query.$skip, 10); 105 | 106 | db.getCustomers(skip, top, function (err, data) { 107 | res.setHeader('X-InlineCount', data.count); 108 | if (err) { 109 | console.log('*** customers err'); 110 | res.json({ 111 | customers: data.customers 112 | }); 113 | } else { 114 | console.log('*** customers ok'); 115 | res.json(data.customers); 116 | } 117 | }); 118 | }; 119 | 120 | exports.customersSummary = function (req, res) { 121 | console.log('*** customersSummary'); 122 | var topVal = req.query.$top, 123 | skipVal = req.query.$skip, 124 | top = (isNaN(topVal)) ? 10 : parseInt(req.query.$top, 10), 125 | skip = (isNaN(skipVal)) ? 0 : parseInt(req.query.$skip, 10); 126 | 127 | db.getCustomersSummary(skip, top, function (err, summary) { 128 | res.setHeader('X-InlineCount', summary.count); 129 | if (err) { 130 | console.log('*** customersSummary err'); 131 | res.json({ 132 | customersSummary: summary.customersSummary 133 | }); 134 | } else { 135 | console.log('*** customersSummary ok'); 136 | res.json(summary.customersSummary); 137 | } 138 | }); 139 | }; 140 | 141 | exports.checkUnique = function (req, res) { 142 | console.log('*** checkUnique'); 143 | 144 | var id = req.params.id, 145 | value = req.query.value, 146 | property = req.query.property; 147 | 148 | db.checkUnique(id, property, value, function (err, opStatus) { 149 | if (err) { 150 | console.log('*** checkUnique err'); 151 | res.json({ 152 | 'status': false 153 | }); 154 | } else { 155 | console.log('*** checkUnique ok'); 156 | res.json(opStatus); 157 | } 158 | }); 159 | }; 160 | 161 | exports.login = function (req, res) { 162 | console.log('*** login'); 163 | var userLogin = req.body.userLogin; 164 | var userName = userLogin.userName; 165 | var password = userLogin.password; 166 | 167 | //Simulate login 168 | res.json({ status: true }); 169 | }; 170 | 171 | exports.logout = function (req, res) { 172 | console.log('*** logout'); 173 | 174 | //Simulate logout 175 | res.json({ status: true }); 176 | }; 177 | 178 | 179 | 180 | 181 | 182 | -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * GET home page. 3 | */ 4 | 5 | exports.index = function(req, res){ 6 | res.render('index'); 7 | }; 8 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var express = require('express'), 2 | cookieParser = require('cookie-parser'), 3 | bodyParser = require('body-parser'), 4 | session = require('express-session'), 5 | errorhandler = require('errorhandler'), 6 | csrf = require('csurf'), 7 | routes = require('./routes'), 8 | api = require('./routes/api'), 9 | DB = require('./lib/accessDB'), 10 | seeder = require('./lib/dbSeeder'), 11 | app = express(); 12 | 13 | app.set('views', __dirname + '/views'); 14 | app.set('view engine', 'pug'); 15 | app.use(session({ 16 | secret: 'customermanagerdemo', 17 | saveUninitialized: true, 18 | resave: true })); 19 | app.use(cookieParser()); 20 | app.use(bodyParser.urlencoded({ extended: true })); 21 | app.use(bodyParser.json()); 22 | app.use(express.static(__dirname + '/')); 23 | app.use(errorhandler()); 24 | // app.use(csrf()); 25 | 26 | // app.use(function (req, res, next) { 27 | // var csrf = req.csrfToken(); 28 | // res.cookie('XSRF-TOKEN', csrf); 29 | // res.locals._csrf = csrf; 30 | // next(); 31 | // }) 32 | 33 | process.on('uncaughtException', function (err) { 34 | if (err) console.log(err, err.stack); 35 | }); 36 | 37 | 38 | DB.startup(function() { 39 | //if (process.env.NODE_ENV === 'development') { 40 | seeder.init(); 41 | //} 42 | }); 43 | 44 | // Routes 45 | app.get('/', routes.index); 46 | 47 | // JSON API 48 | var baseUrl = '/api/dataservice/'; 49 | 50 | app.get(baseUrl + 'customers', api.customers); 51 | app.get(baseUrl + 'customer/:id', api.customer); 52 | app.get(baseUrl + 'customersSummary', api.customersSummary); 53 | app.get(baseUrl + 'customerById/:id', api.customer); 54 | 55 | app.post(baseUrl + 'postCustomer', api.addCustomer); 56 | app.put(baseUrl + 'putCustomer/:id', api.editCustomer); 57 | app.delete(baseUrl + 'deleteCustomer/:id', api.deleteCustomer); 58 | 59 | app.get(baseUrl + 'states', api.states); 60 | 61 | app.get(baseUrl + 'checkUnique/:id', api.checkUnique); 62 | 63 | app.post(baseUrl + 'login', api.login); 64 | app.post(baseUrl + 'logout', api.logout); 65 | 66 | 67 | // redirect all others to the index (HTML5 history) 68 | app.get('*', routes.index); 69 | 70 | // Start server 71 | 72 | app.listen(3000, function () { 73 | console.log("CustMgr Express server listening on port %d in %s mode", this.address().port, app.settings.env); 74 | }); 75 | 76 | if (process.platform === "win32") { 77 | require("readline").createInterface({ 78 | input: process.stdin, 79 | output: process.stdout 80 | }).on("SIGINT", function () { 81 | console.log('SIGINT: Closing MongoDB connection'); 82 | DB.close(); 83 | }); 84 | } 85 | 86 | process.on('SIGINT', function() { 87 | console.log('SIGINT: Closing MongoDB connection'); 88 | DB.close(); 89 | }); 90 | 91 | -------------------------------------------------------------------------------- /styles/styles.css: -------------------------------------------------------------------------------- 1 | html { 2 | overflow-y: scroll; 3 | overflow-x: hidden; 4 | } 5 | 6 | body { 7 | font-family: 'Open Sans' 8 | } 9 | 10 | main { 11 | position: relative; 12 | padding-top: 60px; 13 | } 14 | 15 | /* Ensure display:flex and others don't override a [hidden] */ 16 | [hidden] { display: none !important; } 17 | 18 | .white { 19 | color: white; 20 | } 21 | 22 | .white:hover{ 23 | color: white; 24 | } 25 | 26 | th { 27 | cursor: pointer; 28 | } 29 | 30 | .navbar-header { 31 | width: 300px; 32 | } 33 | 34 | .nav.navbar-padding { 35 | margin-left:25px; 36 | margin-top: 10px; 37 | } 38 | 39 | .app-title { 40 | line-height:50px; 41 | font-size:20px; 42 | color: white; 43 | } 44 | 45 | .navbar .nav > li.toolbar-item > a { 46 | color: #9E9E9E; 47 | font-weight:bold; 48 | -webkit-text-shadow: none; 49 | text-shadow: none; 50 | } 51 | 52 | .navbar .nav > .toolbar-item > a.active { 53 | color: #000; 54 | } 55 | 56 | .toolbar-item a { 57 | cursor: pointer; 58 | } 59 | 60 | .view { 61 | 62 | } 63 | 64 | .indent { 65 | margin-left:5px; 66 | } 67 | 68 | .card-container { 69 | width:85%; 70 | } 71 | 72 | .card { 73 | background-color:#fff; 74 | border: 1px solid #d4d4d4; 75 | height:100px; 76 | margin-bottom: 20px; 77 | position: relative; 78 | } 79 | 80 | .card-header { 81 | background-color:#027FF4; 82 | font-size:14pt; 83 | color:white; 84 | padding:5px; 85 | width:100%; 86 | } 87 | 88 | .card-close { 89 | color: white; 90 | font-weight:bold; 91 | margin-right:5px; 92 | } 93 | 94 | .card-body { 95 | padding-left: 5px; 96 | } 97 | 98 | .card-body-left { 99 | margin-top: -5px; 100 | } 101 | 102 | .card-body-right { 103 | margin-left: 20px; 104 | margin-top: 2px; 105 | } 106 | 107 | .card-body-content { 108 | width: 100px; 109 | } 110 | 111 | .card-image { 112 | height:50px;width:50px;margin-top:10px; 113 | } 114 | 115 | .grid-container div { 116 | padding-left: 0px; 117 | } 118 | 119 | .grid-container td { 120 | vertical-align: middle; 121 | } 122 | 123 | .navbar-brand { 124 | float:none; 125 | } 126 | 127 | a.navbar-brand { 128 | color: #fff; 129 | } 130 | 131 | .navbar-inner { 132 | padding-left: 0px; 133 | -webkit-border-radius: 0px; 134 | border-radius: 0px; 135 | -webkit-box-shadow: none; 136 | -moz-box-shadow: none; 137 | box-shadow: none; 138 | background-color: #027FF4; 139 | background-image: none; 140 | } 141 | 142 | .navbar-inner.toolbar { 143 | background-color: #fafafa; 144 | } 145 | 146 | footer { 147 | margin-top: 10px; 148 | } 149 | 150 | .navbar-inner.footer { 151 | background-color: #fafafa; 152 | -webkit-box-shadow: none; 153 | -moz-box-shadow: none; 154 | box-shadow: none; 155 | height:50px; 156 | } 157 | 158 | .navbar .nav > .active > a, .navbar .nav > .active > a:hover, .navbar .nav > .active > a:focus { 159 | background-color: #efefef; 160 | -webkit-box-shadow: none; 161 | box-shadow: none; 162 | color: #808080; 163 | } 164 | 165 | .navbar .nav li.toolbaritem a:hover, .navbar .nav li a:hover { 166 | color: #E03930; 167 | } 168 | 169 | .navbar .nav > li { 170 | cursor:pointer; 171 | } 172 | 173 | .navbar .nav > li > a { 174 | color: white; 175 | font-weight:bold; 176 | -webkit-text-shadow: none; 177 | text-shadow: none; 178 | height:30px; 179 | padding-top: 6px; 180 | padding-bottom: 0px; 181 | } 182 | 183 | .navbar .nav > li.toolbaritem > a { 184 | color: black; 185 | font-weight:bold; 186 | -webkit-text-shadow: none; 187 | text-shadow: none; 188 | } 189 | 190 | 191 | .navbar-fixed-top .navbar-inner, 192 | .navbar-static-top .navbar-inner { 193 | -webkit-box-shadow: 0 1px 00px rgba(0, 0, 0, 0); 194 | -moz-box-shadow: 0 1px 00px rgba(0, 0, 0, 0); 195 | box-shadow: 0 1px 00px rgba(0, 0, 0, 0); 196 | } 197 | 198 | .nav.navBarPadding { 199 | margin-left:25px; 200 | margin-top: 10px; 201 | } 202 | 203 | .navbarText { 204 | font-weight:bold; 205 | } 206 | 207 | .navbar .brand { 208 | margin-top: 2px; 209 | color: #fff; 210 | -webkit-text-shadow: none; 211 | text-shadow: none; 212 | } 213 | 214 | .navbar-toggle { 215 | border: 1px solid white; 216 | } 217 | 218 | .navbar-toggle .icon-bar { 219 | background-color: white; 220 | } 221 | 222 | footer { 223 | margin-top: 15px; 224 | } 225 | 226 | .navbar-inner.footer { 227 | background-color: #fafafa; 228 | -webkit-box-shadow: none; 229 | -moz-box-shadow: none; 230 | box-shadow: none; 231 | height:50px; 232 | } 233 | 234 | filter-textbox { 235 | margin-top: 5px; 236 | } 237 | 238 | -------------------------------------------------------------------------------- /systemjs-angular-loader.js: -------------------------------------------------------------------------------- 1 | var templateUrlRegex = /templateUrl\s*:(\s*['"`](.*?)['"`]\s*)/gm; 2 | var stylesRegex = /styleUrls *:(\s*\[[^\]]*?\])/g; 3 | var stringRegex = /(['`"])((?:[^\\]\\\1|.)*?)\1/g; 4 | 5 | module.exports.translate = function(load){ 6 | if (load.source.indexOf('moduleId') != -1) return load; 7 | 8 | var url = document.createElement('a'); 9 | url.href = load.address; 10 | 11 | var basePathParts = url.pathname.split('/'); 12 | 13 | basePathParts.pop(); 14 | var basePath = basePathParts.join('/'); 15 | 16 | var baseHref = document.createElement('a'); 17 | baseHref.href = this.baseURL; 18 | baseHref = baseHref.pathname; 19 | 20 | if (!baseHref.startsWith('/base/')) { // it is not karma 21 | basePath = basePath.replace(baseHref, ''); 22 | } 23 | 24 | load.source = load.source 25 | .replace(templateUrlRegex, function(match, quote, url){ 26 | var resolvedUrl = url; 27 | 28 | if (url.startsWith('.')) { 29 | resolvedUrl = basePath + url.substr(1); 30 | } 31 | 32 | return 'templateUrl: "' + resolvedUrl + '"'; 33 | }) 34 | .replace(stylesRegex, function(match, relativeUrls) { 35 | var urls = []; 36 | 37 | while ((match = stringRegex.exec(relativeUrls)) !== null) { 38 | if (match[2].startsWith('.')) { 39 | urls.push('"' + basePath + match[2].substr(1) + '"'); 40 | } else { 41 | urls.push('"' + match[2] + '"'); 42 | } 43 | } 44 | 45 | return "styleUrls: [" + urls.join(', ') + "]"; 46 | }); 47 | 48 | return load; 49 | }; 50 | -------------------------------------------------------------------------------- /systemjs.config.js: -------------------------------------------------------------------------------- 1 | (function(global) { 2 | 3 | // map tells the System loader where to look for things 4 | var map = { 5 | 'app': 'app', // 'dist', 6 | '@angular': 'node_modules/@angular', 7 | 'rxjs': 'node_modules/rxjs' 8 | }; 9 | 10 | // packages tells the System loader how to load when no filename and/or no extension 11 | var packages = { 12 | 'app': { 13 | main: 'main.js', 14 | defaultExtension: 'js', 15 | meta: { 16 | './*.js': { 17 | loader: 'systemjs-angular-loader.js' 18 | } 19 | } 20 | }, 21 | 'rxjs': { defaultExtension: 'js' } 22 | }; 23 | 24 | var ngPackageNames = [ 25 | 'common', 26 | 'compiler', 27 | 'core', 28 | 'forms', 29 | 'http', 30 | 'platform-browser', 31 | 'platform-browser-dynamic', 32 | 'router', 33 | 'upgrade', 34 | ]; 35 | 36 | // Individual files (~300 requests): 37 | function packIndex(pkgName) { 38 | packages['@angular/'+pkgName] = { main: 'index.js', defaultExtension: 'js' }; 39 | } 40 | 41 | // Bundled (~40 requests): 42 | function packUmd(pkgName) { 43 | packages['@angular/'+pkgName] = { main: '/bundles/' + pkgName + '.umd.js', defaultExtension: 'js' }; 44 | } 45 | 46 | // Most environments should use UMD; some (Karma) need the individual index files 47 | var setPackageConfig = System.packageWithIndex ? packIndex : packUmd; 48 | 49 | // Add package entries for angular packages 50 | ngPackageNames.forEach(setPackageConfig); 51 | 52 | var config = { 53 | map: map, 54 | packages: packages 55 | }; 56 | 57 | System.config(config); 58 | 59 | })(this); -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "sourceMap": true, 7 | "emitDecoratorMetadata": true, 8 | "experimentalDecorators": true, 9 | "lib": [ "es2015", "dom" ], 10 | "removeComments": false, 11 | "noImplicitAny": true, 12 | "suppressImplicitAnyIndexErrors": true 13 | }, 14 | "exclude": [ 15 | "node_modules" 16 | ] 17 | } -------------------------------------------------------------------------------- /views/index.pug: -------------------------------------------------------------------------------- 1 | include ../index.html 2 | --------------------------------------------------------------------------------