├── .gitignore ├── LICENSE ├── README.md ├── full-stack.gif ├── package.json ├── release.md ├── src ├── app.js ├── config │ ├── base-project │ │ ├── .gitignore │ │ ├── .jsbeautifyrc │ │ ├── README.md │ │ ├── app.ts │ │ ├── bin │ │ │ └── www.ts │ │ ├── client │ │ │ ├── app.ts │ │ │ ├── components │ │ │ │ ├── account-details │ │ │ │ │ ├── account-details.component.css │ │ │ │ │ ├── account-details.component.html │ │ │ │ │ └── account-details.component.ts │ │ │ │ ├── accounts │ │ │ │ │ ├── account.model.ts │ │ │ │ │ ├── accounts.component.css │ │ │ │ │ ├── accounts.component.html │ │ │ │ │ ├── accounts.component.ts │ │ │ │ │ └── accounts.service.ts │ │ │ │ ├── app │ │ │ │ │ ├── app.component.css │ │ │ │ │ ├── app.component.html │ │ │ │ │ ├── app.component.ts │ │ │ │ │ ├── app.module.ts │ │ │ │ │ └── app.routing.ts │ │ │ │ └── menu │ │ │ │ │ ├── menu.component.css │ │ │ │ │ ├── menu.component.html │ │ │ │ │ └── menu.component.ts │ │ │ ├── index.html │ │ │ ├── polyfills.ts │ │ │ └── vendor.ts │ │ ├── config │ │ │ ├── build.js │ │ │ ├── settings.json │ │ │ ├── webpack.client.js │ │ │ └── webpack.server.js │ │ ├── package.json │ │ ├── server │ │ │ ├── controllers │ │ │ │ └── accounts.ts │ │ │ ├── models │ │ │ │ ├── accounts-schema.ts │ │ │ │ └── accounts.ts │ │ │ ├── routes │ │ │ │ └── routes.ts │ │ │ └── views │ │ │ │ ├── error.ejs │ │ │ │ ├── index.ejs │ │ │ │ └── partials │ │ │ │ └── json-output.ejs │ │ ├── tsconfig.json │ │ └── typings.json │ └── directories.js └── generators │ └── mean-stack.js └── tests └── app.js /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | build/ 3 | typings/ 4 | node_modules/ 5 | .DS* 6 | ._.* 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 JSecademy 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Scaffold your project in seconds using the MEAN Stack 2 | 3 | ## This project is made possible thanks to all of the supporters that are moving their career forward with the [MEAN Stack 2 [Node.js, Express, MongoDB, Angular 2]](https://list.codewithintent.com/full-stack?list=codewithintent&source=github-email-course&form=28&dest=full-stack-confirm). 4 | 5 | ![Full Stack](https://github.com/jsecademy/full-stack/raw/master/full-stack.gif) 6 | 7 | To all of you, that have no one on your team... 8 | 9 | To all those who have failed and have not given up... 10 | 11 | To all that know that if you are willing to work, you too can achieve... 12 | 13 | I welcome you! 14 | 15 | This tool was designed and created for Entrepreneurs, Innovators, and Consultants. 16 | 17 | This project strongly believes in the Agile Software Development practices. 18 | 19 | * **Individuals and interactions** over processes and tools 20 | * **Working software** over comprehensive documentation 21 | * **Customer collaboration** over contract negotiation 22 | * **Responding to change** over following a plan 23 | 24 | But more than just a practice, this project stands behind even stronger values. 25 | 26 | * This project is safe, meets specifications, passes appropriate tests, and does not diminish the quality of life or diminish privacy. 27 | * The ultimate effect of this project is to do the public good 28 | * This project strives for high quality, acceptable cost and a reasonable schedule, ensuring significant tradeoffs are clear to and accepted by all of the core team members. 29 | 30 | **This is not the *perfect* arrangement of the MEAN Stack. It exists primarily to get you started quickly with learning and prototyping using the MEAN Stack.** 31 | 32 | ### Getting Started 33 | 34 | ``` 35 | sudo npm install full-stack -g 36 | full-stack init myProjectName 37 | ``` 38 | 39 | You can learn more about the tool at [MEANStack.net](https://www.meanstack.net/) 40 | 41 | If you are completely new to the MEAN Stack it might be a bit frustrating trying to understand how all of this pieces are put together. I have created a FREE 20-day email course that walks you through the entire stack in just 1 message per day for 20 days. 42 | 43 | ## [MEAN Stack 2 Email Course](https://list.codewithintent.com/full-stack?list=codewithintent&source=github-email-course&form=28&dest=full-stack-confirm) 44 | -------------------------------------------------------------------------------- /full-stack.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsecademy/full-stack/dee5b3405d42153c5ec96b45132649a70e9a2336/full-stack.gif -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "full-stack", 3 | "version": "1.0.2", 4 | "description": "Built for Entrepreneurs, Innovators, and Consultants. Scaffold a project in seconds using the MEAN Stack 2", 5 | "bin": { 6 | "full-stack": "./src/app.js" 7 | }, 8 | "directories": { 9 | "test": "tests" 10 | }, 11 | "scripts": { 12 | "test": "echo \"Error: no test specified\" && exit 1" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/jsecademy/full-stack.git" 17 | }, 18 | "keywords": [ 19 | "MEAN Stack", 20 | "MEAN Stack 2.0" 21 | ], 22 | "author": "Rick Hernandez", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/jsecademy/full-stack/issues" 26 | }, 27 | "homepage": "https://github.com/jsecademy/full-stack#readme", 28 | "dependencies": { 29 | "chalk": "^1.1.3", 30 | "inquirer": "^1.2.2", 31 | "minimist": "^1.2.0", 32 | "progress": "^1.1.8", 33 | "shelljs": "^0.7.5" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /release.md: -------------------------------------------------------------------------------- 1 | npm version patch 2 | npm publish 3 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | "use strict"; 3 | var shell = require('shelljs'); 4 | var chalk = require('chalk'); 5 | var minimist = require('minimist'); 6 | var mean_stack_1 = require('./generators/mean-stack'); 7 | var log = console.log; 8 | var args = minimist(process.argv.slice(2)); 9 | if (inArgs('help') || (args['_'].length === 0) && Object.keys(args).length === 1) { 10 | displayHelp(); 11 | process.exit(); 12 | } 13 | if (inArgs('version')) { 14 | mean_stack_1.MEANStack.getGlobalPath().then(function (path) { 15 | var version = mean_stack_1.MEANStack.getVersion(path + "/full-stack"); 16 | log(chalk.yellow("" + version)); 17 | }); 18 | } 19 | if (inArgs('serve')) { 20 | mean_stack_1.MEANStack.serveLocalInstance(process.cwd(), function (data) { 21 | // Live feed running from process 22 | process.stdout.write(data); 23 | }).then(function (stdout) { 24 | // All done 25 | process.exit(); 26 | }).catch(function (error) { 27 | log(chalk.red('Not able to find a a valid package.json file.')); 28 | process.exit(); 29 | }); 30 | } 31 | var projectName = args['_'][1]; // Get the Project Name 32 | if (inArgs('init') && projectName) { 33 | mean_stack_1.MEANStack.getGlobalPath().then(function (globalPath) { 34 | mean_stack_1.MEANStack.writableDirectory(process.cwd()).then(function () { 35 | mean_stack_1.MEANStack.initializeCore(globalPath).then(function () { 36 | (function generateApplication() { 37 | var options = { 38 | type: 'input', 39 | name: 'name', 40 | message: 'Name your application:', 41 | default: projectName, 42 | validate: function (name) { 43 | return !!name.trim() || 'Name is required'; 44 | } 45 | }; 46 | clear(); 47 | mean_stack_1.MEANStack.askQuestion(options).then(function (question) { 48 | mean_stack_1.MEANStack.directoryExist(process.cwd() + "/" + question.name).then(function () { 49 | // Folder exists 50 | clear(); 51 | log(chalk.white.bold.bgRed('That folder name is already taken:') + chalk.cyan(" " + process.cwd() + "/" + question.name)); 52 | var options = { 53 | type: 'list', 54 | name: 'action', 55 | message: 'What would you like to do?:', 56 | choices: ['Rename the application', ("Delete this directory: " + process.cwd() + "/" + question.name)] 57 | }; 58 | mean_stack_1.MEANStack.askQuestion(options).then(function (answer) { 59 | if (answer.action === 'Rename the application') { 60 | return generateApplication(); 61 | } 62 | if (answer.action === "Delete this directory: " + process.cwd() + "/" + question.name) { 63 | var options_1 = { 64 | type: 'confirm', 65 | name: 'remove', 66 | message: "Are you sure you want to delete this directory?", 67 | default: false 68 | }; 69 | mean_stack_1.MEANStack.askQuestion(options_1).then(function (answer) { 70 | if (answer.remove) { 71 | // Delete Directory 72 | shell.rm('-rf', process.cwd() + "/" + question.name); 73 | log(chalk.cyan('Deleted Directory: ') + chalk.red(process.cwd() + "/" + question.name)); 74 | // Directory does not exists generate app 75 | mean_stack_1.MEANStack.generateApplication(process.cwd(), question.name).then(function () { 76 | displayPostInstallMessage(question.name); 77 | }); 78 | } 79 | else { 80 | // Rename Directory 81 | return generateApplication(); 82 | } 83 | }); 84 | } 85 | }); 86 | }).catch(function (error) { 87 | // Directory does not exists generate app 88 | mean_stack_1.MEANStack.generateApplication(process.cwd(), question.name).then(function () { 89 | displayPostInstallMessage(question.name); 90 | }); 91 | }); 92 | }); 93 | }()); 94 | }).catch(function (error) { 95 | log(chalk.white.bold.bgRed('Core is MISSING!!!')); 96 | process.exit(); 97 | }); 98 | }).catch(function (error) { 99 | // Directory is not writable 100 | log(chalk.white.bgRed.bold('Parent directory is not writable:') + chalk.cyan(" " + process.cwd())); 101 | log(chalk.green.bold('Change Permissions of directory: ') + chalk.cyan('sudo chmod 0750 .')); 102 | process.exit(); 103 | }); 104 | }).catch(function (err) { 105 | log(chalk.white.bgRed('Global path for NPM can not be found!')); 106 | log(chalk.white.bgRed(err)); 107 | process.exit(); 108 | }); 109 | } 110 | if (inArgs('init') && !projectName) { 111 | displayHelp(); 112 | log(chalk.white.bgRed('init command requires a project name')); 113 | process.exit(); 114 | } 115 | function inArgs(name) { 116 | if (args['_'] && args['_'].length > 0) { 117 | // Example full-stack init 118 | return args['_'].find(function (item) { return item === name.toString(); }) !== undefined; 119 | } 120 | if (args[name.toString()] !== undefined) { 121 | // Example full-stack --init 122 | return true; 123 | } 124 | return false; 125 | } 126 | function displayHelp() { 127 | // Output help and exit 128 | log(chalk.cyan('\n Usage: ') + chalk.yellow('full-stack [options] [value]')); 129 | log(chalk.cyan('\n Options:')); 130 | log(chalk.yellow('\n init, --init') + chalk.cyan(' Creates a base project using the MEAN Stack')); 131 | log(chalk.yellow('\n serve, --serve') + chalk.cyan(' Serves the local instance the application.')); 132 | log(chalk.yellow('\n version, --version') + chalk.cyan(' Displays version')); 133 | log(chalk.yellow('\n help, --help') + chalk.cyan(' Displays the description for each of the flags available')); 134 | log(chalk.cyan('\n Read the README.md for more detailed options')); 135 | log(chalk.cyan('\n Example:')); 136 | log(chalk.cyan('\n $ ') + chalk.yellow('full-stack init myProjectName\n')); 137 | } 138 | function displayPostInstallMessage(name) { 139 | clear(); 140 | log(chalk.cyan("\n Application ") + chalk.cyan.bold("" + name) + chalk.cyan(" created")); 141 | log(chalk.cyan('\n Download dependencies:')); 142 | log(chalk.yellow(" $ cd " + name + " && npm install")); 143 | log(chalk.cyan('\n Start the application')); 144 | log(chalk.yellow(" $ full-stack serve\n")); 145 | } 146 | function requirements() { 147 | // Required Programs for the MEAN Stack 148 | if (!shell.which('git')) { 149 | log(chalk.white.bgRed.bold('Git is required! Install git: https://git-scm.com/')); 150 | process.exit(); 151 | } 152 | if (!shell.which('mongo')) { 153 | log(chalk.white.bgRed.bold('MongoDB is required! Install MongoDB: https://docs.mongodb.com/master/installation/')); 154 | process.exit(); 155 | } 156 | } 157 | function clear() { 158 | // process.stdout.write('\x1Bc'); 159 | } 160 | -------------------------------------------------------------------------------- /src/config/base-project/.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | build/ 3 | typings/ 4 | node_modules/ 5 | .DS* 6 | ._.* 7 | npm-debug.log -------------------------------------------------------------------------------- /src/config/base-project/.jsbeautifyrc: -------------------------------------------------------------------------------- 1 | { 2 | "brace_style": "collapse-preserve-inline" 3 | } -------------------------------------------------------------------------------- /src/config/base-project/README.md: -------------------------------------------------------------------------------- 1 | ### [--ApplicationName--] -------------------------------------------------------------------------------- /src/config/base-project/app.ts: -------------------------------------------------------------------------------- 1 | import * as express from 'express'; 2 | import * as logger from 'morgan'; 3 | import * as bodyParser from 'body-parser'; 4 | import * as root from 'app-root-path'; 5 | import * as cookieParser from 'cookie-parser'; 6 | import * as session from 'express-session'; 7 | import * as routes from './server/routes/routes'; 8 | import * as mongoose from 'mongoose'; 9 | 10 | const settings = require('./config/settings.json'); 11 | const db = settings.database; 12 | 13 | mongoose.connect(`mongodb://localhost:${db.port}/${db.name}`); 14 | 15 | const app = express(); 16 | 17 | // setup sessions 18 | app.use(session({ 19 | secret: "hello-secret-hash", 20 | resave: false, 21 | saveUninitialized: false 22 | })); 23 | 24 | // view engine setup 25 | app.set('views', `${root.path}/server/views/`); 26 | app.set('view engine', 'ejs'); 27 | 28 | app.use(logger('dev')); 29 | app.use(bodyParser.json()); 30 | app.use(bodyParser.urlencoded({ 31 | extended: false 32 | })); 33 | 34 | app.use(cookieParser()); 35 | app.use('/api', routes); 36 | 37 | // catch 404 and forward to error handler 38 | app.use((req: express.Request, res: express.Response, next: express.NextFunction) => { 39 | let err: any = new Error('Not Found'); 40 | err.status = 404; 41 | next(err); 42 | }); 43 | 44 | // error handlers 45 | // development error handler 46 | // will print stacktrace 47 | if (app.get('env') === 'development') { 48 | app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => { 49 | res.status(err.status || 500); 50 | res.render('error', { 51 | message: err.message, 52 | error: err 53 | }); 54 | }); 55 | } 56 | 57 | app.use(function(err: any, req: express.Request, res: express.Response, next: Function) { 58 | res.status(err.status || 500); 59 | res.render('error', { 60 | message: err.message, 61 | error: {} 62 | }); 63 | }); 64 | 65 | export = app; -------------------------------------------------------------------------------- /src/config/base-project/bin/www.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | import * as app from '../app'; 5 | import * as http from 'http'; 6 | import * as debug from 'debug'; 7 | import * as mongoose from 'mongoose'; 8 | 9 | // binding to console 10 | let log = debug('modern-express:server'); 11 | log.log = console.log.bind(console); 12 | 13 | /** 14 | * Get port from environment and store in Express. 15 | */ 16 | 17 | let PORT = process.env.PORT || '4000'; 18 | 19 | function getPort(val) { 20 | /** 21 | * Normalize a port into a number, string, or false. 22 | */ 23 | const port = parseInt(val, 10); 24 | if (isNaN(port)) { 25 | // named pipe 26 | return val; 27 | } 28 | if (port >= 0) { 29 | // port number 30 | return port; 31 | } 32 | return false; 33 | } 34 | 35 | app.set('port', PORT); 36 | 37 | /** 38 | * Create HTTP server. 39 | */ 40 | const server = http.createServer(app); 41 | 42 | /** 43 | * Listen on provided port, on all network interfaces. 44 | */ 45 | server.listen(PORT); 46 | 47 | server.on('error', (error) => { 48 | /** 49 | * Event listener for HTTP server "error" event. 50 | */ 51 | if (error.syscall !== 'listen') { 52 | throw error; 53 | } 54 | const bind = typeof PORT === 'string' ? `Port:${PORT} is being used ` : `Port ${PORT}`; 55 | // handle specific listen errors with friendly messages 56 | switch (error.code) { 57 | case 'EACCES': 58 | console.error(bind + ' requires elevated privileges'); 59 | process.exit(1); 60 | break; 61 | case 'EADDRINUSE': 62 | console.error(bind + ' is already in use'); 63 | process.exit(1); 64 | break; 65 | default: 66 | throw error; 67 | } 68 | }); 69 | 70 | server.on('listening', () => { 71 | /** 72 | * Event listener for HTTP server "listening" event. 73 | */ 74 | const addr = server.address(); 75 | const bind = (typeof addr === 'string' ? `port ${addr}` : `port ${addr.port}`); 76 | log(`listening on ${bind}`); 77 | }); -------------------------------------------------------------------------------- /src/config/base-project/client/app.ts: -------------------------------------------------------------------------------- 1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 2 | import { enableProdMode } from '@angular/core'; 3 | import { AppModule } from './components/app/app.module'; 4 | if (process.env.ENV === 'production') { 5 | enableProdMode(); 6 | } 7 | platformBrowserDynamic().bootstrapModule(AppModule); 8 | -------------------------------------------------------------------------------- /src/config/base-project/client/components/account-details/account-details.component.css: -------------------------------------------------------------------------------- 1 | .account { 2 | width: 550px; 3 | margin: 0 auto; 4 | padding: 15px; 5 | } 6 | 7 | h5 { 8 | margin: 0px; 9 | } 10 | 11 | .account p { 12 | text-align: left; 13 | font-size: 30px; 14 | } 15 | 16 | .form-item { 17 | font-size: 20px; 18 | text-align: left; 19 | margin-bottom: 15px; 20 | } 21 | 22 | form { 23 | margin-top: 30px; 24 | } 25 | 26 | input { 27 | width: 100%; 28 | height: 40px; 29 | font-size: 2em; 30 | } 31 | 32 | .item { 33 | border: none; 34 | } 35 | 36 | .item:disabled { 37 | color: gray !important; 38 | cursor: not-allowed !important; 39 | } 40 | 41 | input:focus { 42 | outline: none; 43 | } -------------------------------------------------------------------------------- /src/config/base-project/client/components/account-details/account-details.component.html: -------------------------------------------------------------------------------- 1 |
2 |

