├── client ├── assets │ ├── .gitkeep │ └── menu.json ├── app │ ├── app.component.css │ ├── home │ │ ├── home.component.css │ │ ├── home.component.html │ │ ├── home.component.ts │ │ ├── menu.service.ts │ │ ├── menu.json │ │ ├── home.component.spec.ts │ │ └── menu.service.spec.ts │ ├── demo │ │ ├── chat │ │ │ ├── chat.component.css │ │ │ ├── chat.component.html │ │ │ ├── chat.component.spec.ts │ │ │ └── chat.component.ts │ │ ├── posts │ │ │ ├── posts.component.css │ │ │ ├── posts.component.html │ │ │ ├── posts.component.ts │ │ │ ├── posts.service.spec.ts │ │ │ ├── posts.component.spec.ts │ │ │ └── posts.service.ts │ │ ├── upload-file │ │ │ ├── file-list │ │ │ │ ├── file-list.component.css │ │ │ │ ├── file-list.service.ts │ │ │ │ ├── file-list.component.ts │ │ │ │ ├── file-list.service.spec.ts │ │ │ │ ├── file-list.component.spec.ts │ │ │ │ └── file-list.component.html │ │ │ └── upload-file │ │ │ │ ├── upload-file.component.css │ │ │ │ ├── upload-file.component.ts │ │ │ │ ├── upload-file.component.spec.ts │ │ │ │ └── upload-file.component.html │ │ └── form │ │ │ ├── hero.ts │ │ │ ├── hero-form.component.css │ │ │ ├── hero-form.component.ts │ │ │ ├── hero-form.component.spec.ts │ │ │ └── hero-form.component.html │ ├── app.component.ts │ ├── app-routing.module.ts │ ├── app.module.ts │ ├── app.component.html │ └── app.component.spec.ts ├── favicon.ico ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── typings.d.ts ├── tsconfig.app.json ├── tsconfig.spec.json ├── main.ts ├── styles.css ├── test.ts ├── index.html └── polyfills.ts ├── app.yaml ├── .ebextensions └── nodecommand.config ├── proxy.conf.json ├── server ├── routes │ └── api │ │ ├── demo │ │ ├── test.ts │ │ ├── test.api.spec.ts │ │ ├── test.spec.ts │ │ ├── posts.ts │ │ ├── posts.api.spec.ts │ │ ├── posts.spec.ts │ │ ├── upload-file.ts │ │ └── upload-file.spec.ts │ │ └── crud.ts ├── spec │ └── support │ │ └── jasmine.json ├── tsconfig.app.json ├── tsconfig.spec.json ├── models │ └── user.ts ├── socket.io │ └── chat.ts ├── www.ts └── app.ts ├── e2e ├── tsconfig.e2e.json ├── app.po.ts └── app.e2e-spec.ts ├── .editorconfig ├── .vscode ├── tasks.json └── launch.json ├── tsconfig.json ├── .gitignore ├── protractor.conf.js ├── karma.conf.js ├── .angular-cli.json ├── types └── node-mocks-http │ └── index.d.ts ├── tslint.json ├── package.json └── README.md /client/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/app/app.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/app/home/home.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app.yaml: -------------------------------------------------------------------------------- 1 | env: flex 2 | runtime: nodejs 3 | -------------------------------------------------------------------------------- /client/app/demo/chat/chat.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/app/demo/posts/posts.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/app/home/home.component.html: -------------------------------------------------------------------------------- 1 |
Welcome!
2 | -------------------------------------------------------------------------------- /client/app/demo/upload-file/file-list/file-list.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cj-wang/mean-start/HEAD/client/favicon.ico -------------------------------------------------------------------------------- /client/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /.ebextensions/nodecommand.config: -------------------------------------------------------------------------------- 1 | option_settings: 2 | aws:elasticbeanstalk:container:nodejs: 3 | NodeCommand: "npm run build-start" -------------------------------------------------------------------------------- /client/typings.d.ts: -------------------------------------------------------------------------------- 1 | /* SystemJS module definition */ 2 | declare var module: NodeModule; 3 | interface NodeModule { 4 | id: string; 5 | } 6 | -------------------------------------------------------------------------------- /client/app/demo/form/hero.ts: -------------------------------------------------------------------------------- 1 | export class Hero { 2 | constructor( 3 | public id: number, 4 | public name: string, 5 | public power: string, 6 | public alterEgo?: string 7 | ) { } 8 | } 9 | -------------------------------------------------------------------------------- /proxy.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api": { 3 | "target": "http://localhost:3000", 4 | "secure": false 5 | }, 6 | "/socket.io": { 7 | "target": "http://localhost:3000", 8 | "secure": false 9 | } 10 | } -------------------------------------------------------------------------------- /client/app/demo/form/hero-form.component.css: -------------------------------------------------------------------------------- 1 | .ng-valid[required], .ng-valid.required { 2 | border-left: 5px solid #42A948; /* green */ 3 | } 4 | .ng-invalid:not(form) { 5 | border-left: 5px solid #a94442; /* red */ 6 | } 7 | -------------------------------------------------------------------------------- /client/app/demo/chat/chat.component.html: -------------------------------------------------------------------------------- 1 |
Messages:
2 | 3 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /server/routes/api/demo/test.ts: -------------------------------------------------------------------------------- 1 | import { Router, Request, Response } from 'express'; 2 | 3 | export default Router() 4 | .get('/test', testHandler); 5 | 6 | export function testHandler(req: Request, res: Response) { 7 | res.send('Test API works'); 8 | }; 9 | -------------------------------------------------------------------------------- /e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types":[ 8 | "jasmine", 9 | "node" 10 | ] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /server/spec/support/jasmine.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "", 3 | "spec_files": [ 4 | "**/*.spec.ts" 5 | ], 6 | "helpers": [ 7 | "spec/helpers/**/*.ts" 8 | ], 9 | "stopSpecOnExpectationFailure": false, 10 | "random": false, 11 | "reporters": [] 12 | } 13 | -------------------------------------------------------------------------------- /server/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../dist", 5 | "module": "commonjs", 6 | "baseUrl": "", 7 | "types": [ 8 | "node" 9 | ] 10 | }, 11 | "exclude": [ 12 | "**/*.spec.ts" 13 | ] 14 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /client/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "es2015", 6 | "baseUrl": "", 7 | "types": [ 8 | "node" 9 | ] 10 | }, 11 | "exclude": [ 12 | "test.ts", 13 | "**/*.spec.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "0.1.0", 5 | "command": "tsc", 6 | "isShellCommand": true, 7 | "args": ["-p", "server/tsconfig.app.json"], 8 | "showOutput": "silent", 9 | "problemMatcher": "$tsc" 10 | } -------------------------------------------------------------------------------- /server/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../dist", 5 | "module": "commonjs", 6 | "baseUrl": "", 7 | "types": [ 8 | "jasmine", 9 | "node" 10 | ] 11 | }, 12 | "include": [ 13 | "**/*.spec.ts", 14 | "**/*.d.ts" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /client/app/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-home', 5 | templateUrl: './home.component.html', 6 | styleUrls: ['./home.component.css'] 7 | }) 8 | export class HomeComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /client/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "module": "commonjs", 6 | "baseUrl": "", 7 | "types": [ 8 | "jasmine", 9 | "node" 10 | ] 11 | }, 12 | "files": [ 13 | "test.ts" 14 | ], 15 | "include": [ 16 | "**/*.spec.ts", 17 | "**/*.d.ts" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /e2e/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, element, by } from 'protractor'; 2 | 3 | export class MeanStartPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getBrandText() { 9 | return element(by.css('app-root .navbar-brand')).getText(); 10 | } 11 | 12 | getHomeMenu() { 13 | return element(by.css('app-root [routerlink="/"]')).getText(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /client/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule); 12 | -------------------------------------------------------------------------------- /client/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | 3 | /* Sticky footer styles */ 4 | html { 5 | position: relative; 6 | min-height: 100%; 7 | } 8 | body { 9 | margin: 60px 0; 10 | } 11 | .footer { 12 | position: absolute; 13 | bottom: 0; 14 | width: 100%; 15 | height: 60px; 16 | line-height: 60px; 17 | background-color: #f5f5f5; 18 | } 19 | -------------------------------------------------------------------------------- /client/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build. 2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do 3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `.angular-cli.json`. 5 | 6 | export const environment = { 7 | production: false 8 | }; 9 | -------------------------------------------------------------------------------- /client/app/home/menu.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Http } from '@angular/http'; 3 | import { Observable } from 'rxjs/Observable'; 4 | import 'rxjs/add/operator/map'; 5 | 6 | @Injectable() 7 | export class MenuService { 8 | 9 | constructor(private http: Http) { } 10 | 11 | getMenu() { 12 | return this.http.get('assets/menu.json') 13 | .map(res => res.json().data); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /server/routes/api/demo/test.api.spec.ts: -------------------------------------------------------------------------------- 1 | import * as supertest from 'supertest'; 2 | 3 | import app from '../../../app'; 4 | 5 | describe('GET /api/demo/test', () => { 6 | it('should return "Test API works"', (done) => { 7 | supertest(app) 8 | .get('/api/demo/test') 9 | .expect(200) 10 | .then((response) => { 11 | expect(response.text).toBe('Test API works'); 12 | done(); 13 | }); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /client/app/demo/posts/posts.component.html: -------------------------------------------------------------------------------- 1 |
Posts:
2 |
Loading...
3 |
{{errorMessage}}
4 |
5 |
6 |

{{ post.title }}

7 |

{{post.body}}

8 | Card link 9 | Another link 10 |
11 |
12 | -------------------------------------------------------------------------------- /server/models/user.ts: -------------------------------------------------------------------------------- 1 | import * as mongoose from 'mongoose'; 2 | 3 | export interface IUser { 4 | email: string; 5 | password: string; 6 | name: string; 7 | }; 8 | 9 | const userSchema = new mongoose.Schema({ 10 | email: String, 11 | password: String, 12 | name: String 13 | }); 14 | 15 | interface IUserModel extends IUser, mongoose.Document { } 16 | 17 | const User = mongoose.model('User', userSchema); 18 | 19 | export default User; 20 | -------------------------------------------------------------------------------- /client/app/demo/upload-file/upload-file/upload-file.component.css: -------------------------------------------------------------------------------- 1 | input[type="file"] { 2 | color: white; 3 | } 4 | 5 | .my-drop-zone { 6 | height: 150px; 7 | padding: 50px 20px; 8 | margin-bottom: 20px; 9 | background-color: #f5f5f5; 10 | border-radius: 4px; 11 | -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.05); 12 | box-shadow: inset 0 1px 1px rgba(0,0,0,.05); 13 | border: dotted 3px lightgray; 14 | } 15 | 16 | .nv-file-over { 17 | border: dotted 3px red; 18 | } 19 | -------------------------------------------------------------------------------- /server/socket.io/chat.ts: -------------------------------------------------------------------------------- 1 | import * as sio from 'socket.io'; 2 | 3 | const chatServer = sio({ 4 | path: '/socket.io/chat' 5 | }); 6 | 7 | chatServer.on('connection', (socket) => { 8 | console.log('a user connected'); 9 | socket.on('chat message', (msg) => { 10 | console.log('message: ' + msg); 11 | chatServer.emit('chat message', msg); 12 | }); 13 | socket.on('disconnect', () => { 14 | console.log('user disconnected'); 15 | }); 16 | }); 17 | 18 | export default chatServer; 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "outDir": "./dist/out-tsc", 5 | "baseUrl": "types", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "moduleResolution": "node", 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "target": "es5", 12 | "typeRoots": [ 13 | "node_modules/@types", 14 | "types" 15 | ], 16 | "lib": [ 17 | "es2016", 18 | "dom" 19 | ] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /e2e/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { MeanStartPage } from './app.po'; 2 | 3 | describe('mean-start App', () => { 4 | let page: MeanStartPage; 5 | 6 | beforeEach(() => { 7 | page = new MeanStartPage(); 8 | }); 9 | 10 | it('should display brand', () => { 11 | page.navigateTo(); 12 | expect(page.getBrandText()).toEqual('MEAN Start'); 13 | }); 14 | 15 | it('should display home menu', () => { 16 | page.navigateTo(); 17 | expect(page.getHomeMenu()).toContain('Home'); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /server/routes/api/demo/test.spec.ts: -------------------------------------------------------------------------------- 1 | import * as httpMocks from 'node-mocks-http'; 2 | 3 | import { testHandler } from './test'; 4 | 5 | describe('testHandler', () => { 6 | it('should return "Test API works"', () => { 7 | const req = httpMocks.createRequest(); 8 | const res = httpMocks.createResponse(); 9 | testHandler(req, res); 10 | expect(res._getStatusCode()).toBe(200); 11 | expect(res._isEndCalled()).toBeTruthy(); 12 | expect(res._getData()).toBe('Test API works'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /client/app/home/menu.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title": "Demo", 4 | "sub": [ 5 | { 6 | "title": "REST", 7 | "link": "posts" 8 | }, 9 | { 10 | "title": "Chat", 11 | "link": "chat" 12 | }, 13 | { 14 | "title": "Form", 15 | "link": "form" 16 | }, 17 | { 18 | "title": "Upload Files", 19 | "link": "upload-file" 20 | }, 21 | { 22 | "title": "Uploaded Files", 23 | "link": "file-list" 24 | } 25 | ] 26 | } 27 | ] -------------------------------------------------------------------------------- /server/routes/api/demo/posts.ts: -------------------------------------------------------------------------------- 1 | import { Router, Request, Response } from 'express'; 2 | import axios from 'axios'; 3 | 4 | export default Router() 5 | .get('/posts', getPosts); 6 | 7 | export function getPosts(req: Request, res: Response) { 8 | const API = 'https://jsonplaceholder.typicode.com'; 9 | return axios.get(`${API}/posts`) 10 | .then(posts => { 11 | res.status(200).json({ 12 | data: posts.data 13 | }); 14 | }) 15 | .catch(error => { 16 | res.status(500).json({ 17 | error: 'Error' 18 | }); 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /client/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { MenuService } from './home/menu.service'; 3 | 4 | @Component({ 5 | selector: 'app-root', 6 | templateUrl: './app.component.html', 7 | styleUrls: ['./app.component.css'] 8 | }) 9 | export class AppComponent implements OnInit { 10 | 11 | menu: any; 12 | navbarExpanded = false; 13 | 14 | constructor(private menuService: MenuService) { } 15 | 16 | ngOnInit() { 17 | this.menuService.getMenu().subscribe(menu => { 18 | this.menu = menu; 19 | }); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /client/app/demo/upload-file/upload-file/upload-file.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { FileUploader } from 'ng2-file-upload'; 3 | 4 | @Component({ 5 | selector: 'app-upload-file', 6 | templateUrl: './upload-file.component.html', 7 | styleUrls: ['./upload-file.component.css'] 8 | }) 9 | export class UploadFileComponent implements OnInit { 10 | 11 | hasBaseDropZoneOver = false; 12 | 13 | uploader = new FileUploader({ 14 | url: '/api/demo/upload-file' 15 | }); 16 | 17 | constructor() { } 18 | 19 | ngOnInit() { 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /client/assets/menu.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "title": "Demo", 5 | "sub": [ 6 | { 7 | "title": "REST", 8 | "link": "/posts" 9 | }, 10 | { 11 | "title": "Chat", 12 | "link": "/chat" 13 | }, 14 | { 15 | "title": "Form", 16 | "link": "/form" 17 | }, 18 | { 19 | "title": "Upload Files", 20 | "link": "/upload-file" 21 | }, 22 | { 23 | "title": "Uploaded Files", 24 | "link": "/file-list" 25 | } 26 | ] 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /client/app/demo/upload-file/file-list/file-list.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Http } from '@angular/http'; 3 | import { Observable } from 'rxjs/Observable'; 4 | import 'rxjs/add/operator/map'; 5 | 6 | @Injectable() 7 | export class FileListService { 8 | 9 | constructor(private http: Http) { } 10 | 11 | // Get uploaded files 12 | getFiles() { 13 | return this.http.get('/api/demo/upload-file') 14 | .map(res => res.json().data); 15 | } 16 | 17 | remove(filename) { 18 | return this.http.delete('/api/demo/upload-file/' + filename); 19 | } 20 | 21 | removeAll() { 22 | return this.http.delete('/api/demo/upload-file'); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /server/routes/api/demo/posts.api.spec.ts: -------------------------------------------------------------------------------- 1 | import * as supertest from 'supertest'; 2 | 3 | import app from '../../../app'; 4 | 5 | describe('GET /api/demo/posts', () => { 6 | it('should return the posts', (done) => { 7 | supertest(app) 8 | .get('/api/demo/posts') 9 | .expect(200) 10 | .then((response) => { 11 | expect(response.body.data).toEqual(jasmine.any(Array)); 12 | expect(response.body.data.length).toBeTruthy(); 13 | expect(response.body.data[0]).toEqual({ 14 | userId: 1, 15 | id: 1, 16 | title: jasmine.any(String), 17 | body: jasmine.any(String) 18 | }); 19 | done(); 20 | }); 21 | }); 22 | 23 | }); 24 | -------------------------------------------------------------------------------- /client/app/home/home.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { HomeComponent } from './home.component'; 4 | 5 | describe('HomeComponent', () => { 6 | let component: HomeComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ HomeComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(HomeComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /client/app/demo/chat/chat.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ChatComponent } from './chat.component'; 4 | 5 | describe('ChatComponent', () => { 6 | let component: ChatComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ ChatComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ChatComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /client/app/demo/posts/posts.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { PostsService } from './posts.service'; 3 | 4 | @Component({ 5 | selector: 'app-posts', 6 | templateUrl: './posts.component.html', 7 | styleUrls: ['./posts.component.css'] 8 | }) 9 | export class PostsComponent implements OnInit { 10 | // instantiate posts to an empty array 11 | posts = []; 12 | errorMessage: any; 13 | 14 | constructor(private postsService: PostsService) { } 15 | 16 | ngOnInit() { 17 | // Retrieve posts from the API 18 | this.postsService.getAllPosts().subscribe( 19 | posts => { 20 | this.posts = posts; 21 | }, 22 | error => { 23 | this.errorMessage = error; 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /client/app/demo/form/hero-form.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | import { Hero } from './hero'; 4 | 5 | @Component({ 6 | selector: 'app-hero-form', 7 | templateUrl: './hero-form.component.html', 8 | styleUrls: ['./hero-form.component.css'] 9 | }) 10 | export class HeroFormComponent implements OnInit { 11 | 12 | powers = [ 13 | 'Really Smart', 14 | 'Super Flexible', 15 | 'Super Hot', 16 | 'Weather Changer' 17 | ]; 18 | model = new Hero(18, 'Dr IQ', this.powers[0], 'Chuck Overstreet'); 19 | submitted = false; 20 | 21 | constructor() { } 22 | 23 | ngOnInit() { 24 | } 25 | 26 | onSubmit() { 27 | this.submitted = true; 28 | } 29 | 30 | newHero() { 31 | this.model = new Hero(42, '', ''); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | testem.log 34 | /typings 35 | 36 | # e2e 37 | /e2e/*.js 38 | /e2e/*.map 39 | 40 | # System Files 41 | .DS_Store 42 | Thumbs.db 43 | 44 | # Elastic Beanstalk Files 45 | .elasticbeanstalk/* 46 | !.elasticbeanstalk/*.cfg.yml 47 | !.elasticbeanstalk/*.global.yml 48 | -------------------------------------------------------------------------------- /client/app/demo/chat/chat.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, NgZone } from '@angular/core'; 2 | import * as sio from 'socket.io-client'; 3 | 4 | @Component({ 5 | selector: 'app-chat', 6 | templateUrl: './chat.component.html', 7 | styleUrls: ['./chat.component.css'] 8 | }) 9 | export class ChatComponent implements OnInit { 10 | msgs = ['aa', 'bb']; 11 | socket: SocketIOClient.Socket; 12 | 13 | constructor(private zone: NgZone) { } 14 | 15 | ngOnInit() { 16 | this.socket = sio({ 17 | path: '/socket.io/chat' 18 | }); 19 | this.socket.on('chat message', (msg: string) => { 20 | this.zone.run(() => { 21 | this.msgs.push(msg); 22 | }); 23 | }); 24 | } 25 | 26 | send(msg: string) { 27 | // this.msgs.push(msg); 28 | this.socket.emit('chat message', msg); 29 | }; 30 | 31 | } 32 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible Node.js debug attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "program": "${workspaceRoot}/server/www.ts", 12 | "cwd": "${workspaceRoot}", 13 | "outFiles": [ 14 | "${workspaceRoot}/dist/**/*.js" 15 | ], 16 | "sourceMaps": true, 17 | "preLaunchTask": "tsc" 18 | }, 19 | { 20 | "type": "node", 21 | "request": "attach", 22 | "name": "Attach to Process", 23 | "port": 5858, 24 | "outFiles": [], 25 | "sourceMaps": true 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /client/app/demo/form/hero-form.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { FormsModule } from '@angular/forms'; 3 | 4 | import { HeroFormComponent } from './hero-form.component'; 5 | 6 | describe('HeroFormComponent', () => { 7 | let component: HeroFormComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(async(() => { 11 | TestBed.configureTestingModule({ 12 | imports: [FormsModule], 13 | declarations: [HeroFormComponent] 14 | }) 15 | .compileComponents(); 16 | })); 17 | 18 | beforeEach(() => { 19 | fixture = TestBed.createComponent(HeroFormComponent); 20 | component = fixture.componentInstance; 21 | fixture.detectChanges(); 22 | }); 23 | 24 | it('should create', () => { 25 | expect(component).toBeTruthy(); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './e2e/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | beforeLaunch: function() { 23 | require('ts-node').register({ 24 | project: 'e2e/tsconfig.e2e.json' 25 | }); 26 | }, 27 | onPrepare() { 28 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /client/app/home/menu.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | import { Http, BaseRequestOptions } from '@angular/http'; 3 | import { MockBackend } from '@angular/http/testing'; 4 | 5 | import { MenuService } from './menu.service'; 6 | 7 | describe('MenuService', () => { 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({ 10 | providers: [ 11 | MockBackend, 12 | BaseRequestOptions, 13 | { 14 | provide: Http, 15 | useFactory: (backendInstance: MockBackend, defaultOptions: BaseRequestOptions) => { 16 | return new Http(backendInstance, defaultOptions); 17 | }, 18 | deps: [MockBackend, BaseRequestOptions] 19 | }, 20 | MenuService 21 | ] 22 | 23 | }); 24 | }); 25 | 26 | it('should ...', inject([MenuService], (service: MenuService) => { 27 | expect(service).toBeTruthy(); 28 | })); 29 | }); 30 | -------------------------------------------------------------------------------- /client/app/demo/posts/posts.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | import { Http, BaseRequestOptions } from '@angular/http'; 3 | import { MockBackend } from '@angular/http/testing'; 4 | 5 | import { PostsService } from './posts.service'; 6 | 7 | describe('PostsService', () => { 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({ 10 | providers: [ 11 | MockBackend, 12 | BaseRequestOptions, 13 | { 14 | provide: Http, 15 | useFactory: (backendInstance: MockBackend, defaultOptions: BaseRequestOptions) => { 16 | return new Http(backendInstance, defaultOptions); 17 | }, 18 | deps: [MockBackend, BaseRequestOptions] 19 | }, 20 | PostsService 21 | ] 22 | }); 23 | }); 24 | 25 | it('should ...', inject([PostsService], (service: PostsService) => { 26 | expect(service).toBeTruthy(); 27 | })); 28 | }); 29 | -------------------------------------------------------------------------------- /client/app/demo/upload-file/file-list/file-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { FileListService } from './file-list.service'; 3 | 4 | @Component({ 5 | selector: 'app-file-list', 6 | templateUrl: './file-list.component.html', 7 | styleUrls: ['./file-list.component.css'] 8 | }) 9 | export class FileListComponent implements OnInit { 10 | 11 | files = []; 12 | 13 | constructor(private fileListService: FileListService) { } 14 | 15 | ngOnInit() { 16 | this.refresh(); 17 | } 18 | 19 | refresh() { 20 | this.fileListService.getFiles().subscribe((files) => { 21 | this.files = files; 22 | }); 23 | } 24 | 25 | remove(file) { 26 | this.fileListService.remove(file.key).subscribe(() => { 27 | this.refresh(); 28 | }); 29 | } 30 | 31 | removeAll() { 32 | this.fileListService.removeAll().subscribe(() => { 33 | this.refresh(); 34 | }); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /client/app/demo/upload-file/upload-file/upload-file.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { FileUploadModule } from 'ng2-file-upload'; 4 | 5 | import { UploadFileComponent } from './upload-file.component'; 6 | 7 | describe('UploadFileComponent', () => { 8 | let component: UploadFileComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(async(() => { 12 | TestBed.configureTestingModule({ 13 | imports: [ 14 | FileUploadModule 15 | ], 16 | declarations: [ UploadFileComponent ] 17 | }) 18 | .compileComponents(); 19 | })); 20 | 21 | beforeEach(() => { 22 | fixture = TestBed.createComponent(UploadFileComponent); 23 | component = fixture.componentInstance; 24 | fixture.detectChanges(); 25 | }); 26 | 27 | it('should create', () => { 28 | expect(component).toBeTruthy(); 29 | expect(component.uploader).toBeTruthy(); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /client/app/demo/posts/posts.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { Observable } from 'rxjs/Rx'; 3 | 4 | import { PostsComponent } from './posts.component'; 5 | import { PostsService } from './posts.service'; 6 | 7 | class FakePostsService { 8 | getAllPosts() { 9 | return Observable.of([ 10 | {}, 11 | {} 12 | ]); 13 | } 14 | } 15 | 16 | describe('PostsComponent', () => { 17 | let component: PostsComponent; 18 | let fixture: ComponentFixture; 19 | 20 | beforeEach(async(() => { 21 | TestBed.configureTestingModule({ 22 | declarations: [PostsComponent], 23 | providers: [{ provide: PostsService, useClass: FakePostsService }] 24 | }) 25 | .compileComponents(); 26 | })); 27 | 28 | beforeEach(() => { 29 | fixture = TestBed.createComponent(PostsComponent); 30 | component = fixture.componentInstance; 31 | fixture.detectChanges(); 32 | }); 33 | 34 | it('should create', () => { 35 | expect(component).toBeTruthy(); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /client/app/demo/upload-file/file-list/file-list.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | import { Http, BaseRequestOptions } from '@angular/http'; 3 | import { MockBackend } from '@angular/http/testing'; 4 | 5 | import { FileListService } from './file-list.service'; 6 | 7 | describe('FileListService', () => { 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({ 10 | providers: [FileListService] 11 | }); 12 | TestBed.configureTestingModule({ 13 | providers: [ 14 | MockBackend, 15 | BaseRequestOptions, 16 | { 17 | provide: Http, 18 | useFactory: (backendInstance: MockBackend, defaultOptions: BaseRequestOptions) => { 19 | return new Http(backendInstance, defaultOptions); 20 | }, 21 | deps: [MockBackend, BaseRequestOptions] 22 | }, 23 | FileListService 24 | ] 25 | }); 26 | }); 27 | 28 | it('should ...', inject([FileListService], (service: FileListService) => { 29 | expect(service).toBeTruthy(); 30 | })); 31 | }); 32 | -------------------------------------------------------------------------------- /client/app/demo/upload-file/file-list/file-list.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { Observable } from 'rxjs/Rx'; 3 | 4 | import { FileListComponent } from './file-list.component'; 5 | import { FileListService } from './file-list.service'; 6 | 7 | class FakeFileListService { 8 | getFiles() { 9 | return Observable.of([]); 10 | } 11 | } 12 | 13 | describe('FileListComponent', () => { 14 | let component: FileListComponent; 15 | let fixture: ComponentFixture; 16 | 17 | beforeEach(async(() => { 18 | TestBed.configureTestingModule({ 19 | declarations: [ FileListComponent ], 20 | providers: [{ provide: FileListService, useClass: FakeFileListService }] 21 | }) 22 | .compileComponents(); 23 | })); 24 | 25 | beforeEach(() => { 26 | fixture = TestBed.createComponent(FileListComponent); 27 | component = fixture.componentInstance; 28 | fixture.detectChanges(); 29 | }); 30 | 31 | it('should create', () => { 32 | expect(component).toBeTruthy(); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /client/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/long-stack-trace-zone'; 4 | import 'zone.js/dist/proxy.js'; 5 | import 'zone.js/dist/sync-test'; 6 | import 'zone.js/dist/jasmine-patch'; 7 | import 'zone.js/dist/async-test'; 8 | import 'zone.js/dist/fake-async-test'; 9 | import { getTestBed } from '@angular/core/testing'; 10 | import { 11 | BrowserDynamicTestingModule, 12 | platformBrowserDynamicTesting 13 | } from '@angular/platform-browser-dynamic/testing'; 14 | 15 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. 16 | declare var __karma__: any; 17 | declare var require: any; 18 | 19 | // Prevent Karma from running prematurely. 20 | __karma__.loaded = function () {}; 21 | 22 | // First, initialize the Angular testing environment. 23 | getTestBed().initTestEnvironment( 24 | BrowserDynamicTestingModule, 25 | platformBrowserDynamicTesting() 26 | ); 27 | // Then we find all the tests. 28 | const context = require.context('./', true, /\.spec\.ts$/); 29 | // And load the modules. 30 | context.keys().map(context); 31 | // Finally, start Karma to run the tests. 32 | __karma__.start(); 33 | -------------------------------------------------------------------------------- /client/app/demo/posts/posts.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Http, Response } from '@angular/http'; 3 | import { Observable } from 'rxjs/Observable'; 4 | import 'rxjs/add/observable/throw'; 5 | import 'rxjs/add/operator/catch'; 6 | import 'rxjs/add/operator/map'; 7 | 8 | @Injectable() 9 | export class PostsService { 10 | 11 | constructor(private http: Http) { } 12 | 13 | // Get all posts from the API 14 | getAllPosts() { 15 | return this.http.get('/api/demo/posts') 16 | .map(res => res.json().data) 17 | .catch(this.handleError); 18 | } 19 | 20 | private handleError(error: Response | any) { 21 | // In a real world app, you might use a remote logging infrastructure 22 | let errMsg: string; 23 | if (error instanceof Response) { 24 | try { 25 | const body = error.json() || ''; 26 | const err = body.error || JSON.stringify(body); 27 | errMsg = `${error.status} - ${error.statusText || ''} ${err}`; 28 | } catch (e) { 29 | errMsg = `${error.status} - ${error.statusText || ''}`; 30 | } 31 | } else { 32 | errMsg = error.message ? error.message : error.toString(); 33 | } 34 | console.error(errMsg); 35 | return Observable.throw(errMsg); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/0.13/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular/cli'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular/cli/plugins/karma') 14 | ], 15 | client:{ 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | files: [ 19 | { pattern: './client/test.ts', watched: false } 20 | ], 21 | preprocessors: { 22 | './client/test.ts': ['@angular/cli'] 23 | }, 24 | mime: { 25 | 'text/x-typescript': ['ts','tsx'] 26 | }, 27 | coverageIstanbulReporter: { 28 | reports: [ 'html', 'lcovonly' ], 29 | fixWebpackSourcePaths: true 30 | }, 31 | angularCli: { 32 | environment: 'dev' 33 | }, 34 | reporters: config.angularCli && config.angularCli.codeCoverage 35 | ? ['progress', 'coverage-istanbul'] 36 | : ['progress', 'kjhtml'], 37 | port: 9876, 38 | colors: true, 39 | logLevel: config.LOG_INFO, 40 | autoWatch: true, 41 | browsers: ['Chrome'], 42 | singleRun: false 43 | }); 44 | }; 45 | -------------------------------------------------------------------------------- /client/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { HomeComponent } from './home/home.component'; 5 | import { PostsComponent } from './demo/posts/posts.component'; 6 | import { ChatComponent } from './demo/chat/chat.component'; 7 | import { HeroFormComponent } from './demo/form/hero-form.component'; 8 | import { UploadFileComponent } from './demo/upload-file/upload-file/upload-file.component'; 9 | import { FileListComponent } from './demo/upload-file/file-list/file-list.component'; 10 | 11 | const appRoutes: Routes = [ 12 | { 13 | path: 'home', 14 | component: HomeComponent 15 | }, 16 | { 17 | path: 'posts', 18 | component: PostsComponent 19 | }, 20 | { 21 | path: 'chat', 22 | component: ChatComponent 23 | }, 24 | { 25 | path: 'form', 26 | component: HeroFormComponent 27 | }, 28 | { 29 | path: 'upload-file', 30 | component: UploadFileComponent 31 | }, 32 | { 33 | path: 'file-list', 34 | component: FileListComponent 35 | }, 36 | { 37 | path: '', 38 | redirectTo: '/home', 39 | pathMatch: 'full' 40 | }, 41 | { 42 | path: '**', 43 | // component: PageNotFoundComponent 44 | redirectTo: '/home' 45 | } 46 | ]; 47 | 48 | @NgModule({ 49 | imports: [ 50 | RouterModule.forRoot(appRoutes) 51 | ], 52 | exports: [ 53 | RouterModule 54 | ] 55 | }) 56 | export class AppRoutingModule { } 57 | -------------------------------------------------------------------------------- /client/app/demo/upload-file/file-list/file-list.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
Uploaded files
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 30 | 31 | 32 |
NameSizeUploaded DateActions
{{ file.originalname }}{{ file.size/1024/1024 | number:'.2' }} MB{{file.uploadDate | date:'medium'}} 23 | 24 | Download 25 | 26 | 29 |
33 | 34 |
35 | 38 |
39 | 40 |
41 |
-------------------------------------------------------------------------------- /.angular-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "project": { 4 | "name": "mean-start" 5 | }, 6 | "apps": [ 7 | { 8 | "root": "client", 9 | "outDir": "dist/public", 10 | "assets": [ 11 | "assets", 12 | "favicon.ico" 13 | ], 14 | "index": "index.html", 15 | "main": "main.ts", 16 | "polyfills": "polyfills.ts", 17 | "test": "test.ts", 18 | "tsconfig": "tsconfig.app.json", 19 | "testTsconfig": "tsconfig.spec.json", 20 | "prefix": "app", 21 | "styles": [ 22 | "../node_modules/bootstrap/dist/css/bootstrap.css", 23 | "../node_modules/font-awesome/css/font-awesome.css", 24 | "styles.css" 25 | ], 26 | "scripts": [], 27 | "environmentSource": "environments/environment.ts", 28 | "environments": { 29 | "dev": "environments/environment.ts", 30 | "prod": "environments/environment.prod.ts" 31 | } 32 | } 33 | ], 34 | "e2e": { 35 | "protractor": { 36 | "config": "./protractor.conf.js" 37 | } 38 | }, 39 | "lint": [ 40 | { 41 | "project": "client/tsconfig.app.json" 42 | }, 43 | { 44 | "project": "client/tsconfig.spec.json" 45 | }, 46 | { 47 | "project": "e2e/tsconfig.e2e.json" 48 | }, 49 | { 50 | "project": "server/tsconfig.app.json" 51 | }, 52 | { 53 | "project": "server/tsconfig.spec.json" 54 | } 55 | ], 56 | "test": { 57 | "karma": { 58 | "config": "./karma.conf.js" 59 | } 60 | }, 61 | "defaults": { 62 | "styleExt": "css", 63 | "component": {} 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /client/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { HttpModule } from '@angular/http'; 5 | 6 | import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; 7 | import { FileUploadModule } from 'ng2-file-upload'; 8 | 9 | import { AppComponent } from './app.component'; 10 | import { AppRoutingModule } from './app-routing.module'; 11 | 12 | import { HomeComponent } from './home/home.component'; 13 | import { MenuService } from './home/menu.service'; 14 | 15 | import { PostsComponent } from './demo/posts/posts.component'; 16 | import { PostsService } from './demo/posts/posts.service'; 17 | import { ChatComponent } from './demo/chat/chat.component'; 18 | import { HeroFormComponent } from './demo/form/hero-form.component'; 19 | import { UploadFileComponent } from './demo/upload-file/upload-file/upload-file.component'; 20 | import { FileListComponent } from './demo/upload-file/file-list/file-list.component'; 21 | import { FileListService } from './demo/upload-file/file-list/file-list.service'; 22 | 23 | @NgModule({ 24 | declarations: [ 25 | AppComponent, 26 | HomeComponent, 27 | PostsComponent, 28 | ChatComponent, 29 | HeroFormComponent, 30 | UploadFileComponent, 31 | FileListComponent 32 | ], 33 | imports: [ 34 | BrowserModule, 35 | FormsModule, 36 | HttpModule, 37 | NgbModule.forRoot(), 38 | FileUploadModule, 39 | AppRoutingModule 40 | ], 41 | providers: [ 42 | MenuService, 43 | PostsService, 44 | FileListService 45 | ], 46 | bootstrap: [AppComponent] 47 | }) 48 | export class AppModule { } 49 | -------------------------------------------------------------------------------- /server/routes/api/demo/posts.spec.ts: -------------------------------------------------------------------------------- 1 | import * as httpMocks from 'node-mocks-http'; 2 | import * as nock from 'nock'; 3 | 4 | import { getPosts } from './posts'; 5 | 6 | describe('getPosts', () => { 7 | it('should return the posts', (done) => { 8 | // Expected data 9 | const posts = [{ 10 | userId: '1', 11 | id: '1', 12 | title: 'title1', 13 | body: 'body1' 14 | }, { 15 | userId: '2', 16 | id: '2', 17 | title: 'title2', 18 | body: 'body2' 19 | }]; 20 | // Mock http 21 | nock('https://jsonplaceholder.typicode.com') 22 | .get('/posts') 23 | .reply(200, posts); 24 | // Mock req and res 25 | const req = httpMocks.createRequest(); 26 | const res = httpMocks.createResponse(); 27 | // Test 28 | getPosts(req, res) 29 | .then(() => { 30 | expect(res._getStatusCode()).toBe(200); 31 | expect(res._isEndCalled()).toBeTruthy(); 32 | expect(res._isJSON()).toBeTruthy(); 33 | expect(JSON.parse(res._getData()).data).toEqual(posts); 34 | done(); 35 | }); 36 | }); 37 | 38 | it('should return error if destination not reachable', (done) => { 39 | // Previous interceptor was used, we're not setting up a new one here, so the next request will fail 40 | const req = httpMocks.createRequest(); 41 | const res = httpMocks.createResponse(); 42 | getPosts(req, res) 43 | .then(() => { 44 | expect(res._getStatusCode()).toBe(500); 45 | expect(res._isEndCalled()).toBeTruthy(); 46 | expect(res._isJSON()).toBeTruthy(); 47 | expect(JSON.parse(res._getData()).error).toBe('Error'); 48 | done(); 49 | }); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /client/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 31 | 32 | 33 |
34 | 35 |
-------------------------------------------------------------------------------- /client/app/demo/form/hero-form.component.html: -------------------------------------------------------------------------------- 1 |
2 |
Hero Form
3 |
4 |
5 | 6 | 7 |
8 | Name is required 9 |
10 |
11 |
12 | 13 | 14 |
15 |
16 | 17 | 20 |
21 | Power is required 22 |
23 |
24 | 25 | 26 |
27 |
28 |
29 |
You submitted the following:
30 |
31 |
Name
32 |
{{ model.name }}
33 |
34 |
35 |
Alter Ego
36 |
{{ model.alterEgo }}
37 |
38 |
39 |
Power
40 |
{{ model.power }}
41 |
42 |
43 | 44 |
-------------------------------------------------------------------------------- /server/routes/api/crud.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | 3 | const crudRouter = Router(); 4 | 5 | const models = require('require-all')({ 6 | dirname: __dirname + '/../../models', 7 | filter: /^([^\.].*)\.(ts|js)$/ 8 | }); 9 | 10 | Object.keys(models).forEach((name) => { 11 | console.log(`Add REST ${name}`); 12 | const model = models[name].default; 13 | 14 | crudRouter.route('/' + name + 's') 15 | // C 16 | .post((req, res) => { 17 | const m = new model(); 18 | Object.assign(m, req.body); 19 | m.save((err) => { 20 | if (err) { 21 | res.json({ error: err }); 22 | } else { 23 | res.json(m); 24 | } 25 | }); 26 | }) 27 | // R 28 | .get((req, res) => { 29 | model.find((err, ms) => { 30 | if (err) { 31 | res.json({ error: err }); 32 | } else { 33 | res.json(ms); 34 | } 35 | }); 36 | }); 37 | 38 | crudRouter.route('/' + name + 's/:_id') 39 | // R 40 | .get((req, res) => { 41 | model.findById(req.params._id, (err, m) => { 42 | if (err) { 43 | res.json({ error: err }); 44 | } else { 45 | res.json(m); 46 | } 47 | }); 48 | }) 49 | // U 50 | .put((req, res) => { 51 | model.findById(req.params._id, (err, m) => { 52 | if (err) { 53 | res.json({ error: err }); 54 | return; 55 | } 56 | Object.assign(m, req.body); 57 | m.save((errSave) => { 58 | if (errSave) { 59 | res.json({ error: errSave }); 60 | } else { 61 | res.json(m); 62 | } 63 | }); 64 | }); 65 | }) 66 | // D 67 | .delete((req, res) => { 68 | model.remove({ 69 | _id: req.params._id 70 | }, (err) => { 71 | if (err) { 72 | res.json({ error: err }); 73 | } 74 | }); 75 | }); 76 | 77 | }); 78 | 79 | export default crudRouter; 80 | -------------------------------------------------------------------------------- /client/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | import { Observable } from 'rxjs/Rx'; 4 | import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; 5 | 6 | import { AppComponent } from './app.component'; 7 | import { MenuService } from './home/menu.service'; 8 | 9 | class FakeMenuService { 10 | getMenu() { 11 | return Observable.of([ 12 | { 13 | title: 'Menu1', 14 | sub: [ 15 | { 16 | title: 'Menu1-Sub1', 17 | link: 'menu1-1' 18 | }, 19 | { 20 | title: 'Menu1-Sub2', 21 | link: 'menu1-2' 22 | } 23 | ] 24 | } 25 | ]); 26 | } 27 | } 28 | 29 | describe('AppComponent', () => { 30 | beforeEach(async(() => { 31 | TestBed.configureTestingModule({ 32 | imports: [ 33 | RouterTestingModule, 34 | NgbModule.forRoot() 35 | ], 36 | declarations: [ 37 | AppComponent 38 | ], 39 | providers: [{ provide: MenuService, useClass: FakeMenuService }] 40 | }).compileComponents(); 41 | })); 42 | 43 | it('should create the app', async(() => { 44 | const fixture = TestBed.createComponent(AppComponent); 45 | const app = fixture.debugElement.componentInstance; 46 | expect(app).toBeTruthy(); 47 | })); 48 | 49 | it(`should have a menu`, async(() => { 50 | const fixture = TestBed.createComponent(AppComponent); 51 | const app = fixture.debugElement.componentInstance; 52 | fixture.detectChanges(); 53 | expect(app.menu).toBeTruthy(); 54 | })); 55 | 56 | it('should render menu', async(() => { 57 | const fixture = TestBed.createComponent(AppComponent); 58 | fixture.detectChanges(); 59 | const compiled = fixture.debugElement.nativeElement; 60 | expect(compiled.querySelector('[href="/menu1-1"]').textContent).toBe('Menu1-Sub1'); 61 | expect(compiled.querySelector('[href="/menu1-2"]').textContent).toBe('Menu1-Sub2'); 62 | })); 63 | }); 64 | -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | MeanStart 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 43 | 44 | 45 |
46 | Loading... 47 |
48 | 49 |
50 | 51 | 52 |
53 |
54 | GitHub 55 | © 2017 56 |
57 |
58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /server/routes/api/demo/upload-file.ts: -------------------------------------------------------------------------------- 1 | import { Express, Router, Request, Response } from 'express'; 2 | import * as multer from 'multer'; 3 | import * as multerS3 from 'multer-s3'; 4 | import * as AWS from 'aws-sdk'; 5 | 6 | // Create an S3 client 7 | const s3 = new AWS.S3(); 8 | const bucketName = 'cj-wang-mean-start-upload-file'; 9 | 10 | // Setup multer with multerS3 11 | const upload = multer({ 12 | storage: multerS3({ 13 | s3: s3, 14 | bucket: bucketName, 15 | acl: 'public-read', 16 | key: function (req, file, cb) { 17 | cb(null, file.originalname); 18 | } 19 | }) 20 | }); 21 | 22 | /** 23 | * Store infomation of all the uploaded files 24 | * TODO: Use DB stoorage 25 | */ 26 | const files: { [key: string]: Express.Multer.File } = {}; 27 | 28 | export { files }; 29 | 30 | export default Router() 31 | .post('/upload-file', upload.single('file'), uploadFile) 32 | .get('/upload-file', listFiles) 33 | .delete('/upload-file/:_filename', remove) 34 | .delete('/upload-file', removeAll); 35 | 36 | /** 37 | * Upload 38 | * @param req 39 | * @param res 40 | */ 41 | export function uploadFile(req: Request, res: Response) { 42 | const file = req.file; 43 | // Set upload date 44 | file['uploadDate'] = new Date(); 45 | // Save file info 46 | files[file.originalname] = file; 47 | // Send response 48 | res.end(); 49 | }; 50 | 51 | /** 52 | * List uploaded files 53 | * @param req 54 | * @param res 55 | */ 56 | export function listFiles(req: Request, res: Response) { 57 | const filesArray = []; 58 | Object.keys(files).forEach((filename) => { 59 | filesArray.push(files[filename]); 60 | }); 61 | res.json({ 62 | data: filesArray 63 | }); 64 | }; 65 | 66 | /** 67 | * Remove file 68 | * @param req 69 | * @param res 70 | */ 71 | export function remove(req: Request, res: Response) { 72 | const file = files[req.params._filename]; 73 | delete files[req.params._filename]; 74 | res.end(); 75 | } 76 | 77 | /** 78 | * Remove all files 79 | * @param req 80 | * @param res 81 | */ 82 | export function removeAll(req: Request, res: Response) { 83 | Object.keys(files).forEach((filename) => { 84 | delete files[filename]; 85 | }); 86 | res.end(); 87 | } 88 | -------------------------------------------------------------------------------- /server/routes/api/demo/upload-file.spec.ts: -------------------------------------------------------------------------------- 1 | import * as httpMocks from 'node-mocks-http'; 2 | import { Express } from 'express'; 3 | 4 | import { files, uploadFile, listFiles, remove, removeAll } from './upload-file'; 5 | 6 | describe('uploadFile', () => { 7 | it('should create a file entry', () => { 8 | const req = httpMocks.createRequest({}); 9 | req.file = { 10 | fieldname: 'file', 11 | filename: 'abc.txt', 12 | originalname: 'abc.txt', 13 | encoding: '7bit', 14 | mimetype: 'application/octet-stream', 15 | size: 123, 16 | destination: '', 17 | path: '', 18 | buffer: null, 19 | }; 20 | const res = httpMocks.createResponse(); 21 | uploadFile(req, res); 22 | expect(res._getStatusCode()).toBe(200); 23 | expect(res._isEndCalled()).toBeTruthy(); 24 | expect(files[req.file.filename]).toBe(req.file); 25 | expect(files[req.file.filename]['uploadDate']).toBeTruthy(); 26 | }); 27 | }); 28 | 29 | describe('listFiles', () => { 30 | it('should return the file list', () => { 31 | const req = httpMocks.createRequest({}); 32 | const res = httpMocks.createResponse(); 33 | listFiles(req, res); 34 | expect(res._getStatusCode()).toBe(200); 35 | expect(res._isEndCalled()).toBeTruthy(); 36 | expect(res._isJSON()).toBeTruthy(); 37 | expect(JSON.parse(res._getData()).data.length).toBe(1); 38 | expect(JSON.parse(res._getData()).data[0].filename).toBe('abc.txt'); 39 | }); 40 | }); 41 | 42 | describe('remove', () => { 43 | it('should delete the file entry', () => { 44 | const req = httpMocks.createRequest({ 45 | params: { 46 | _filename: 'abcde' 47 | } 48 | }); 49 | const res = httpMocks.createResponse(); 50 | remove(req, res); 51 | expect(res._getStatusCode()).toBe(200); 52 | expect(res._isEndCalled()).toBeTruthy(); 53 | expect(files[req.params._filename]).toBeFalsy(); 54 | }); 55 | }); 56 | 57 | describe('removeAll', () => { 58 | it('should delete all the file entries', () => { 59 | const req = httpMocks.createRequest({}); 60 | const res = httpMocks.createResponse(); 61 | removeAll(req, res); 62 | expect(res._getStatusCode()).toBe(200); 63 | expect(res._isEndCalled()).toBeTruthy(); 64 | expect(files).toEqual({}); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /types/node-mocks-http/index.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for node-mocks-http 1.6.1 2 | // Project: https://github.com/howardabrams/node-mocks-http 3 | // Definitions by: CJ Wang 4 | // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped 5 | 6 | import * as express from 'express'; 7 | 8 | export function createRequest(reqOpts?: MockRequestOptions): MockRequest; 9 | export function createResponse(resOpts?: MockResponseOptions): MockResponse; 10 | export function createMocks(reqOpts?: MockRequestOptions, resOpts?: MockResponseOptions): MockRequestResponse; 11 | 12 | export interface MockRequestOptions { 13 | method?: string; 14 | url?: string; 15 | originalUrl?: string; 16 | path?: string; 17 | params?: { [key: string]: any; }; 18 | session?: { [key: string]: any; }; 19 | cookies?: { [key: string]: any; }; 20 | signedCookies?: { [key: string]: any; }; 21 | headers?: { [key: string]: any; }; 22 | body?: { [key: string]: any; }; 23 | query?: { [key: string]: any; }; 24 | files?: { [key: string]: any; }; 25 | eventEmitter?: any; 26 | } 27 | 28 | export interface MockRequest extends express.Request { 29 | _setParameter(key: string, value: any): void; 30 | _setSessionVariable(variable: string, value: any): void; 31 | _setCookiesVariable(variable: string, value: any): void; 32 | _setSignedCookiesVariable(variable: string, value: any): void; 33 | _setHeadersVariable(variable: string, value: any): void; 34 | _setFilesVariable(variable: string, value: any): void; 35 | _setMethod(method: string): void; 36 | _setURL(value: string): void; 37 | _setOriginalUrl(value: string): void; 38 | _setBody(body: any): void; 39 | _addBody(key: string, value: any): void; 40 | } 41 | 42 | export interface MockResponseOptions { 43 | encoding?: string; 44 | writableStream?: any; 45 | eventEmitter?: any; 46 | req?: MockRequest; 47 | } 48 | 49 | export interface MockResponse extends express.Response { 50 | _isEndCalled(): boolean; 51 | _getHeaders(): any; 52 | _getData(): any; 53 | _getStatusCode(): number; 54 | _getStatusMessage(): string; 55 | _isJSON(): boolean; 56 | _isUTF8(): boolean; 57 | _isDataLengthValid(): boolean; 58 | _getRedirectUrl(): string; 59 | _getRenderView(): string; 60 | _getRenderData(): any; 61 | } 62 | 63 | export interface MockRequestResponse { 64 | req: MockRequest; 65 | res: MockResponse; 66 | } 67 | -------------------------------------------------------------------------------- /client/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/ 22 | // import 'core-js/es6/symbol'; 23 | // import 'core-js/es6/object'; 24 | // import 'core-js/es6/function'; 25 | // import 'core-js/es6/parse-int'; 26 | // import 'core-js/es6/parse-float'; 27 | // import 'core-js/es6/number'; 28 | // import 'core-js/es6/math'; 29 | // import 'core-js/es6/string'; 30 | // import 'core-js/es6/date'; 31 | // import 'core-js/es6/array'; 32 | // import 'core-js/es6/regexp'; 33 | // import 'core-js/es6/map'; 34 | // import 'core-js/es6/set'; 35 | 36 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 37 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 38 | 39 | /** IE10 and IE11 requires the following to support `@angular/animation`. */ 40 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 41 | 42 | 43 | /** Evergreen browsers require these. **/ 44 | import 'core-js/es6/reflect'; 45 | import 'core-js/es7/reflect'; 46 | 47 | 48 | /** ALL Firefox browsers require the following to support `@angular/animation`. **/ 49 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 50 | 51 | 52 | 53 | /*************************************************************************************************** 54 | * Zone JS is required by Angular itself. 55 | */ 56 | import 'zone.js/dist/zone'; // Included with Angular CLI. 57 | 58 | 59 | 60 | /*************************************************************************************************** 61 | * APPLICATION IMPORTS 62 | */ 63 | 64 | /** 65 | * Date, currency, decimal and percent pipes. 66 | * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 67 | */ 68 | // import 'intl'; // Run `npm install --save intl`. 69 | -------------------------------------------------------------------------------- /server/www.ts: -------------------------------------------------------------------------------- 1 | import * as http from 'http'; 2 | import * as debugModule from 'debug'; 3 | 4 | import app from './app'; 5 | 6 | const debug = debugModule('express-start:server'); 7 | 8 | // Get port from environment and store in Express. 9 | const port = normalizePort(process.env.PORT || '3000'); 10 | app.set('port', port); 11 | 12 | // create server and listen on provided port (on all network interfaces). 13 | const server = http.createServer(app); 14 | 15 | // socket.io 16 | const sioModules = require('require-all')({ 17 | dirname: __dirname + '/socket.io', 18 | filter: (filename: string) => { 19 | filename = filename.toLowerCase(); 20 | if ((filename.endsWith('.ts') && !filename.endsWith('.spec.ts')) 21 | || (filename.endsWith('.js') && !filename.endsWith('.spec.js'))) { 22 | return filename.substr(0, filename.length - 3); 23 | } 24 | } 25 | }); 26 | for (const name of Object.keys(sioModules)) { 27 | const exported = sioModules[name].default; 28 | if (exported && exported.constructor.name === 'Server') { 29 | console.log(`Add socket.io server ${name}`); 30 | const sioServer = exported as SocketIO.Server; 31 | sioServer.attach(server); 32 | } 33 | } 34 | 35 | server.listen(port); 36 | server.on('error', onError); 37 | server.on('listening', onListening); 38 | 39 | /** 40 | * Normalize a port into a number, string, or false. 41 | */ 42 | function normalizePort(val: any): number | string | boolean { 43 | const portInt = parseInt(val, 10); 44 | 45 | if (isNaN(portInt)) { 46 | // named pipe 47 | return val; 48 | } 49 | 50 | if (portInt >= 0) { 51 | // port number 52 | return portInt; 53 | } 54 | 55 | return false; 56 | } 57 | 58 | /** 59 | * Event listener for HTTP server "error" event. 60 | */ 61 | function onError(error) { 62 | if (error.syscall !== 'listen') { 63 | throw error; 64 | } 65 | 66 | const bind = typeof port === 'string' 67 | ? 'Pipe ' + port 68 | : 'Port ' + port; 69 | 70 | // handle specific listen errors with friendly messages 71 | switch (error.code) { 72 | case 'EACCES': 73 | console.error(bind + ' requires elevated privileges'); 74 | process.exit(1); 75 | break; 76 | case 'EADDRINUSE': 77 | console.error(bind + ' is already in use'); 78 | process.exit(1); 79 | break; 80 | default: 81 | throw error; 82 | } 83 | } 84 | 85 | /** 86 | * Event listener for HTTP server "listening" event. 87 | */ 88 | function onListening() { 89 | const addr = server.address(); 90 | const bind = typeof addr === 'string' 91 | ? 'pipe ' + addr 92 | : 'port ' + addr.port; 93 | 94 | debug('Listening on ' + bind); 95 | console.log('Listening on ' + bind); 96 | } 97 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "callable-types": true, 7 | "class-name": true, 8 | "comment-format": [ 9 | true, 10 | "check-space" 11 | ], 12 | "curly": true, 13 | "eofline": true, 14 | "forin": true, 15 | "import-blacklist": [true, "rxjs"], 16 | "import-spacing": true, 17 | "indent": [ 18 | true, 19 | "spaces" 20 | ], 21 | "interface-over-type-literal": true, 22 | "label-position": true, 23 | "max-line-length": [ 24 | true, 25 | 140 26 | ], 27 | "member-access": false, 28 | "member-ordering": [ 29 | true, 30 | "static-before-instance", 31 | "variables-before-functions" 32 | ], 33 | "no-arg": true, 34 | "no-bitwise": true, 35 | "no-console": [ 36 | true, 37 | "debug", 38 | "info", 39 | "time", 40 | "timeEnd", 41 | "trace" 42 | ], 43 | "no-construct": true, 44 | "no-debugger": true, 45 | "no-duplicate-variable": true, 46 | "no-empty": false, 47 | "no-empty-interface": true, 48 | "no-eval": true, 49 | "no-inferrable-types": [true, "ignore-params"], 50 | "no-shadowed-variable": true, 51 | "no-string-literal": false, 52 | "no-string-throw": true, 53 | "no-switch-case-fall-through": true, 54 | "no-trailing-whitespace": true, 55 | "no-unused-expression": true, 56 | "no-use-before-declare": true, 57 | "no-var-keyword": true, 58 | "object-literal-sort-keys": false, 59 | "one-line": [ 60 | true, 61 | "check-open-brace", 62 | "check-catch", 63 | "check-else", 64 | "check-whitespace" 65 | ], 66 | "prefer-const": true, 67 | "quotemark": [ 68 | true, 69 | "single" 70 | ], 71 | "radix": true, 72 | "semicolon": [ 73 | "always" 74 | ], 75 | "triple-equals": [ 76 | true, 77 | "allow-null-check" 78 | ], 79 | "typedef-whitespace": [ 80 | true, 81 | { 82 | "call-signature": "nospace", 83 | "index-signature": "nospace", 84 | "parameter": "nospace", 85 | "property-declaration": "nospace", 86 | "variable-declaration": "nospace" 87 | } 88 | ], 89 | "typeof-compare": true, 90 | "unified-signatures": true, 91 | "variable-name": false, 92 | "whitespace": [ 93 | true, 94 | "check-branch", 95 | "check-decl", 96 | "check-operator", 97 | "check-separator", 98 | "check-type" 99 | ], 100 | 101 | "directive-selector": [true, "attribute", "app", "camelCase"], 102 | "component-selector": [true, "element", "app", "kebab-case"], 103 | "use-input-property-decorator": true, 104 | "use-output-property-decorator": true, 105 | "use-host-property-decorator": true, 106 | "no-input-rename": true, 107 | "no-output-rename": true, 108 | "use-life-cycle-interface": true, 109 | "use-pipe-transform-interface": true, 110 | "component-class-suffix": true, 111 | "directive-class-suffix": true, 112 | "no-access-missing-member": true, 113 | "templates-use-public": true, 114 | "invoke-injectable": true 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mean-start", 3 | "version": "0.0.1", 4 | "license": "MIT", 5 | "scripts": { 6 | "ng": "ng", 7 | "postinstall": "npm run build", 8 | "build": "rimraf dist && ng build --env=prod && tsc --project server/tsconfig.app.json", 9 | "start": "pm2 start dist/www.js --no-daemon", 10 | "build-start": "npm run build && npm start", 11 | "dev": "concurrently \"ng serve --progress false --proxy-config proxy.conf.json\" \"npm run api\" ", 12 | "api": "cd server && nodemon --ext ts --ignore *.spec.ts --exec ts-node www", 13 | "test": "concurrently \"ng test --progress false\" \"npm run test-server\" ", 14 | "test-ng": "ng test", 15 | "test-server": "cd server && nodemon --ext ts --exec jasmine-ts **/*[!.api].spec.ts", 16 | "test-api": "cd server && nodemon --ext ts --exec jasmine-ts **/*.api.spec.ts", 17 | "lint": "ng lint", 18 | "e2e": "ng e2e" 19 | }, 20 | "private": true, 21 | "dependencies": { 22 | "@angular/cli": "1.4.5", 23 | "@angular/common": "4.4.4", 24 | "@angular/compiler": "4.4.4", 25 | "@angular/compiler-cli": "4.4.4", 26 | "@angular/core": "4.4.4", 27 | "@angular/forms": "4.4.4", 28 | "@angular/http": "4.4.4", 29 | "@angular/platform-browser": "4.4.4", 30 | "@angular/platform-browser-dynamic": "4.4.4", 31 | "@angular/router": "4.4.4", 32 | "@ng-bootstrap/ng-bootstrap": "1.0.0-beta.5", 33 | "@types/aws-sdk": "0.0.42", 34 | "@types/body-parser": "0.0.33", 35 | "@types/cookie-parser": "^1.3.30", 36 | "@types/debug": "0.0.29", 37 | "@types/express": "^4.0.34", 38 | "@types/mongoose": "^4.7.1", 39 | "@types/morgan": "^1.7.32", 40 | "@types/multer": "^1.3.3", 41 | "@types/multer-s3": "^2.7.2", 42 | "@types/node": "~6.0.60", 43 | "@types/socket.io": "^1.4.27", 44 | "@types/socket.io-client": "^1.4.29", 45 | "aws-sdk": "^2.35.0", 46 | "axios": "^0.15.3", 47 | "body-parser": "^1.15.2", 48 | "bootstrap": "^4.1.3", 49 | "cookie-parser": "^1.4.3", 50 | "core-js": "^2.4.1", 51 | "debug": "^2.3.3", 52 | "express": "^4.14.0", 53 | "font-awesome": "^4.7.0", 54 | "mongoose": "^4.7.2", 55 | "morgan": "^1.7.0", 56 | "multer": "^1.3.0", 57 | "multer-s3": "^2.5.0", 58 | "ng2-file-upload": "^1.2.0", 59 | "pm2": "^2.4.0", 60 | "require-all": "^2.1.0", 61 | "rimraf": "^2.6.1", 62 | "rxjs": "^5.1.0", 63 | "socket.io": "^1.7.2", 64 | "typescript": "2.3.4", 65 | "zone.js": "^0.8.4" 66 | }, 67 | "devDependencies": { 68 | "@types/jasmine": "2.5.38", 69 | "@types/nock": "^8.2.1", 70 | "@types/supertest": "^2.0.0", 71 | "codelyzer": "~2.0.0", 72 | "concurrently": "^3.1.0", 73 | "jasmine": "2.5.2", 74 | "jasmine-core": "~2.5.2", 75 | "jasmine-spec-reporter": "~3.2.0", 76 | "jasmine-ts": "0.0.3", 77 | "karma": "~1.4.1", 78 | "karma-chrome-launcher": "~2.0.0", 79 | "karma-cli": "~1.0.1", 80 | "karma-jasmine": "~1.1.0", 81 | "karma-jasmine-html-reporter": "^0.2.2", 82 | "karma-coverage-istanbul-reporter": "^0.2.0", 83 | "nock": "^9.0.9", 84 | "node-mocks-http": "^1.6.1", 85 | "nodemon": "^1.11.0", 86 | "protractor": "~5.1.0", 87 | "supertest": "^3.0.0", 88 | "ts-node": "~2.0.0", 89 | "tslint": "~4.5.0" 90 | }, 91 | "engines": { 92 | "node": "6.11.1" 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /server/app.ts: -------------------------------------------------------------------------------- 1 | import * as express from 'express'; 2 | import * as bodyParser from 'body-parser'; 3 | import * as cookieParser from 'cookie-parser'; 4 | import * as path from 'path'; 5 | import * as logger from 'morgan'; 6 | import * as mongoose from 'mongoose'; 7 | require('mongoose').Promise = require('bluebird'); 8 | 9 | // import index from './routes/index'; 10 | // import users from './routes/users'; 11 | 12 | const app: express.Express = express(); 13 | 14 | // // view engine setup 15 | // app.set('views', path.join(__dirname, 'views')); 16 | // app.set('view engine', 'jade'); 17 | 18 | // uncomment after placing your favicon in /public 19 | // app.use(favicon(__dirname + '/public/favicon.ico')); 20 | app.use(logger('dev')); 21 | app.use(bodyParser.json()); 22 | app.use(bodyParser.urlencoded({ extended: false })); 23 | app.use(cookieParser()); 24 | app.use(express.static(path.join(__dirname, 'public'))); 25 | 26 | // app.use('/', index); 27 | // app.use('/users', users); 28 | 29 | // routes 30 | const routeModules = require('require-all')({ 31 | dirname: __dirname + '/routes', 32 | filter: (filename: string) => { 33 | filename = filename.toLowerCase(); 34 | if ((filename.endsWith('.ts') && !filename.endsWith('.spec.ts')) 35 | || (filename.endsWith('.js') && !filename.endsWith('.spec.js'))) { 36 | return filename.substr(0, filename.length - 3); 37 | } 38 | }, 39 | map: name => '/' + name 40 | }); 41 | function resolve(root: string, modules): void { 42 | for (const name of Object.keys(modules)) { 43 | if (!name.startsWith('/')) { 44 | return; 45 | } 46 | const module = modules[name]; 47 | if (module.default && module.default.route) { 48 | console.log(`Add router ${root + name}`); 49 | const router = module.default as express.Router; 50 | app.use(root, router); 51 | } else { 52 | resolve(root + name, module); 53 | } 54 | } 55 | } 56 | resolve('', routeModules); 57 | 58 | // Default to main page, angular route takes over 59 | app.use((req, res) => { 60 | res.sendFile(path.join(__dirname, 'public/index.html')); 61 | }); 62 | 63 | // // catch 404 and forward to error handler 64 | // app.use((req, res, next) => { 65 | // var err = new Error('Not Found'); 66 | // err['status'] = 404; 67 | // next(err); 68 | // }); 69 | 70 | // // error handlers 71 | 72 | // // development error handler 73 | // // will print stacktrace 74 | // if (app.get('env') === 'development') { 75 | 76 | // app.use((error: any, req, res, next) => { 77 | // res.status(error['status'] || 500); 78 | // res.render('error', { 79 | // message: error.message, 80 | // error 81 | // }); 82 | // }); 83 | // } 84 | 85 | // // production error handler 86 | // // no stacktraces leaked to user 87 | // app.use((error: any, req, res, next) => { 88 | // res.status(error['status'] || 500); 89 | // res.render('error', { 90 | // message: error.message, 91 | // error: {} 92 | // }); 93 | // return null; 94 | // }); 95 | 96 | export default app; 97 | 98 | // Connect to MongoDB 99 | mongoose.connect('mongodb://localhost/test', { useMongoClient: true}) 100 | .then( () => { 101 | console.log('MongoDB connected'); 102 | }) 103 | .catch( () => { 104 | console.log('unable to connect to mongoDB'); 105 | }); 106 | 107 | // mongoose.connect('mongodb://localhost/test'); 108 | // const db = mongoose.connection; 109 | // db.on('error', console.error.bind(console, 'connection error:')); 110 | // db.once('openUri', () => { 111 | // console.log('MongoDB connected'); 112 | // }); 113 | -------------------------------------------------------------------------------- /client/app/demo/upload-file/upload-file/upload-file.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
6 | 7 |
Select files
8 | 9 | 10 |
or
11 |
13 | Drop files here 14 |
15 | 16 |
17 | 18 |
19 | 20 |
Upload queue: {{ uploader?.queue?.length }}
21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 41 | 46 | 57 | 58 | 59 |
NameSizeProgressStatusActions
{{ item?.file?.name }}{{ item?.file?.size/1024/1024 | number:'.2' }} MB 37 |
38 |
39 |
40 |
42 | 43 | 44 | 45 | 47 | 50 | 53 | 56 |
60 | 61 |
62 |
63 | Queue progress: 64 |
65 |
66 |
67 |
68 | 71 | 74 | 77 |
78 | 79 |
80 | 81 |
82 | 83 |
84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This project is outdated, please checkout the new [mean-start-2](https://github.com/cj-wang/mean-start-2), full stack Angular + Nest, based on ngx-admin template. 2 | 3 | # MEAN Start 4 | 5 | **Angular + Angular CLI + Express + Mongoose + Socket.IO. All in TypeScript.** 6 | 7 | [DEMO](https://still-everglades-27346.herokuapp.com) 8 | 9 | ## Prerequisites 10 | 11 | * [Node.js and npm](https://docs.npmjs.com/getting-started/installing-node) 12 | * [MongoDB](https://docs.mongodb.com/manual/installation/) 13 | * [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) 14 | 15 | Install global dependencies: 16 | ```bash 17 | npm install -g typescript 18 | npm install -g @angular/cli 19 | ``` 20 | 21 | ## Create a new project based on the MEAN Start 22 | 23 | Clone this repo into new project folder (e.g., `my-proj`): 24 | ```bash 25 | git clone https://github.com/cj-wang/mean-start my-proj 26 | cd my-proj 27 | ``` 28 | 29 | ## Install npm packages 30 | 31 | Install the npm packages described in the `package.json`: 32 | ```bash 33 | yarn 34 | ``` 35 | or alternatively: 36 | ```bash 37 | npm install 38 | ``` 39 | 40 | ## Development server 41 | 42 | Start the dev server: 43 | ```bash 44 | npm run dev 45 | ``` 46 | 47 | Navigate to [http://localhost:4200/](http://localhost:4200/) for the app. 48 | Shut it down manually with `Ctrl-C`. 49 | 50 | The `npm run dev` script starts 2 servers concurrently: 51 | 52 | * **angular-cli dev server** - starts at `http://localhost:4200/`, serving the `angular` app. 53 | The `angular` app will automatically reload if you change any of the client source files. 54 | 55 | * **express server** - starts at `http://localhost:3000/`, serving the REST APIs. 56 | The `express` server will be automatically restarted by `nodemon` if you change any of the server source files. 57 | 58 | The `angular-cli` dev server is configured to proxy all API calls to `http://localhost:4200/api` to go to the `express` server `http://localhost:3000/api`, 59 | so that the whole app is available at [http://localhost:4200/](http://localhost:4200/). 60 | 61 | You're ready to write your application. 62 | 63 | ## Develop 64 | 65 | ### Model class 66 | 67 | Add `mongoose` model classes in `server/models/` directory, e.g. `server/models/user.ts`: 68 | ```TypeScript 69 | import * as mongoose from 'mongoose'; 70 | 71 | export interface IUser { 72 | email: string; 73 | password: string; 74 | name: string; 75 | }; 76 | 77 | const userSchema = new mongoose.Schema({ 78 | email: String, 79 | password: String, 80 | name: String 81 | }); 82 | 83 | interface IUserModel extends IUser, mongoose.Document { } 84 | 85 | const User = mongoose.model('User', userSchema); 86 | 87 | export default User; 88 | ``` 89 | 90 | Model classes in `server/models/` directory are exposed as REST APIs by default. 91 | E.g. with the `User` model added, below REST APIs are created automatically: 92 | * POST /api/users - Create a User 93 | * GET /api/users - Get all the users 94 | * GET /api/users/:user_id - Get a single user 95 | * PUT /api/users/:user_id - Update a user with new info 96 | * DELETE /api/users/:user_id - Delete a user 97 | 98 | >TODO: Role-based access control required. 99 | 100 | ### Custom API 101 | 102 | Add API modules in `server/routes/api/` directory, e.g. `server/routes/api/demo/test.ts`: 103 | ```TypeScript 104 | import { Router, Request, Response } from 'express'; 105 | 106 | export default Router() 107 | .get('/test', testHandler); 108 | 109 | export function testHandler(req: Request, res: Response) { 110 | res.send('Test API works'); 111 | }; 112 | ``` 113 | 114 | All the API modules must have a default export of type `express.Router`. 115 | They will be imported by `app.ts` and be added to the `express` app automatically. 116 | The root of the Routers correspond to the sub directories starting from `api/`, so the path of above API is `/api/demo/test`. 117 | 118 | The handler functions should also be exported for unit testing. 119 | 120 | >TODO: Role-based access control required. 121 | 122 | ### Socket.IO 123 | 124 | Add Socket.IO modules in `server/socket.io/` directory, e.g. `server/socket.io/chat.ts`: 125 | ```TypeScript 126 | import * as sio from 'socket.io'; 127 | 128 | const chatServer = sio({ 129 | path: '/socket.io/chat' 130 | }); 131 | 132 | chatServer.on('connection', (socket) => { 133 | console.log('a user connected'); 134 | socket.on('chat message', (msg) => { 135 | console.log('message: ' + msg); 136 | chatServer.emit('chat message', msg); 137 | }); 138 | socket.on('disconnect', () => { 139 | console.log('user disconnected'); 140 | }); 141 | }); 142 | 143 | export default chatServer; 144 | ``` 145 | 146 | All the Socket.IO modules must have a default export of type `SocketIO.Server`. 147 | They will be imported by `www.ts` and be attached to the `express` server automatically. 148 | 149 | ### Angular Code scaffolding 150 | 151 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive/pipe/service/class/module`. 152 | 153 | ## Build 154 | 155 | ```bash 156 | npm run build 157 | ``` 158 | The server compiled files will be stored in the `dist/` directory. 159 | The client build artifacts will be stored in the `dist/public/` directory. 160 | The `dist/` directory is the full distribution of the app. 161 | 162 | ## Running unit tests 163 | 164 | Run `npm test` to execute the unit tests for both the Angular app and the server app concurrently. 165 | You can also run `npm run test-ng` or `npm run test-server` to test the Angular app or the server app separately. 166 | 167 | ## Running server API integration tests 168 | 169 | Run `npm run test-api` to execute the server API integration tests. 170 | 171 | API integration test files must be named as `*.api.spec.ts` to be separated from unit test files. 172 | 173 | ## Running end-to-end tests 174 | 175 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 176 | Before running the tests make sure you are serving the app via `ng serve`. 177 | 178 | ## Deploying to AWS Elastic Beanstalk 179 | 180 | Prerequisites: 181 | * [AWS Account](https://console.aws.amazon.com/elasticbeanstalk) 182 | * [AWS EB CLI](http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/eb-cli3-install.html) 183 | 184 | Deploy the app to Elastic Beanstalk: 185 | ```bash 186 | eb init --platform node.js --region us-west-2 187 | eb create --instance_type t2.small --sample node-express-env 188 | eb deploy 189 | ``` 190 | 191 | To view your application in the web browser run: 192 | ```bash 193 | eb open 194 | ``` 195 | 196 | ## Deploying to Google App Engine 197 | 198 | Prerequisites: 199 | * [Create GAE Project](https://console.cloud.google.com/projectselector/appengine/create?lang=nodejs&st=true) 200 | * [Google Cloud SDK](https://cloud.google.com/sdk/docs/) 201 | 202 | Deploy the app to the App Engine flexible environment: 203 | ```bash 204 | gcloud app deploy 205 | ``` 206 | 207 | To view your application in the web browser run: 208 | ```bash 209 | gcloud app browse 210 | ``` 211 | 212 | ## Deploying to Heroku 213 | 214 | Prerequisites: 215 | * [Heroku Account](https://signup.heroku.com/signup/dc) 216 | * [Heroku CLI](https://cli.heroku.com/) 217 | 218 | Commit your changes to git, then deploy your app to Heroku: 219 | ```bash 220 | heroku create 221 | git push heroku master 222 | ``` 223 | 224 | To open the app in your browser, type: 225 | ```bash 226 | heroku open 227 | ``` 228 | 229 | ## Further help 230 | 231 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 232 | --------------------------------------------------------------------------------