├── 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 | Send
--------------------------------------------------------------------------------
/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 |
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 | Name
10 | Size
11 |
12 | Uploaded Date
13 | Actions
14 |
15 |
16 |
17 |
18 | {{ file.originalname }}
19 | {{ file.size/1024/1024 | number:'.2' }} MB
20 |
21 | {{file.uploadDate | date:'medium'}}
22 |
23 |
24 | Download
25 |
26 |
27 | Remove
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | Remove all
37 |
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 |
3 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/client/app/demo/form/hero-form.component.html:
--------------------------------------------------------------------------------
1 |
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 |
Edit
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 |
20 |
42 |
43 |
44 |
45 |
46 | Loading...
47 |
48 |
49 |
50 |
51 |
52 |
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 | Name
26 | Size
27 | Progress
28 | Status
29 | Actions
30 |
31 |
32 |
33 |
34 | {{ item?.file?.name }}
35 | {{ item?.file?.size/1024/1024 | number:'.2' }} MB
36 |
37 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | Upload
49 |
50 |
51 | Cancel
52 |
53 |
54 | Remove from queue
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | Queue progress:
64 |
67 |
68 |
69 | Upload all
70 |
71 |
72 | Cancel all
73 |
74 |
75 | Clear queue
76 |
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 |
--------------------------------------------------------------------------------