Id: {{account._id}}

3 |

Created: {{account.created | date:'M/d/y h:m:s a'}}

4 |

Full Name: {{account.name.first}} {{account.name.last}}

5 |

Account Number: {{account.accountNumber}}

6 |

Balance: ${{account.balance}}

7 |
Return to Accounts
8 |
9 | 10 |
11 |
Last Viewed Account
12 |

Id: {{lastViewed._id}}

13 |

Created: {{lastViewed.created | date:'M/d/y h:m:s a'}}

14 |

Full Name: {{lastViewed.name.first}} {{lastViewed.name.last}}

15 |

Account Number: {{lastViewed.accountNumber}}

16 |

Balance: ${{lastViewed.balance}}

17 |
Return to Menu
18 |
19 | 20 |
21 |
New Account
22 |
23 |
24 | 25 | 26 |
27 |
28 | 29 | 30 |
31 |
32 | 33 | 34 |
35 |
36 | 37 | 38 |
39 |
40 | 41 |
42 |
43 |
Return to Menu
44 |
45 | 46 |
47 |
New Account
48 |
49 |
50 | 51 | 52 |
53 |
54 | 55 | 56 |
57 |
58 | 59 | 60 |
61 |
62 | 63 |
64 |
65 |
Return to Accounts
66 |
-------------------------------------------------------------------------------- /src/config/base-project/client/components/account-details/account-details.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ActivatedRoute, Params, Router } from '@angular/router'; 3 | import { Account } from '../accounts/account.model'; 4 | import { AccountsService } from '../accounts/accounts.service'; 5 | import { Observable } from 'rxjs/Observable'; 6 | 7 | @Component({ 8 | selector: 'account-details', 9 | templateUrl: './account-details.component.html', 10 | styleUrls: ['./account-details.component.css', '../menu/menu.component.css'], 11 | providers: [AccountsService] 12 | }) 13 | export class AccountDetailsComponent implements OnInit { 14 | private account: Account; 15 | private lastViewed: Account; 16 | private newAccount: Account; 17 | private updateAccount: Account; 18 | 19 | constructor(private route: ActivatedRoute, private accountsService: AccountsService, private router: Router) {} 20 | 21 | public ngOnInit(): void { 22 | const observer: Observable < Params > = this.route.params; 23 | observer.subscribe((param) => { 24 | if (param['id'] && this.router.url.indexOf('/update-account') < 0) { 25 | // Read Account 26 | this.accountsService 27 | .getAccounts() 28 | .then((accounts) => { 29 | this.account = accounts.find((account) => { 30 | return param['id'] === account._id 31 | }); 32 | this.accountsService.setLastViewed(this.account.accountNumber); 33 | }); 34 | } 35 | if (this.router.url === '/last-viewed') { 36 | // Get the last viewed item 37 | this.accountsService 38 | .getLastViewed() 39 | .then((account) => { 40 | if (account) { 41 | this.lastViewed = account; 42 | } else { 43 | // Not found 44 | this.router.navigate(['/accounts']); 45 | } 46 | }); 47 | } 48 | if (this.router.url === '/create-account') { 49 | // Create new account 50 | this.newAccount = new Account(); 51 | } 52 | if (this.router.url.indexOf('/update-account') > -1 && param['id']) { 53 | // Update account 54 | this.accountsService 55 | .getAccounts() 56 | .then((accounts) => { 57 | this.updateAccount = accounts.find((account) => { 58 | return param['id'] === account._id 59 | }); 60 | }); 61 | } 62 | }); 63 | } 64 | 65 | private onSubmit(): void { 66 | this.accountsService 67 | .createAccount(this.newAccount) 68 | .then((account) => { 69 | this.router.navigate(['/accounts']); 70 | }); 71 | } 72 | 73 | private onUpdate(): void { 74 | this.accountsService 75 | .updateAccount(this.updateAccount) 76 | .then((account) => { 77 | this.router.navigate(['/accounts']); 78 | }); 79 | } 80 | } -------------------------------------------------------------------------------- /src/config/base-project/client/components/accounts/account.model.ts: -------------------------------------------------------------------------------- 1 | export class Account { 2 | public _id: string; 3 | public accountNumber: number; 4 | public balance: number; 5 | public created: string; 6 | public name: any; 7 | constructor() { 8 | this.name = {}; 9 | } 10 | } -------------------------------------------------------------------------------- /src/config/base-project/client/components/accounts/accounts.component.css: -------------------------------------------------------------------------------- 1 | .accounts { 2 | width: 1400px; 3 | padding: 20px; 4 | margin: 48px; 5 | } 6 | 7 | .accounts p { 8 | font-size: 55px; 9 | } 10 | 11 | table { 12 | font-size: 20px; 13 | margin: 0 auto; 14 | width: 100%; 15 | max-width: 100%; 16 | margin-bottom: 20px; 17 | background-color: transparent; 18 | border-spacing: 0; 19 | border-collapse: collapse; 20 | } 21 | 22 | table>tbody>tr>td, 23 | table>tbody>tr>th, 24 | table>tfoot>tr>td, 25 | table>tfoot>tr>th, 26 | table>thead>tr>td, 27 | table>thead>tr>th { 28 | padding: 9px; 29 | line-height: 1.4; 30 | vertical-align: top; 31 | border-bottom: 1px solid #ddd; 32 | } 33 | 34 | .my-button { 35 | padding: 10px; 36 | } 37 | 38 | .my-button:hover { 39 | cursor: pointer; 40 | padding: 10px; 41 | } -------------------------------------------------------------------------------- /src/config/base-project/client/components/accounts/accounts.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
IdAccount NumberBalanceCreatedFull NameAction
{{account._id}}{{account.accountNumber}}${{account.balance}}{{account.created | date:'M/d/y h:m:s a'}}{{account.name.first}} {{account.name.last}}
20 |
21 |
Create New Account
22 |
Return to Menu
23 |
24 |
-------------------------------------------------------------------------------- /src/config/base-project/client/components/accounts/accounts.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | 4 | import { AccountsService } from './accounts.service'; 5 | import { Account } from './account.model'; 6 | 7 | @Component({ 8 | selector: 'accounts', 9 | templateUrl: './accounts.component.html', 10 | styleUrls: ['./accounts.component.css', '../menu/menu.component.css'], 11 | providers: [AccountsService] 12 | }) 13 | export class AccountsComponent { 14 | private accounts = []; 15 | 16 | constructor(private accountsService: AccountsService, private router: Router) {} 17 | 18 | private create() { 19 | this.router.navigate(['/create-account']); 20 | } 21 | private read(index) { 22 | let account: Account = this.accounts[index]; 23 | this.router.navigate(['/account', account._id]); 24 | } 25 | private update(index) { 26 | let account: Account = this.accounts[index]; 27 | this.router.navigate(['/update-account', account._id]); 28 | } 29 | private delete(index) { 30 | let account: Account = this.accounts[index]; 31 | this.accountsService 32 | .deleteAccount(account.accountNumber) 33 | .then((removed) => { 34 | this.getAccounts(); 35 | }) 36 | } 37 | private getAccounts() { 38 | this.accountsService 39 | .getAccounts() 40 | .then((accounts) => { 41 | this.accounts = accounts; 42 | }); 43 | } 44 | ngOnInit(): void { 45 | this.getAccounts(); 46 | } 47 | } -------------------------------------------------------------------------------- /src/config/base-project/client/components/accounts/accounts.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Http } from '@angular/http'; 3 | import { Account } from './account.model'; 4 | 5 | @Injectable() 6 | export class AccountsService { 7 | constructor(private http: Http) {} 8 | 9 | public getAccounts(): Promise < Account[] > { 10 | return this.http.get('/api/accounts') 11 | .toPromise() 12 | .then(response => response.json().accounts as Account[]) 13 | .catch(this.handleError); 14 | } 15 | 16 | public setLastViewed(accountNumber: Number) { 17 | this.http.get(`/api/account/${accountNumber}`) 18 | .toPromise() 19 | .then(response => response.json().account as Account) 20 | .catch(this.handleError); 21 | } 22 | 23 | public getLastViewed(): Promise < Account > { 24 | return this.http.get('/api/last-viewed') 25 | .toPromise() 26 | .then(response => response.json().account as Account) 27 | .catch(this.handleError); 28 | } 29 | 30 | public deleteAccount(accountNumber: Number): Promise < Account > { 31 | return this.http.delete(`/api/account/${accountNumber}`) 32 | .toPromise() 33 | .then(response => response.json().account as Account) 34 | .catch(this.handleError); 35 | } 36 | 37 | public createAccount(account: Account): Promise < Account > { 38 | return this.http.post('/api/account', { 39 | 'account-number': account.accountNumber, 40 | 'first-name': account.name.first, 41 | 'last-name': account.name.last, 42 | 'balance': account.balance 43 | }).toPromise() 44 | .then(response => response.json().account as Account) 45 | .catch(this.handleError); 46 | } 47 | 48 | public updateAccount(account: Account): Promise < Account > { 49 | return this.http.put(`/api/account/${account.accountNumber}`, { 50 | 'account-number': account.accountNumber, 51 | 'first-name': account.name.first, 52 | 'last-name': account.name.last, 53 | 'balance': account.balance 54 | }).toPromise() 55 | .then(response => response.json().account as Account) 56 | .catch(this.handleError); 57 | } 58 | 59 | private handleError(error: any): Promise < any > { 60 | console.error('An error occurred', error); 61 | return Promise.reject(error.message || error); 62 | } 63 | } -------------------------------------------------------------------------------- /src/config/base-project/client/components/app/app.component.css: -------------------------------------------------------------------------------- 1 | main { 2 | font-family: Arial, Helvetica, sans-serif; 3 | text-align: center; 4 | display: block; 5 | font-size: 3em; 6 | margin-top: 48px; 7 | } -------------------------------------------------------------------------------- /src/config/base-project/client/components/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
-------------------------------------------------------------------------------- /src/config/base-project/client/components/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | @Component({ 3 | selector: 'my-app', 4 | templateUrl: './app.component.html', 5 | styleUrls: ['./app.component.css'] 6 | }) 7 | export class AppComponent { } 8 | -------------------------------------------------------------------------------- /src/config/base-project/client/components/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { HttpModule } from '@angular/http'; 4 | import { FormsModule } from '@angular/forms'; 5 | 6 | // Components 7 | import { AppComponent } from './app.component'; 8 | import { AccountsComponent } from '../accounts/accounts.component'; 9 | import { AccountDetailsComponent } from '../account-details/account-details.component'; 10 | import { MenuComponent } from '../menu/menu.component'; 11 | 12 | // Services 13 | import { AccountsService } from '../accounts/accounts.service'; 14 | 15 | // Router 16 | import { Router } from './app.routing'; 17 | 18 | @NgModule({ 19 | imports: [ 20 | BrowserModule, 21 | HttpModule, 22 | Router, 23 | FormsModule 24 | ], 25 | declarations: [ 26 | AppComponent, 27 | AccountsComponent, 28 | AccountDetailsComponent, 29 | MenuComponent 30 | ], 31 | providers: [ 32 | AccountsService 33 | ], 34 | bootstrap: [AppComponent] 35 | }) 36 | export class AppModule {} -------------------------------------------------------------------------------- /src/config/base-project/client/components/app/app.routing.ts: -------------------------------------------------------------------------------- 1 | import { ModuleWithProviders } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | import { AccountsComponent } from '../accounts/accounts.component'; 5 | import { AccountDetailsComponent } from '../account-details/account-details.component'; 6 | import { MenuComponent } from '../menu/menu.component'; 7 | 8 | const appRoutes: Routes = [{ 9 | path: '', 10 | component: MenuComponent 11 | }, 12 | { 13 | path: 'account/:id', 14 | component: AccountDetailsComponent 15 | }, 16 | { 17 | path: 'accounts', 18 | component: AccountsComponent 19 | }, 20 | { 21 | path: 'last-viewed', 22 | component: AccountDetailsComponent 23 | }, 24 | { 25 | path: 'create-account', 26 | component: AccountDetailsComponent 27 | }, 28 | { 29 | path: 'update-account/:id', 30 | component: AccountDetailsComponent 31 | } 32 | ]; 33 | 34 | export const Router: ModuleWithProviders = RouterModule.forRoot(appRoutes); -------------------------------------------------------------------------------- /src/config/base-project/client/components/menu/menu.component.css: -------------------------------------------------------------------------------- 1 | .menu { 2 | max-width: 350px; 3 | } 4 | 5 | .menu .item { 6 | width: 100%; 7 | } 8 | 9 | .box-wrapper { 10 | box-sizing: border-box; 11 | -webkit-box-orient: vertical; 12 | -webkit-flex-direction: column; 13 | flex-direction: column; 14 | margin: 8px; 15 | box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .2), 0 1px 1px 0 rgba(0, 0, 0, .14), 0 2px 1px -1px rgba(0, 0, 0, .12); 16 | margin: 0 auto; 17 | position: relative; 18 | padding-left: 25px; 19 | padding-right: 25px; 20 | padding-bottom: 25px; 21 | } 22 | 23 | .box-wrapper .item { 24 | box-shadow: 0 2px 5px 0 rgba(0, 0, 0, .26); 25 | color: rgb(33, 33, 33); 26 | margin-top: 20px; 27 | background-color: rgb(250, 250, 250); 28 | max-width: 100%; 29 | outline: none; 30 | display: inline-block; 31 | position: relative; 32 | vertical-align: middle; 33 | text-align: center; 34 | border-radius: 3px; 35 | box-sizing: border-box; 36 | text-transform: uppercase; 37 | font-weight: 500; 38 | font-size: 20px; 39 | -webkit-transition: box-shadow .4s cubic-bezier(.25, .8, .25, 1), background-color .4s cubic-bezier(.25, .8, .25, 1); 40 | transition: box-shadow .4s cubic-bezier(.25, .8, .25, 1), background-color .4s cubic-bezier(.25, .8, .25, 1); 41 | padding: 20px; 42 | } 43 | 44 | .box-wrapper .item:hover { 45 | background: transparent; 46 | cursor: pointer; 47 | } -------------------------------------------------------------------------------- /src/config/base-project/client/components/menu/menu.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/config/base-project/client/components/menu/menu.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | @Component({ 4 | selector: 'menu', 5 | templateUrl: './menu.component.html', 6 | styleUrls: ['./menu.component.css'] 7 | }) 8 | export class MenuComponent { 9 | constructor(private router: Router) {} 10 | } -------------------------------------------------------------------------------- /src/config/base-project/client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | [--ApplicationName--] 7 | 8 | 9 | 10 | 11 | 12 | Loading... 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/config/base-project/client/polyfills.ts: -------------------------------------------------------------------------------- 1 | import 'core-js/es6'; 2 | import 'core-js/es7/reflect'; 3 | require('zone.js/dist/zone'); 4 | if (process.env.ENV === 'production') { 5 | // Production 6 | } else { 7 | // Development 8 | Error['stackTraceLimit'] = Infinity; 9 | require('zone.js/dist/long-stack-trace-zone'); 10 | } 11 | -------------------------------------------------------------------------------- /src/config/base-project/client/vendor.ts: -------------------------------------------------------------------------------- 1 | // Angular 2 2 | import '@angular/platform-browser'; 3 | import '@angular/platform-browser-dynamic'; 4 | import '@angular/core'; 5 | import '@angular/common'; 6 | import '@angular/http'; 7 | import '@angular/router'; 8 | // RxJS 9 | import 'rxjs'; 10 | // Other vendors for example jQuery, Lodash or Bootstrap 11 | // You can import js, ts, css, sass, ... 12 | -------------------------------------------------------------------------------- /src/config/base-project/config/build.js: -------------------------------------------------------------------------------- 1 | const settings = require('./settings.json'); 2 | const webpack = require("webpack"); 3 | var WebpackDevServer = require("webpack-dev-server"); 4 | const serverConfig = require('./webpack.server'); 5 | const clientConfig = require('./webpack.client'); 6 | const spawn = require('child_process').spawn; 7 | 8 | /* Server webpack instance */ 9 | var express; 10 | webpack(serverConfig, (err, stats) => { 11 | if (err) { 12 | throw new Error('Webpack could not build at this time.', err); 13 | } 14 | console.log('[webpack:server:build]', stats.toString({ 15 | chunks: false, 16 | colors: true 17 | })); 18 | startExpressApp(); // After build start the server 19 | }); 20 | 21 | if (!process.env.PORT) { 22 | // Use the server port defined in the settings file 23 | process.env.PORT = settings.server.port; 24 | } 25 | process.env.DEBUG = 'modern-express:*'; 26 | 27 | function startExpressApp() { 28 | if (express && !express.killed) { 29 | // Stop the express application 30 | express.kill(); 31 | } 32 | // Start the express application 33 | express = spawn('node', ['build/compiled'], { 34 | env: process.env 35 | }); 36 | 37 | express.stdout.on('data', (data) => { 38 | process.stdout.write(data); 39 | }); 40 | } 41 | 42 | /* Client webpack instance */ 43 | if (clientConfig.devServer.inline) { 44 | // Attach live reload 45 | clientConfig.entry.app.unshift(`webpack-dev-server/client?http://${clientConfig.devServer.host}:${settings.client.port}`); 46 | } 47 | const compiler = webpack(clientConfig); 48 | clientConfig.devServer.port = settings.client.port; 49 | clientConfig.devServer.proxy[settings.server.proxy] = `http://localhost:${settings.server.port}`; 50 | 51 | const server = new WebpackDevServer(compiler, clientConfig.devServer); 52 | server.listen(settings.client.port, function() { 53 | console.log(`Starting server on http://localhost:${settings.client.port}`); 54 | }); -------------------------------------------------------------------------------- /src/config/base-project/config/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "database": { 3 | "port": 27017, 4 | "name": "myapp" 5 | }, 6 | "server": { 7 | "port": 4000, 8 | "proxy": "/api" 9 | }, 10 | "client": { 11 | "port": 3000 12 | } 13 | } -------------------------------------------------------------------------------- /src/config/base-project/config/webpack.client.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 4 | const root = require('app-root-path').path; 5 | 6 | module.exports = { 7 | devtool: 'cheap-module-eval-source-map', 8 | entry: { 9 | polyfills: `${root}/client/polyfills.ts`, 10 | vendor: `${root}/client/vendor.ts`, 11 | app: [`${root}/client/app.ts`] 12 | }, 13 | output: { 14 | path: `${root}/build/client`, 15 | publicPath: '/', 16 | filename: '[name].js', 17 | chunkFilename: '[id].chunk.js' 18 | }, 19 | resolve: { 20 | extensions: ['', '.js', '.ts'] 21 | }, 22 | module: { 23 | loaders: [{ 24 | test: /\.ts$/, 25 | loaders: ['awesome-typescript-loader', 'angular2-template-loader'] 26 | }, 27 | { 28 | test: /\.html$/, 29 | loader: 'html' 30 | }, 31 | { 32 | test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/, 33 | loader: 'file?name=assets/[name].[hash].[ext]' 34 | }, 35 | { 36 | test: /\.css$/, 37 | exclude: `${root}/client/components`, 38 | loader: ExtractTextPlugin.extract('style', 'css?sourceMap') 39 | }, 40 | { 41 | test: /\.css$/, 42 | include: `${root}/client/components`, 43 | loader: 'raw' 44 | } 45 | ] 46 | }, 47 | plugins: [ 48 | new webpack.optimize.CommonsChunkPlugin({ 49 | name: ['app', 'vendor', 'polyfills'] 50 | }), 51 | 52 | new HtmlWebpackPlugin({ 53 | template: `${root}/client/index.html` 54 | }), 55 | new ExtractTextPlugin('[name].css') 56 | ], 57 | devServer: { 58 | historyApiFallback: true, 59 | port: 3000, 60 | host: '0.0.0.0', 61 | inline: true, 62 | progress: true, 63 | stats: 'minimal', 64 | proxy: {} 65 | } 66 | }; -------------------------------------------------------------------------------- /src/config/base-project/config/webpack.server.js: -------------------------------------------------------------------------------- 1 | const root = require('app-root-path').path; 2 | module.exports = { 3 | entry: { 4 | build: `${root}/bin/www.ts` 5 | }, 6 | target: 'node', 7 | externals: [ 8 | /^[a-z\-0-9]+$/ // Ignore node_modules folder 9 | ], 10 | output: { 11 | filename: 'compiled', // output file 12 | path: `${root}/build`, 13 | libraryTarget: "commonjs" 14 | }, 15 | resolve: { 16 | // Add in `.ts` and `.tsx` as a resolvable extension. 17 | extensions: ['', '.webpack.js', '.web.js', '.ts', '.tsx', '.js'], 18 | }, 19 | resolveLoader: { 20 | root: [`${root}/node_modules`] 21 | }, 22 | module: { 23 | loaders: [{ 24 | // all files with a `.ts` or `.tsx` extension will be handled by `ts-loader` 25 | test: /\.tsx?$/, 26 | exclude: 'node_modules', 27 | loader: 'ts-loader' 28 | }, { test: /\.json$/, loader: "json-loader" }] 29 | }, 30 | watch: true 31 | }; -------------------------------------------------------------------------------- /src/config/base-project/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "projectName", 3 | "version": "1.0.0", 4 | "description": "projectDescription", 5 | "private": true, 6 | "directories": { 7 | "test": "tests" 8 | }, 9 | "scripts": { 10 | "postinstall": "node node_modules/typings/dist/bin.js install --overwrite", 11 | "start": "node config/build.js" 12 | }, 13 | "main": "", 14 | "keywords": [ 15 | "Easy", 16 | "TypeScript", 17 | "Starter", 18 | "Template" 19 | ], 20 | "author": "Rick Hernandez", 21 | "license": "MIT", 22 | "devDependencies": { 23 | "angular2-template-loader": "^0.4.0", 24 | "awesome-typescript-loader": "^2.2.4", 25 | "css-loader": "^0.23.1", 26 | "extract-text-webpack-plugin": "^1.0.1", 27 | "file-loader": "^0.8.5", 28 | "html-loader": "^0.4.3", 29 | "html-webpack-plugin": "^2.15.0", 30 | "json-loader": "^0.5.4", 31 | "raw-loader": "^0.5.1", 32 | "style-loader": "^0.13.1", 33 | "ts-loader": "^0.8.1", 34 | "typescript": "2.0.10", 35 | "typings": "^1.3.2", 36 | "webpack": "^1.13.0", 37 | "webpack-dev-server": "^1.14.1" 38 | }, 39 | "dependencies": { 40 | "app-root-path": "^1.3.0", 41 | "body-parser": "^1.15.2", 42 | "cookie-parser": "^1.4.3", 43 | "ejs": "^2.5.1", 44 | "express": "^4.14.0", 45 | "express-session": "^1.14.1", 46 | "mongoose": "^4.6.0", 47 | "morgan": "^1.7.0", 48 | "@angular/common": "2.0.0-rc.7", 49 | "@angular/compiler": "2.0.0-rc.7", 50 | "@angular/core": "2.0.0-rc.7", 51 | "@angular/forms": "2.0.0-rc.7", 52 | "@angular/http": "2.0.0-rc.7", 53 | "@angular/platform-browser": "2.0.0-rc.7", 54 | "@angular/platform-browser-dynamic": "2.0.0-rc.7", 55 | "@angular/router": "3.0.0-rc.3", 56 | "core-js": "^2.4.1", 57 | "rxjs": "5.0.0-beta.12", 58 | "zone.js": "^0.6.21" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/config/base-project/server/controllers/accounts.ts: -------------------------------------------------------------------------------- 1 | import { AccountsModel } from '../models/accounts'; 2 | /** 3 | * Accounts 4 | */ 5 | class Accounts { 6 | private static AccountsModel; 7 | public static initData(): void { 8 | AccountsModel.initData(); 9 | } 10 | public static getAccounts(): Promise < {} > { 11 | const promise = new Promise((resolve, reject) => { 12 | AccountsModel.getAccounts().then((data) => { 13 | resolve(data); 14 | }, (err) => { 15 | reject(err); 16 | }); 17 | }); 18 | return promise; 19 | } 20 | public static getAccount(accountNumber: number): Promise < { name } > { 21 | const promise = new Promise((resolve, reject) => { 22 | AccountsModel.getAccounts().then((data) => { 23 | const account = data.filter((item) => { 24 | return item.account === accountNumber; 25 | }); 26 | if (account.length !== 1) { 27 | reject('Invalid Account Number'); 28 | } else { 29 | resolve(account[0]); 30 | } 31 | }, (err) => { 32 | reject(err); 33 | }); 34 | }); 35 | return promise; 36 | } 37 | 38 | public static validAccountNumber(accountNumber: number): Promise < {} > { 39 | return AccountsModel.validAccount(accountNumber); 40 | } 41 | 42 | public static createAccount(account: Object): Promise < {} > { 43 | // TODO: Write more validation logic here 44 | return AccountsModel.createAccount(account); 45 | } 46 | 47 | } 48 | export { Accounts }; -------------------------------------------------------------------------------- /src/config/base-project/server/models/accounts-schema.ts: -------------------------------------------------------------------------------- 1 | import * as mongoose from 'mongoose'; 2 | 3 | (mongoose).Promise = global.Promise; 4 | 5 | const Schema = mongoose.Schema; 6 | const AccountSchema = new Schema({ 7 | created: { 8 | type: Date, 9 | default: Date.now 10 | }, 11 | accountNumber: { 12 | type: Number, 13 | unique: true 14 | }, 15 | name: { 16 | first: String, 17 | last: String 18 | }, 19 | balance: Number, 20 | }); 21 | 22 | const schema = mongoose.model('Account', AccountSchema); // You now have access to the Accounts Collection 23 | 24 | export { schema }; -------------------------------------------------------------------------------- /src/config/base-project/server/models/accounts.ts: -------------------------------------------------------------------------------- 1 | import * as root from 'app-root-path'; 2 | import { schema } from './accounts-schema'; 3 | 4 | /** 5 | * Accounts 6 | */ 7 | class AccountsModel { 8 | private static AccountsCollection = schema; 9 | public static initData(): void { 10 | const accounts = [{ 11 | account: 118950, 12 | name: "Isabella", 13 | lastName: "Ramirez", 14 | balance: 2582.77 15 | }, { 16 | account: 13458, 17 | name: "Megan", 18 | lastName: "Miller", 19 | balance: 392.93 20 | }, { 21 | account: 52950, 22 | name: "Ferguson", 23 | lastName: "Jones", 24 | balance: 90332.24 25 | }]; 26 | accounts.forEach((account) => { 27 | let promise = AccountsModel.AccountsCollection.findOne({ 28 | accountNumber: account.account 29 | }); 30 | promise.then((found) => { 31 | if (!found) { 32 | let Account = new AccountsModel.AccountsCollection({ 33 | accountNumber: account.account, 34 | name: { 35 | first: account.name, 36 | last: account.lastName 37 | }, 38 | balance: account.balance 39 | }); 40 | Account.save(); 41 | } 42 | }); 43 | }); 44 | } 45 | 46 | public static getAccounts(): Promise<{ account }[]> { 47 | const promise = new Promise((resolve, reject) => { 48 | AccountsModel.AccountsCollection.find({}).exec().then((accounts) => { 49 | resolve(accounts); 50 | }).catch((err) => { 51 | reject(err); 52 | }); 53 | }); 54 | return promise; 55 | } 56 | 57 | public static validAccount(account: number): Promise<{}> { 58 | const promise = new Promise((resolve, reject) => { 59 | AccountsModel.AccountsCollection.findOne({ accountNumber: account }) 60 | .exec() 61 | .then((account) => { 62 | if (account) { 63 | resolve(account); 64 | } else { 65 | reject(); 66 | } 67 | }).catch((err) => { 68 | reject(err); 69 | }); 70 | }); 71 | return promise; 72 | } 73 | 74 | public static createAccount(account: Object): Promise<{}> { 75 | const promise = new Promise((resolve, reject) => { 76 | 77 | let Account = new AccountsModel.AccountsCollection({ 78 | accountNumber: account['account-number'], 79 | name: { 80 | first: account['first-name'], 81 | last: account['last-name'] 82 | }, 83 | balance: account['balance'] 84 | }); 85 | 86 | Account.save((err, account) => { 87 | if (err) { 88 | reject(); 89 | } else { 90 | resolve(account); 91 | } 92 | }); 93 | 94 | }); 95 | return promise; 96 | } 97 | } 98 | 99 | export { AccountsModel }; -------------------------------------------------------------------------------- /src/config/base-project/server/routes/routes.ts: -------------------------------------------------------------------------------- 1 | import * as express from 'express'; 2 | import { Accounts } from '../controllers/accounts'; 3 | 4 | const router = express.Router(); 5 | Accounts.initData(); 6 | 7 | router.get('/last-viewed', (req: express.Request, res: express.Response, next: express.NextFunction) => { 8 | res.json({ 9 | account: req.session['account'] 10 | }); 11 | }); 12 | 13 | router.post('/account', (req: express.Request, res: express.Response, next: express.NextFunction) => { 14 | const account = req.body; 15 | if (account.hasOwnProperty('account-number') && account.hasOwnProperty('first-name') && account.hasOwnProperty('last-name') && account.hasOwnProperty('balance')) { 16 | Accounts.createAccount(account) 17 | .then((account) => { 18 | res.json(account); 19 | }).catch((error) => { 20 | res.sendStatus(500); // Error creating account 21 | }); 22 | } else { 23 | res.sendStatus(404); // Invalid Parameters Number 24 | } 25 | }); 26 | 27 | router 28 | .use('/account/:accountNumber', (req: express.Request, res: express.Response, next: express.NextFunction) => { 29 | // Verify Account Number is valid 30 | Accounts.validAccountNumber(req.params.accountNumber) 31 | .then((account) => { 32 | req['account'] = account; // Valid Account Number 33 | next(); 34 | }) 35 | .catch((err) => { 36 | // Account Number not Found 37 | res.sendStatus(404); // Invalid Account Number 38 | }); 39 | }) 40 | .route('/account/:accountNumber') 41 | .get((req: express.Request, res: express.Response, next: express.NextFunction) => { 42 | // Read Account 43 | req.session['account'] = req['account']; 44 | res.json(req.session['account']); 45 | }) 46 | .put((req: express.Request, res: express.Response, next: express.NextFunction) => { 47 | // Update Account 48 | if (req.body['balance']) { 49 | req['account'].balance = req.body['balance']; 50 | } 51 | if (req.body['first-name']) { 52 | req['account'].name.first = req.body['first-name']; 53 | } 54 | if (req.body['last-name']) { 55 | req['account'].name.last = req.body['last-name']; 56 | } 57 | req['account'].save((err) => { 58 | if (err) { 59 | res.sendStatus(500); // Error updating account 60 | } else { 61 | res.json(req['account']); 62 | } 63 | }); 64 | }) 65 | .delete((req: express.Request, res: express.Response, next: express.NextFunction) => { 66 | // Delete Account 67 | req['account'].remove((err) => { 68 | if (err) { 69 | res.sendStatus(500); // Error removing account 70 | } else { 71 | res.json(req['account']); 72 | } 73 | }); 74 | }); 75 | 76 | router.get('/accounts', (req: express.Request, res: express.Response, next: express.NextFunction) => { 77 | Accounts.getAccounts().then((accounts) => { 78 | res.json({ 79 | recent: req['account'], 80 | accounts: accounts 81 | }); 82 | }).catch((error) => { 83 | let err: any = new Error('Not Found'); 84 | err.status = 404; 85 | next(err); 86 | }); 87 | }); 88 | 89 | export = router; -------------------------------------------------------------------------------- /src/config/base-project/server/views/error.ejs: -------------------------------------------------------------------------------- 1 |

<%= message %>

2 |

<%= error.status %>

3 |
<%= error.stack %>
-------------------------------------------------------------------------------- /src/config/base-project/server/views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= title %> 7 | 8 | 9 | 10 | 11 |

<%= title %>

12 | <% if(typeof partial !== 'undefined') { %> 13 | <%- include(partial) %> 14 | <% } %> 15 | <% if(typeof account !== 'undefined') { %> 16 |

Last Viewed Account

17 |

Account Number: <%= account.account %>

18 |

Account Name: <%= account.name %> <%= account.lastName %>

19 |

Account Balance: $<%= account.balance %>

20 | <% } %> 21 | 22 | -------------------------------------------------------------------------------- /src/config/base-project/server/views/partials/json-output.ejs: -------------------------------------------------------------------------------- 1 |

2 | <% if(typeof data !== 'undefined') { %> 3 |

4 |             <%=  '\n'  + JSON.stringify(data, null, 2).replace('[', '').replace(']', '')%>
5 |         
6 | <% } %> 7 |

-------------------------------------------------------------------------------- /src/config/base-project/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es5", 5 | "noImplicitAny": false, 6 | "moduleResolution": "node", 7 | "sourceMap": true, 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "removeComments": false, 11 | "suppressImplicitAnyIndexErrors": true, 12 | "outDir": "build" 13 | }, 14 | "exclude": [ 15 | "node_modules", 16 | "config", 17 | "build", 18 | "release" 19 | ] 20 | } -------------------------------------------------------------------------------- /src/config/base-project/typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "resolution": { 3 | "main": "typings/" 4 | }, 5 | "globalDependencies": { 6 | "app-root-path": "registry:dt/app-root-path#1.2.1+20160719232327", 7 | "body-parser": "registry:dt/body-parser#0.0.0+20160619023215", 8 | "cookie-parser": "registry:dt/cookie-parser#1.3.4+20160316155526", 9 | "core-js": "registry:dt/core-js#0.0.0+20160725163759", 10 | "debug": "registry:dt/debug#0.0.0+20160317120654", 11 | "express": "registry:dt/express#4.0.0+20160708185218", 12 | "express-serve-static-core": "registry:dt/express-serve-static-core#4.0.0+20160829034835", 13 | "express-session": "registry:dt/express-session#0.0.0+20160819134112", 14 | "mime": "registry:dt/mime#0.0.0+20160316155526", 15 | "mongodb": "registry:dt/mongodb#2.1.0+20160602142941", 16 | "mongoose": "registry:dt/mongoose#4.5.9+20160826204928", 17 | "morgan": "registry:dt/morgan#1.7.0+20160524142355", 18 | "node": "registry:dt/node#6.0.0+20160830141956", 19 | "serve-static": "registry:dt/serve-static#0.0.0+20160606155157" 20 | } 21 | } -------------------------------------------------------------------------------- /src/config/directories.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var names = { 3 | core: 'base-project', 4 | ignore: ['.git', 'node_modules', 'typings', 'build'] 5 | }; 6 | exports.names = names; 7 | -------------------------------------------------------------------------------- /src/generators/mean-stack.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var shell = require('shelljs'); 3 | var inquirer = require('inquirer'); 4 | var fs = require('fs'); 5 | var path = require('path'); 6 | var directories = require('../config/directories'); 7 | var child_process = require('child_process'); 8 | /** 9 | * MEAN Stack Generator 10 | */ 11 | var MEANStack = (function () { 12 | function MEANStack() { 13 | } 14 | /* 15 | * Creates Everything Required for the MEAN Stack 16 | */ 17 | MEANStack.generateApplication = function (parent, base) { 18 | MEANStack.coreTokens['ApplicationName'] = base; 19 | var promise = new Promise(function (resolve, reject) { 20 | MEANStack.generateDirectories(parent, base).then(function () { 21 | MEANStack.generateFiles(parent, base).then(function () { 22 | resolve(); 23 | }); 24 | }); 25 | }); 26 | return promise; 27 | }; 28 | /* 29 | * Create Base Directories for the MEAN Stack 30 | */ 31 | MEANStack.generateDirectories = function (parent, base) { 32 | var promise = new Promise(function (resolve, reject) { 33 | var projectDir = parent + "/" + base; 34 | try { 35 | shell.mkdir('-p', projectDir); // Project Directory 36 | var core_1 = directories.names.core; 37 | MEANStack.coreDirectories.forEach(function (name) { 38 | name = name.substr(name.lastIndexOf(core_1) + core_1.length + 1, name.length); 39 | try { 40 | shell.mkdir('-p', projectDir + "/" + name); 41 | } 42 | catch (err) { 43 | reject(err); 44 | } 45 | }); 46 | resolve(); 47 | } 48 | catch (error) { 49 | reject(error); 50 | } 51 | }); 52 | return promise; 53 | }; 54 | /* 55 | * Create Generic Files for the MEAN Stack 56 | */ 57 | MEANStack.generateFiles = function (parent, base) { 58 | var projectDir = parent + "/" + base; 59 | var promise = new Promise(function (resolve, reject) { 60 | var core = directories.names.core; 61 | MEANStack.coreFiles.forEach(function (name) { 62 | var fileName = name.substr(name.lastIndexOf(core) + core.length + 1, name.length); 63 | try { 64 | shell.touch(projectDir + "/" + fileName); 65 | MEANStack.parseFile(name, projectDir + "/" + fileName); 66 | } 67 | catch (err) { 68 | reject(err); 69 | } 70 | }); 71 | resolve(); 72 | }); 73 | return promise; 74 | }; 75 | /** 76 | * Parses original file 77 | */ 78 | MEANStack.parseFile = function (source, destination) { 79 | try { 80 | var data = fs.readFileSync(source, 'utf8'); 81 | if (data.indexOf('[--') !== -1 && data.indexOf('--]') !== -1) { 82 | // This file contains a token 83 | var lines = data.split('\n'); 84 | for (var line in lines) { 85 | var start = lines[line].indexOf('[--'); 86 | var end = lines[line].indexOf('--]'); 87 | if (start !== -1 && end !== -1) { 88 | // This line has a token 89 | var value = lines[line].substring(start + 3, end); 90 | if (MEANStack.coreTokens[value]) { 91 | // This is a valid token replace the value 92 | var tokenized = lines[line] 93 | .replace(value, MEANStack.coreTokens[value]) 94 | .replace('[--', '') 95 | .replace('--]', ''); 96 | lines[line] = tokenized; 97 | } 98 | } 99 | } 100 | var joinedLines = lines.join('\n'); 101 | fs.writeFileSync(destination, joinedLines, 'utf8'); 102 | } 103 | else { 104 | // No Tokens founds just copy over code 105 | fs.writeFileSync(destination, data, 'utf8'); 106 | } 107 | } 108 | catch (err) { 109 | throw err; 110 | } 111 | }; 112 | MEANStack.askQuestion = function (options) { 113 | return inquirer.prompt(options); 114 | }; 115 | MEANStack.writableDirectory = function (directory) { 116 | var promise = new Promise(function (resolve, reject) { 117 | try { 118 | fs.accessSync(directory, fs['W_OK'] | fs['R_OK']); 119 | resolve(); 120 | } 121 | catch (err) { 122 | reject(err); 123 | } 124 | }); 125 | return promise; 126 | }; 127 | MEANStack.directoryExist = function (directory) { 128 | var promise = new Promise(function (resolve, reject) { 129 | fs.lstat(directory, function (err, stat) { 130 | if (!err && stat.isDirectory()) { 131 | // Folder or File Exists 132 | resolve(stat.isDirectory()); 133 | } 134 | else { 135 | // Directory does not Exists 136 | if (!err && stat.isFile()) { 137 | // Check if it's a file 138 | reject(stat.isFile()); 139 | } 140 | else { 141 | reject(err); 142 | } 143 | } 144 | }); 145 | }); 146 | return promise; 147 | }; 148 | /** 149 | * Initialize the core code and registers it to this instance. 150 | **/ 151 | MEANStack.initializeCore = function (base) { 152 | var promise = new Promise(function (resolve, reject) { 153 | var project = '/full-stack/src/config/base-project'; 154 | MEANStack.directoryExist("" + base + project).then(function () { 155 | // Found Base Project 156 | MEANStack.registerFiles("" + base + project); 157 | resolve(); // Analyzed Project 158 | }).catch(function (error) { 159 | reject('Core Is missing!'); 160 | }); 161 | }); 162 | return promise; 163 | }; 164 | MEANStack.registerFiles = function (directory) { 165 | var ignore = directories.names.ignore; 166 | var files = fs.readdirSync(directory); 167 | files.forEach(function (file) { 168 | var destination = directory + "/" + file; 169 | var ignoreDestination = ignore.find(function (item) { 170 | return item === file; 171 | }) !== undefined; 172 | if (!ignoreDestination) { 173 | // Valid Directory or File 174 | var stat = fs.lstatSync(destination); 175 | if (stat.isFile()) { 176 | MEANStack.coreFiles.push(destination); 177 | } 178 | if (stat.isDirectory()) { 179 | MEANStack.coreDirectories.push(destination); 180 | MEANStack.registerFiles(destination); 181 | } 182 | } 183 | }); 184 | }; 185 | /** 186 | * Finds the Global path for npm modules 187 | **/ 188 | MEANStack.getGlobalPath = function () { 189 | var promise = new Promise(function (resolve, reject) { 190 | try { 191 | var binary = shell.which('npm'); 192 | var globalDir = path.resolve(binary['stdout'] + '/../../lib/node_modules'); 193 | resolve(globalDir); 194 | } 195 | catch (err) { 196 | reject(err); 197 | } 198 | }); 199 | return promise; 200 | }; 201 | MEANStack.serveLocalInstance = function (parent, cb) { 202 | var promise = new Promise(function (resolve, reject) { 203 | MEANStack.directoryExist(parent + "/package.json").then(function () { 204 | // This is a directory! ERROR out 205 | reject(); 206 | }).catch(function (isFile) { 207 | if (isFile === true) { 208 | // This is a valid file 209 | try { 210 | fs.accessSync(parent + "/package.json", fs['R_OK']); 211 | } 212 | catch (err) { 213 | // Not able to read in JSON file 214 | reject(err); 215 | } 216 | var config = require(parent + "/package.json"); 217 | if (config && config.scripts && config.scripts.start && config.scripts.start !== '') { 218 | // Valid start command 219 | var child = child_process.exec(config.scripts.start); 220 | child.stdout.on('data', function (data) { 221 | // Runs callback with live feed 222 | cb(data); 223 | }); 224 | child.stdout.on('close', function (data) { 225 | // All done! 226 | resolve(); 227 | }); 228 | } 229 | else { 230 | reject(); 231 | } 232 | } 233 | else { 234 | // ERROR Thrown 235 | reject(isFile); 236 | } 237 | }); 238 | }); 239 | return promise; 240 | }; 241 | MEANStack.getVersion = function (parent) { 242 | var config = require(parent + "/package.json"); 243 | return config.version; 244 | }; 245 | MEANStack.coreTokens = {}; 246 | MEANStack.coreFiles = []; 247 | MEANStack.coreDirectories = []; 248 | return MEANStack; 249 | }()); 250 | exports.MEANStack = MEANStack; 251 | -------------------------------------------------------------------------------- /tests/app.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsecademy/full-stack/dee5b3405d42153c5ec96b45132649a70e9a2336/tests/app.js --------------------------------------------------------------------------------