├── .gitignore
├── README.md
├── client
├── app
│ ├── app-routing.module.ts
│ ├── base.component.ts
│ ├── base.html
│ ├── base.module.ts
│ ├── components
│ │ ├── campgrounds
│ │ │ ├── campground.detail.component.html
│ │ │ ├── campground.detail.component.ts
│ │ │ ├── campground.form.component.html
│ │ │ ├── campground.form.component.ts
│ │ │ ├── campgrounds.component.css
│ │ │ ├── campgrounds.component.html
│ │ │ ├── campgrounds.component.ts
│ │ │ ├── comment.form.component.html
│ │ │ └── comment.form.component.ts
│ │ └── user
│ │ │ ├── profile.component.html
│ │ │ ├── profile.component.ts
│ │ │ ├── user.component.html
│ │ │ └── user.component.ts
│ ├── main.ts
│ ├── models
│ │ ├── campground.ts
│ │ ├── campgroundDetail.ts
│ │ └── comment.ts
│ └── services
│ │ ├── api.service.ts
│ │ ├── campgounds.service.ts
│ │ └── user.service.ts
├── index.html
├── polyfills.ts
└── systemjs.config.js
├── gulpfile.js
├── gulpfile.ts
├── package-lock.json
├── package.json
├── server
└── src
│ ├── Authentication.ts
│ ├── Passport.ts
│ ├── Server.ts
│ ├── bin
│ └── www
│ ├── database
│ ├── DatabaseSchema.ts
│ └── DatabaseService.ts
│ ├── model
│ ├── Campground.ts
│ ├── Comment.ts
│ └── User.ts
│ ├── repository
│ ├── BaseRepository.ts
│ ├── CampgroundRepository.ts
│ ├── CommentRepository.ts
│ └── UserRepository.ts
│ └── routes
│ ├── Campground.ts
│ ├── Comment.ts
│ └── User.ts
├── tsconfig.json
└── tslint.json
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/*
2 | *.iml
3 | node_modules/*
4 | dist/*
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Fullstack web application using Angular4, Bootstrap4, Primeng, Express, Mysql, Gulp and Passport.
2 |
3 | ## Introduction
4 | This project demonstrates the basic CRUD functions combining Angular4, Bootstrap4, Primeng, Express, Mysql, Gulp4 and Passport.
5 | It also uses Gulp for running task and TsLint. You can use this web application doing user signup/login, campground create/edit/delete,
6 | and comment create/edit/delete.
7 |
8 | 
9 |
10 | ## Prerequisites
11 | 1. The latest version of Nodejs need to be installed.
12 | 2. Docker MySQL5 container
13 |
14 | ### Docker MySQL container preparation
15 | 1. Run `docker pull mysql:5`
16 | 2. Run `docker volume create mysqldata`
17 | 3. Run `docker run --name mysql5 -p 3306:3306 -v mysqldata:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:5`
18 | 4. Run `docker run -it --link mysql5:mysql --rm mysql:5 sh -c 'exec mysql -h"$MYSQL_PORT_3306_TCP_ADDR" -P"$MYSQL_PORT_3306_TCP_PORT" -uroot -p"$MYSQL_ENV_MYSQL_ROOT_PASSWORD"'`
19 | 5. Setup database and user
20 | ```
21 | mysql> CREATE DATABASE yelpcamp;
22 | mysql> CREATE USER 'sa'@'172.17.0.1' IDENTIFIED BY '(IJN8uhb';
23 | mysql> GRANT ALL ON yelpcamp.* TO 'sa'@'172.17.0.1';
24 | mysql> FLUSH PRIVILEGES;
25 | ```
26 |
27 | 6. Check privileges `mysql> SHOW GRANTS FOR 'sa'@'172.17.0.1';`
28 | 7. If you want to rerun docker container, run `docker run mysql5`
29 |
30 | ## Api Document (from Express's view)
31 | ```
32 | 1. getAllCampgrounds (GET) http://localhost:8080/api/campground
33 | 2. createCampground (POST) http://localhost:8080/api/campground
34 | 3. getOneCampground (GET) http://localhost:8080/api/campground/:id
35 | 4. getOneCampground (GET) http://localhost:8080/api/campground/:id/edit
36 | 5. editCampground (PUT) http://localhost:8080/api/campground/:id/edit
37 | 5. deleteCampground (DELETE) http://localhost:8080/api/campground/:id
38 | 6. createComment (POST) http://localhost:8080/api/comment
39 | 7. getComment (GET) http://localhost:8080/api/comment/:comment_id/edit
40 | 8. editComment (PUT) http://localhost:8080/api/comment/:comment_id/edit
41 | 9. deleteComment (DELETE) http://localhost:8080/api/comment/:comment_id
42 | 10. signup (POST) http://localhost:8080/api/signup
43 | 11. login (POST) http://localhost:8080/api/login
44 | 12. logout (GET) http://localhost:8080/api/logout
45 | 13. profile (GET) http://localhost:8080/api/profile
46 | ```
47 |
48 | ### How do I get set up? ###
49 |
50 | * Clone the repo:
51 | ```
52 | git clone https://LaurenceHo@bitbucket.org/LaurenceHo/angular4-express-mysql.git
53 | ```
54 | or
55 | ```
56 | git clone https://github.com/LaurenceHo/angular4-express-mysql.git
57 | ```
58 |
59 | * Install npm package. Build and compile the dist folder:
60 | ```
61 | npm install
62 | npm run build
63 | ```
64 |
65 | * Launch the whole web application:
66 | ```
67 | npm run start-all
68 | ```
69 |
70 | * Visit in your browser: `http://localhost:8080`
71 |
72 | ### Reference
73 | This project is based on https://github.com/moizKachwala/Angular2-express-mongoose-gulp-node-typescript. I learnt a lot from this project.
74 |
75 | ### Existing major issues:
76 | 1. browserSync cannot reload the browser automatically when modifying client side code.
77 | 2. When user refreshes the browser, the login session would be gone, and user needs to login again.
78 | 3. Some UI issues did not solve yet:
79 | * When user do place comment, the UI does display comment properly
80 |
--------------------------------------------------------------------------------
/client/app/app-routing.module.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by Laurence Ho on 07-02-2017.
3 | */
4 | import { NgModule } from '@angular/core';
5 | import { RouterModule, Routes } from '@angular/router';
6 |
7 | import { CampgroundDetailComponent } from './components/campgrounds/campground.detail.component';
8 | import { CampgroundFormComponent } from './components/campgrounds/campground.form.component';
9 | import { CampgroundsComponent } from './components/campgrounds/campgrounds.component';
10 | import { CommentFormComponent } from './components/campgrounds/comment.form.component';
11 | import { ProfileComponent } from './components/user/profile.component';
12 | import { UserComponent } from './components/user/user.component';
13 |
14 | const appRoutes: Routes = [
15 | {
16 | path: '',
17 | pathMatch: 'full',
18 | redirectTo: 'campground'
19 | },
20 | {
21 | path: 'campground',
22 | component: CampgroundsComponent
23 | },
24 | {
25 | path: 'campground/detail/:id',
26 | component: CampgroundDetailComponent
27 | },
28 | {
29 | path: 'campground/new',
30 | component: CampgroundFormComponent
31 | },
32 | {
33 | path: 'campground/detail/:id/edit',
34 | component: CampgroundFormComponent
35 | },
36 | {
37 | path: 'campground/detail/:id/comment/new',
38 | component: CommentFormComponent
39 | },
40 | {
41 | path: 'campground/detail/:id/comment/:comment_id/edit',
42 | component: CommentFormComponent
43 | },
44 | {
45 | path: 'login',
46 | component: UserComponent
47 | },
48 | {
49 | path: 'signup',
50 | component: UserComponent
51 | },
52 | {
53 | path: 'logout',
54 | component: UserComponent
55 | },
56 | {
57 | path: 'profile',
58 | component: ProfileComponent
59 | },
60 | {
61 | path: '**',
62 | component: CampgroundsComponent
63 | }
64 | ];
65 |
66 | @NgModule({
67 | imports: [ RouterModule.forRoot(appRoutes) ],
68 | exports: [ RouterModule ]
69 | })
70 |
71 | export class AppRoutingModule {
72 | }
73 |
--------------------------------------------------------------------------------
/client/app/base.component.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by Laurence Ho on 07-02-2017.
3 | */
4 |
5 | import { Component, OnInit } from '@angular/core';
6 | import { NavigationEnd, Router } from '@angular/router';
7 | import 'rxjs/add/operator/filter';
8 | import { UserService } from './services/user.service';
9 |
10 | @Component({
11 | selector: 'my-app',
12 | templateUrl: './app/base.html'
13 | })
14 |
15 | export class BaseComponent implements OnInit {
16 | title = 'YelpCamp';
17 | userdata: any;
18 |
19 | constructor(private userService: UserService, private router: Router) {
20 | }
21 |
22 | ngOnInit() {
23 | this.router.events.filter(event => event instanceof NavigationEnd)
24 | .subscribe(event => {
25 | this.userService.getProfile().subscribe(data => {
26 | if (data) {
27 | this.userdata = this.userService.getUserData();
28 | } else {
29 | this.userService.flush();
30 | this.userdata = null;
31 | }
32 | });
33 | }
34 | );
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/client/app/base.html:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/client/app/base.module.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by Laurence Ho on 07-02-2017.
3 | */
4 |
5 | import { HashLocationStrategy, LocationStrategy } from '@angular/common';
6 | import { NgModule } from '@angular/core';
7 | import { FormsModule } from '@angular/forms';
8 | import { HttpModule } from '@angular/http';
9 | import { BrowserModule } from '@angular/platform-browser';
10 | import { ButtonModule } from 'primeng/primeng';
11 |
12 | import { AppRoutingModule } from './app-routing.module';
13 | import { BaseComponent } from './base.component';
14 |
15 | import { ApiService } from './services/api.service';
16 | import { CampgroundService } from './services/campgounds.service';
17 | import { UserService } from './services/user.service';
18 |
19 | import { CampgroundDetailComponent } from './components/campgrounds/campground.detail.component';
20 | import { CampgroundFormComponent } from './components/campgrounds/campground.form.component';
21 | import { CampgroundsComponent } from './components/campgrounds/campgrounds.component';
22 | import { CommentFormComponent } from './components/campgrounds/comment.form.component';
23 | import { ProfileComponent } from './components/user/profile.component';
24 | import { UserComponent } from './components/user/user.component';
25 |
26 | @NgModule({
27 | imports: [
28 | // Angular Module
29 | BrowserModule,
30 | HttpModule,
31 | FormsModule,
32 | AppRoutingModule,
33 | // Primeng Module
34 | ButtonModule,
35 | ],
36 | declarations: [
37 | BaseComponent,
38 | CampgroundsComponent,
39 | CampgroundDetailComponent,
40 | CampgroundFormComponent,
41 | CommentFormComponent,
42 | UserComponent,
43 | ProfileComponent
44 | ],
45 | providers: [
46 | ApiService,
47 | CampgroundService,
48 | UserService,
49 | {provide: LocationStrategy, useClass: HashLocationStrategy}
50 | ],
51 | bootstrap: [ BaseComponent ]
52 | })
53 | export class BaseModule {
54 | }
55 |
--------------------------------------------------------------------------------
/client/app/components/campgrounds/campground.detail.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
YelpCamp
5 |
6 | - Campground Info
7 | - Other Info
8 |
9 |
10 |
11 |
12 |

13 |
14 |
${{campDetail?.campground?.price ? campDetail?.campground?.price :
15 | 'Unknown'}}NZD/night
16 |
17 | {{campDetail?.campground?.name}}
18 |
19 |
20 | {{campDetail?.campground?.description}}
21 |
22 |
Submitted By {{campDetail?.campground?.username}}
23 |
24 |
29 |
34 |
35 |
36 |
37 |
38 |
Comments
39 |
40 |
45 |
46 |
47 |
50 |
51 |
52 |
53 |
54 |
{{comment.username}}
55 |
56 | {{comment.text}}
57 |
58 |
59 |
64 |
69 |
70 |
71 |
72 |
73 |
Go Back
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/client/app/components/campgrounds/campground.detail.component.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by Laurence Ho on 08-02-2017.
3 | */
4 |
5 | import { Component, OnInit } from '@angular/core';
6 | import { ActivatedRoute, Params, Router } from '@angular/router';
7 | import 'rxjs/add/operator/switchMap';
8 |
9 | import * as _ from 'lodash';
10 | import { CampgroundDetail } from '../../models/campgroundDetail';
11 | import { Comment } from '../../models/comment';
12 | import { CampgroundService } from '../../services/campgounds.service';
13 | import { UserService } from '../../services/user.service';
14 |
15 | @Component({
16 | selector: 'campDetail',
17 | templateUrl: './app/components/campgrounds/campground.detail.component.html',
18 | styleUrls: [ './app/components/campgrounds/campgrounds.component.css' ]
19 | })
20 |
21 | export class CampgroundDetailComponent implements OnInit {
22 | selectedComment: Comment;
23 |
24 | campDetail: CampgroundDetail = new CampgroundDetail();
25 | userdata: any;
26 |
27 | constructor(private campgroundService: CampgroundService,
28 | private userService: UserService,
29 | private route: ActivatedRoute,
30 | private router: Router) {
31 | }
32 |
33 | ngOnInit() {
34 | this.route.params
35 | .switchMap((params: Params) => this.campgroundService.getCampgroundDetail(params.id))
36 | .subscribe(data => this.campDetail = data);
37 | this.userdata = this.userService.getUserData();
38 | }
39 |
40 | doLogin() {
41 | this.router.navigateByUrl('/login');
42 | }
43 |
44 | doEditCampground(id: number) {
45 | this.router.navigateByUrl('/campground/detail/' + id + '/edit');
46 | }
47 |
48 | doDeleteCampground(id: number) {
49 | if (this.route.snapshot.url[ 0 ].path === 'campground') {
50 | this.campgroundService.deleteCampground(id).subscribe(data => {
51 | if (data.message === 'OK') {
52 | this.router.navigate([ '/campground' ]);
53 | }
54 | });
55 | }
56 | }
57 |
58 | doEditComment(comment: Comment) {
59 | this.selectedComment = comment;
60 | }
61 |
62 | doDeleteComment(comment_id: number) {
63 | this.campgroundService.deleteComment(comment_id)
64 | .subscribe(data => {
65 | if (data.message === 'OK') {
66 | _.remove(this.campDetail.comments, comment => {
67 | return comment.id === comment_id;
68 | });
69 | }
70 | });
71 | }
72 |
73 | updateUI(comment: Comment) {
74 | // FIXME let tempComment = comment[ 'comment' ];
75 | this.campDetail.comments.push(comment);
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/client/app/components/campgrounds/campground.form.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Create a new campground
5 |
7 | Edit "{{campground.name}}"
8 |
9 |
10 |
41 |
42 |
--------------------------------------------------------------------------------
/client/app/components/campgrounds/campground.form.component.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by laurence-ho on 2/07/17.
3 | */
4 | import { Component, OnInit } from '@angular/core';
5 | import { ActivatedRoute, Router } from '@angular/router';
6 | import { Campground } from '../../models/campground';
7 | import { CampgroundService } from '../../services/campgounds.service';
8 | import { UserService } from '../../services/user.service';
9 |
10 | @Component({
11 | selector: 'campDetail',
12 | templateUrl: './app/components/campgrounds/campground.form.component.html',
13 | styleUrls: [ './app/components/campgrounds/campgrounds.component.css' ]
14 | })
15 |
16 | export class CampgroundFormComponent implements OnInit {
17 | userdata: any;
18 | campground: Campground = new Campground();
19 |
20 | constructor(private campgroundService: CampgroundService,
21 | private userService: UserService,
22 | private route: ActivatedRoute,
23 | private router: Router) {
24 | }
25 |
26 | ngOnInit() {
27 | this.userdata = this.userService.getUserData();
28 |
29 | if (!this.userdata) {
30 | this.router.navigate([ '/login' ]);
31 | }
32 |
33 | if (this.route.snapshot.url[ 1 ].path !== 'new' && this.route.snapshot.url[ 3 ].path === 'edit') {
34 | this.campgroundService.getCampground(Number(this.route.snapshot.url[ 2 ].path))
35 | .subscribe(data => this.campground = data.campground);
36 | }
37 | }
38 |
39 | doSubmit() {
40 | if (this.route.snapshot.url[ 0 ].path === 'campground') {
41 | if (this.route.snapshot.url[ 1 ].path === 'new') {
42 | this.campground.user_id = this.userdata.id;
43 | this.campground.username = this.userdata.username;
44 |
45 | this.campgroundService.createCampground(this.campground)
46 | .subscribe(data => {
47 | if (data.message === 'OK') {
48 | this.router.navigate([ '/campground/detail', data.campground_id ]);
49 | }
50 | });
51 | } else if (this.route.snapshot.url[ 1 ].path !== 'new' && this.route.snapshot.url[ 3 ].path === 'edit') {
52 | this.campgroundService.editCampground(this.campground)
53 | .subscribe(data => {
54 | if (data.message === 'OK') {
55 | this.router.navigate([ '/campground/detail', this.campground.id ]);
56 | }
57 | });
58 | }
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/client/app/components/campgrounds/campgrounds.component.css:
--------------------------------------------------------------------------------
1 | .campground-item {
2 | display: flex;
3 | flex-wrap: wrap;
4 | }
5 |
6 | .thumbnail {
7 | border: lightgray 0.05rem solid;
8 | border-radius: 0.3rem;
9 | position: relative;
10 | padding: 0.4rem;
11 | margin-bottom: 1.8rem;
12 | }
13 |
14 | .thumbnail img {
15 | width: 100%;
16 | }
17 |
18 | .thumbnail .caption-full {
19 | padding: 2rem;
20 | }
21 |
22 | #delete {
23 | display: inline;
24 | }
25 |
--------------------------------------------------------------------------------
/client/app/components/campgrounds/campgrounds.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Welcome to YelpCamp
5 |
View our hand-picked campgrounds from all over the world
6 |
7 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |

20 |
21 |
{{camp.name}}
22 |
Price: ${{camp.price}}NZD/night
23 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/client/app/components/campgrounds/campgrounds.component.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by Laurence Ho on 07-02-2017.
3 | */
4 | import { Component, OnInit } from '@angular/core';
5 | import { Router } from '@angular/router';
6 |
7 | import { Campground } from '../../models/campground';
8 | import { CampgroundService } from '../../services/campgounds.service';
9 | import { UserService } from '../../services/user.service';
10 |
11 | @Component({
12 | selector: 'camps',
13 | templateUrl: './app/components/campgrounds/campgrounds.component.html',
14 | styleUrls: [ './app/components/campgrounds/campgrounds.component.css' ]
15 | })
16 |
17 | export class CampgroundsComponent implements OnInit {
18 | camps: Campground[];
19 | userdata: any;
20 |
21 | constructor(private router: Router, private campService: CampgroundService, private userService: UserService) {
22 | }
23 |
24 | ngOnInit() {
25 | this.getCampgrounds();
26 | this.userdata = this.userService.getUserData();
27 | }
28 |
29 | getCampgrounds() {
30 | this.campService.getCampgrounds().subscribe(camps => this.camps = camps);
31 | }
32 |
33 | moreInfo(campId: number) {
34 | this.router.navigateByUrl('/campground/detail/' + campId);
35 | }
36 |
37 | addNewCampground() {
38 | this.router.navigateByUrl('/campground/new');
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/client/app/components/campgrounds/comment.form.component.html:
--------------------------------------------------------------------------------
1 | Leave your comment:
2 |
3 |
14 |
15 |
--------------------------------------------------------------------------------
/client/app/components/campgrounds/comment.form.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
2 | import { NgForm } from '@angular/forms';
3 | import { ActivatedRoute, Router } from '@angular/router';
4 | import { Comment } from '../../models/comment';
5 | import { CampgroundService } from '../../services/campgounds.service';
6 | import { UserService } from '../../services/user.service';
7 |
8 | /**
9 | * Created by laurence-ho on 3/07/17.
10 | */
11 |
12 | @Component({
13 | selector: 'app-comment',
14 | templateUrl: './app/components/campgrounds/comment.form.component.html',
15 | styleUrls: [ './app/components/campgrounds/campgrounds.component.css' ]
16 | })
17 |
18 | export class CommentFormComponent implements OnInit {
19 | @ViewChild('f') commentForm: NgForm;
20 | @Input() comment: Comment;
21 | @Output() insertedComment = new EventEmitter();
22 |
23 | userdata: any;
24 | campground_id: number;
25 |
26 | constructor(private campgroundService: CampgroundService,
27 | private userService: UserService,
28 | private route: ActivatedRoute,
29 | private router: Router) {
30 | }
31 |
32 | ngOnInit() {
33 | this.userdata = this.userService.getUserData();
34 |
35 | if (!this.userdata) {
36 | this.router.navigate([ '/login' ]);
37 | }
38 |
39 | if (!isNaN(Number(this.route.snapshot.url[ 2 ].path))) {
40 | this.campground_id = Number(this.route.snapshot.url[ 2 ].path);
41 | }
42 |
43 | if (!this.comment) {
44 | this.comment = new Comment();
45 | }
46 | }
47 |
48 | doSubmit() {
49 | this.comment.user_id = this.userdata.id;
50 | this.comment.username = this.userdata.username;
51 | this.comment.campground_id = this.campground_id;
52 |
53 | if (this.comment.text) {
54 | if (!this.comment.id) {
55 | this.campgroundService.createComment(this.comment)
56 | // FIXME
57 | // .then(data => {
58 | // this.campgroundService.getComment(data.comment_id)
59 | // .subscribe(comment => this.insertedComment.emit(comment));
60 | // })
61 | .subscribe(data => console.log(data));
62 | } else {
63 | this.campgroundService.editComment(this.comment)
64 | .subscribe(data => console.log(data)); // FIXME
65 | }
66 | }
67 | this.commentForm.reset();
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/client/app/components/user/profile.component.html:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
13 |
Local
14 |
15 | id:
16 | {{id}}
17 |
18 | username:
19 | {{username}}
20 |
21 | password:
22 | {{password}}
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/client/app/components/user/profile.component.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by laurence-ho on 29/06/17.
3 | */
4 |
5 | import { Component, OnInit } from '@angular/core';
6 | import { Router } from '@angular/router';
7 | import { UserService } from '../../services/user.service';
8 |
9 | @Component({
10 | selector: 'login',
11 | templateUrl: './app/components/user/profile.component.html'
12 | })
13 |
14 | export class ProfileComponent implements OnInit {
15 | id: number = 0;
16 | username: string = '';
17 | password: string = '';
18 |
19 | constructor(private userService: UserService, private router: Router) {
20 | }
21 |
22 | ngOnInit() {
23 | this.userService.getProfile()
24 | .subscribe(data => {
25 | this.id = data.id;
26 | this.username = data.username;
27 | this.password = data.password;
28 | });
29 | }
30 |
31 | doLogout() {
32 | this.router.navigateByUrl('/logout');
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/client/app/components/user/user.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Login
5 |
6 | Sign Up
7 |
8 |
9 |
0" class="alert alert-danger" role="alert">
10 | {{message}}
11 |
12 |
13 |
14 | Signup account successfully, please login.
15 |
16 |
17 |
41 |
42 |
43 |
44 |
Need an account? Sign Up
45 |
Already have an account? Login
46 |
47 |
Or go home
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/client/app/components/user/user.component.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by laurence-ho on 7/06/17.
3 | */
4 |
5 | import { Component, OnInit } from '@angular/core';
6 | import { ActivatedRoute, Router } from '@angular/router';
7 | import { UserService } from '../../services/user.service';
8 |
9 | @Component({
10 | selector: 'login',
11 | templateUrl: './app/components/user/user.component.html'
12 | })
13 |
14 | export class UserComponent implements OnInit {
15 | username: string = '';
16 | password: string = '';
17 | remember: boolean = false;
18 |
19 | error: boolean = false;
20 | signupSuccessful: boolean = false;
21 |
22 | message: string = '';
23 |
24 | constructor(private route: ActivatedRoute, private router: Router, private userService: UserService) {
25 |
26 | }
27 |
28 | ngOnInit() {
29 | if (this.route.snapshot.url[ 0 ].path === 'login') {
30 | if (this.userService.getUserData()) {
31 | this.router.navigate([ '/login' ]);
32 | }
33 | }
34 |
35 | if (this.route.snapshot.url[ 0 ].path === 'logout') {
36 | this.doLogout();
37 | }
38 | }
39 |
40 | doSomething() {
41 | if (this.route.snapshot.url[ 0 ].path === 'login') {
42 | this.doLogin();
43 | } else {
44 | this.doSignup();
45 | }
46 | }
47 |
48 | doLogin() {
49 | this.userService.doLogin(this.username, this.password, this.remember)
50 | .subscribe(data => {
51 | this.userService.setUserData(data);
52 | this.router.navigate([ '/campground' ]);
53 | });
54 | }
55 |
56 | doSignup() {
57 | this.userService.doSignup(this.username, this.password)
58 | .subscribe(data => {
59 | this.signupSuccessful = true;
60 | this.router.navigate([ '/profile' ]);
61 | });
62 | }
63 |
64 | doLogout() {
65 | this.userService.doLogout();
66 | this.router.navigate([ '/campground' ]);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/client/app/main.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by Laurence Ho on 07-02-2017.
3 | */
4 |
5 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
6 | import { BaseModule } from './base.module';
7 |
8 | platformBrowserDynamic().bootstrapModule(BaseModule).catch(err => console.log(err));
9 |
--------------------------------------------------------------------------------
/client/app/models/campground.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by Laurence Ho on 07-02-2017.
3 | */
4 |
5 | export class Campground {
6 | id: number;
7 | name: string;
8 | image: string;
9 | description: string;
10 | price: number;
11 | username: string;
12 | user_id: number;
13 | }
14 |
--------------------------------------------------------------------------------
/client/app/models/campgroundDetail.ts:
--------------------------------------------------------------------------------
1 | import { Campground } from './campground';
2 |
3 | export class CampgroundDetail {
4 | campground: Campground;
5 | comments: any[];
6 | }
7 |
--------------------------------------------------------------------------------
/client/app/models/comment.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by Laurence Ho on 08-02-2017.
3 | */
4 |
5 | export class Comment {
6 | id: number;
7 | text: string;
8 | campground_id: number;
9 | username: string;
10 | user_id: number;
11 | }
12 |
--------------------------------------------------------------------------------
/client/app/services/api.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Headers, Http, RequestOptionsArgs, Response, URLSearchParams } from '@angular/http';
3 | import { Router } from '@angular/router';
4 | import * as _ from 'lodash';
5 | import 'rxjs/add/observable/throw';
6 | import { Observable } from 'rxjs/Observable';
7 | import 'rxjs/Rx';
8 |
9 | @Injectable()
10 | export class ApiService {
11 |
12 | constructor(private http: Http, private router: Router) {
13 | }
14 |
15 | perform(method: string, urlPath: string, body: any, params: any, formParams: any): Observable {
16 | const requestOptions: RequestOptionsArgs = {};
17 | const headers = new Headers({Accept: '*/*'});
18 |
19 | if (!_.isEmpty(formParams)) {
20 | // Form submit
21 | headers.append('Content-Type', 'application/x-www-form-urlencoded');
22 |
23 | const formBody = new URLSearchParams();
24 |
25 | for (const formParam of Object.keys(formParams)) {
26 | formBody.append(formParam, formParams[ formParam ]);
27 | }
28 |
29 | requestOptions.body = formBody.toString();
30 | } else {
31 | // JSON content
32 | headers.append('Content-Type', 'application/json');
33 |
34 | if (body) {
35 | requestOptions.body = JSON.stringify(body);
36 | }
37 | }
38 |
39 | const searchParams = new URLSearchParams();
40 |
41 | if (!_.isEmpty(params)) {
42 | for (const param of Object.keys(params)) {
43 | searchParams.append(param, params[ param ]);
44 | }
45 | }
46 |
47 | requestOptions.search = searchParams;
48 | requestOptions.method = method.toLowerCase();
49 | requestOptions.headers = headers;
50 |
51 | const methodName = method.toLowerCase();
52 |
53 | if (!this.http[ methodName ]) {
54 | throw new Error(`Unknown HTTP method: ${method}`);
55 | }
56 |
57 | return this.http.request(urlPath, requestOptions)
58 | .catch((error) => {
59 | // FIXME, need to handle error
60 | return Observable.throw(error);
61 | })
62 | .map(this.getJson);
63 | }
64 |
65 | private getJson(response: Response) {
66 | let formattedResponse;
67 |
68 | try {
69 | formattedResponse = response.json();
70 | } catch (error) {
71 | formattedResponse = response.text();
72 | }
73 | return formattedResponse;
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/client/app/services/campgounds.service.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by Laurence Ho on 07-02-2017.
3 | */
4 |
5 | import { Injectable } from '@angular/core';
6 | import { Observable } from 'rxjs/Observable';
7 | import 'rxjs/Rx';
8 | import { Campground } from '../models/campground';
9 | import { CampgroundDetail } from '../models/campgroundDetail';
10 | import { Comment } from '../models/comment';
11 | import { ApiService } from './api.service';
12 |
13 | @Injectable()
14 | export class CampgroundService {
15 |
16 | private campgroundsUrl = 'api/campground/'; // URL to web api
17 | private commentUrl = 'api/comment/';
18 |
19 | constructor(private apiService: ApiService) {
20 | }
21 |
22 | getCampgrounds(): Observable {
23 | const _params: any = {};
24 | const _formParams: any = {};
25 | const _bodyData: any = {};
26 |
27 | return this.apiService.perform('get', this.campgroundsUrl, _bodyData, _params, _formParams);
28 | }
29 |
30 | getCampgroundDetail(id: number): Observable {
31 | const _params: any = {};
32 | const _formParams: any = {};
33 | const _bodyData: any = {};
34 |
35 | return this.apiService.perform('get', this.campgroundsUrl + id, _bodyData, _params, _formParams);
36 | }
37 |
38 | getCampground(id: number): Observable {
39 | const _params: any = {};
40 | const _formParams: any = {};
41 | const _bodyData: any = {};
42 | const url = this.campgroundsUrl + id + '/edit';
43 |
44 | return this.apiService.perform('get', url, _bodyData, _params, _formParams);
45 | }
46 |
47 | editCampground(campground: Campground): Observable {
48 | const _params: any = {};
49 | const _formParams: any = {};
50 | const url = this.campgroundsUrl + campground.id + '/edit';
51 |
52 | return this.apiService.perform('put', url, campground, _params, _formParams);
53 | }
54 |
55 | createCampground(campground: Campground): Observable {
56 | const _params: any = {};
57 | const _formParams: any = {};
58 |
59 | return this.apiService.perform('post', this.campgroundsUrl, campground, _params, _formParams);
60 | }
61 |
62 | deleteCampground(id: number): Observable {
63 | const _params: any = {};
64 | const _formParams: any = {};
65 | const _bodyData: any = {};
66 |
67 | return this.apiService.perform('delete', this.campgroundsUrl + id, _bodyData, _params, _formParams);
68 | }
69 |
70 | getComment(id: number): Observable {
71 | const _params: any = {};
72 | const _formParams: any = {};
73 | const _bodyData: any = {};
74 | const url = this.commentUrl + id + '/edit';
75 |
76 | return this.apiService.perform('get', url, _bodyData, _params, _formParams);
77 | }
78 |
79 | editComment(comment: Comment): Observable {
80 | const _params: any = {};
81 | const _formParams: any = {};
82 | const url = this.commentUrl + comment.id + '/edit';
83 |
84 | return this.apiService.perform('put', url, comment, _params, _formParams);
85 | }
86 |
87 | createComment(comment: Comment): Observable {
88 | const _params: any = {};
89 | const _formParams: any = {};
90 |
91 | return this.apiService.perform('post', this.commentUrl, comment, _params, _formParams);
92 | }
93 |
94 | deleteComment(id: number): Observable {
95 | const _params: any = {};
96 | const _formParams: any = {};
97 | const _bodyData: any = {};
98 |
99 | return this.apiService.perform('delete', this.commentUrl + id, _bodyData, _params, _formParams);
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/client/app/services/user.service.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by laurence-ho on 7/06/17.
3 | */
4 |
5 | import { Injectable } from '@angular/core';
6 | import { Observable } from 'rxjs/Observable';
7 | import 'rxjs/Rx';
8 | import { ApiService } from './api.service';
9 |
10 | export interface InternalStateType {
11 | [ key: string ]: any;
12 | }
13 |
14 | @Injectable()
15 | export class UserService {
16 | _state: InternalStateType = {};
17 | USER_DATA_KEY: string = 'user_data';
18 |
19 | private loginUrl = 'api/login';
20 | private signupUrl = 'api/signup';
21 | private logoutUrl = 'api/logout';
22 | private profileUrl = 'api/profile';
23 |
24 | constructor(private apiService: ApiService) {
25 | const userdata = window.localStorage.getItem(this.USER_DATA_KEY);
26 | if (userdata) {
27 | try {
28 | this.setUserData(JSON.parse(userdata));
29 | } catch (error) {
30 | console.error('error parsing user data json');
31 | }
32 | }
33 | }
34 |
35 | get state() {
36 | return this._state = this._clone(this._state);
37 | }
38 |
39 | set state(value) {
40 | throw new Error('do not mutate the `.state` directly');
41 | }
42 |
43 | flush() {
44 | this._state = {};
45 | window.localStorage.removeItem(this.USER_DATA_KEY);
46 | }
47 |
48 | private _clone(object: InternalStateType) {
49 | return JSON.parse(JSON.stringify(object));
50 | }
51 |
52 | setUserData(data: any) {
53 | if (data) {
54 | window.localStorage.setItem(this.USER_DATA_KEY, data);
55 | return this._state.user = data;
56 | }
57 | }
58 |
59 | getUserData(): any {
60 | const state = this.state;
61 | return state.hasOwnProperty('user') ? state.user : undefined;
62 | }
63 |
64 | doLogin(username: string, password: string, remember: boolean): Observable {
65 | const _params: any = {};
66 | const _bodyData: any = {};
67 | const _formParams: any = {};
68 |
69 | if (username !== undefined) {
70 | _formParams.username = username;
71 | }
72 |
73 | if (password !== undefined) {
74 | _formParams.password = password;
75 | }
76 |
77 | if (remember !== undefined && remember === false) {
78 | _formParams.remember = remember;
79 | }
80 |
81 | return this.apiService.perform('post', this.loginUrl, _bodyData, _params, _formParams);
82 | }
83 |
84 | doSignup(username: string, password: string): Observable {
85 | const _params: any = {};
86 | const _bodyData: any = {};
87 | const _formParams: any = {};
88 |
89 | if (username !== undefined) {
90 | _formParams.username = username;
91 | }
92 |
93 | if (password !== undefined) {
94 | _formParams.password = password;
95 | }
96 |
97 | return this.apiService.perform('post', this.signupUrl, _bodyData, _params, _formParams);
98 | }
99 |
100 | doLogout() {
101 | this.flush();
102 |
103 | const _params: any = {};
104 | const _formParams: any = {};
105 | const _bodyData: any = {};
106 |
107 | return this.apiService.perform('get', this.logoutUrl, _bodyData, _params, _formParams);
108 | }
109 |
110 | getProfile() {
111 | const _params: any = {};
112 | const _formParams: any = {};
113 | const _bodyData: any = {};
114 |
115 | return this.apiService.perform('get', this.profileUrl, _bodyData, _params, _formParams);
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | YelpCamp
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | Loading...
39 |
40 |
41 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/client/polyfills.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by laurence-ho on 29/06/17.
3 | */
4 | /**
5 | * This file includes polyfills needed by Angular and is loaded before the app.
6 | * You can add your own extra polyfills to this file.
7 | *
8 | * This file is divided into 2 sections:
9 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
10 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
11 | * file.
12 | *
13 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
14 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
15 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
16 | *
17 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
18 | */
19 |
20 | /***************************************************************************************************
21 | * BROWSER POLYFILLS
22 | */
23 |
24 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/
25 | // import 'core-js/es6/symbol';
26 | // import 'core-js/es6/object';
27 | // import 'core-js/es6/function';
28 | // import 'core-js/es6/parse-int';
29 | // import 'core-js/es6/parse-float';
30 | // import 'core-js/es6/number';
31 | // import 'core-js/es6/math';
32 | // import 'core-js/es6/string';
33 | // import 'core-js/es6/date';
34 | // import 'core-js/es6/array';
35 | // import 'core-js/es6/regexp';
36 | // import 'core-js/es6/map';
37 | // import 'core-js/es6/set';
38 |
39 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */
40 | // import 'classlist.js'; // Run `npm install --save classlist.js`.
41 |
42 | /** IE10 and IE11 requires the following to support `@angular/animation`. */
43 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`.
44 |
45 |
46 | /** Evergreen browsers require these. **/
47 | import 'core-js/es6/reflect';
48 | import 'core-js/es7/reflect';
49 |
50 |
51 | /** ALL Firefox browsers require the following to support `@angular/animation`. **/
52 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`.
53 |
54 |
55 | /***************************************************************************************************
56 | * Zone JS is required by Angular itself.
57 | */
58 | import 'zone.js/dist/zone'; // Included with Angular CLI.
59 |
60 |
61 | /***************************************************************************************************
62 | * APPLICATION IMPORTS
63 | */
64 |
65 | /**
66 | * Date, currency, decimal and percent pipes.
67 | * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10
68 | */
69 | // import 'intl'; // Run `npm install --save intl`.
70 |
--------------------------------------------------------------------------------
/client/systemjs.config.js:
--------------------------------------------------------------------------------
1 | var isPublic = typeof window != "undefined";
2 |
3 | (function (global) {
4 | // map tells the System loader where to look for things
5 | var map = {
6 | 'app': 'app', // 'dist',
7 | '@angular': (isPublic) ? '@angular' : 'node_modules/@angular',
8 | 'lodash': 'http://localhost:8080/libs/lodash/lodash.min.js',
9 | 'primeng': 'http://localhost:8080/libs/primeng/',
10 | 'rxjs': (isPublic) ? 'rxjs' : 'node_modules/rxjs'
11 | };
12 | // packages tells the System loader how to load when no filename and/or no extension
13 | var packages = {
14 | 'app': {main: 'main.js', defaultExtension: 'js'},
15 | 'rxjs': {defaultExtension: 'js'},
16 | 'lodash': {defaultExtension: 'js'},
17 | 'primeng': {defaultExtension: 'js'}
18 | };
19 | var ngPackageNames = [
20 | 'animations',
21 | 'common',
22 | 'compiler',
23 | 'core',
24 | 'forms',
25 | 'http',
26 | 'platform-browser',
27 | 'platform-browser-dynamic',
28 | 'platform-server',
29 | 'router',
30 | 'upgrade'
31 | ];
32 |
33 | // Individual files (~300 requests):
34 | function packIndex(pkgName) {
35 | packages[ '@angular/' + pkgName ] = {main: 'index.js', defaultExtension: 'js'};
36 | }
37 |
38 | // Bundled (~40 requests):
39 | function packUmd(pkgName) {
40 | packages[ '@angular/' + pkgName ] = {main: 'bundles/' + pkgName + '.umd.js', defaultExtension: 'js'};
41 | }
42 |
43 | // Most environments should use UMD; some (Karma) need the individual index files
44 | var setPackageConfig = System.packageWithIndex ? packIndex : packUmd;
45 | // Add package entries for angular packages
46 | ngPackageNames.forEach(setPackageConfig);
47 | var config = {
48 | map: map,
49 | packages: packages
50 | };
51 | System.config(config);
52 | })(this);
53 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by laurence-ho on 29/06/17.
3 | */
4 |
5 | require('ts-node').register({project: false, disableWarnings: true});
6 | require('./gulpfile.ts');
--------------------------------------------------------------------------------
/gulpfile.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by Laurence Ho on 07-02-2017.
3 | */
4 |
5 | 'use strict';
6 |
7 | const gulp = require('gulp');
8 | const browserSync = require('browser-sync').create();
9 | const sass = require('gulp-sass');
10 | const del = require('del');
11 | const tsc = require('gulp-typescript');
12 | const sourcemaps = require('gulp-sourcemaps');
13 | const tsProject = tsc.createProject('tsconfig.json');
14 | const tslint = require('gulp-tslint');
15 | const nodemon = require('gulp-nodemon');
16 |
17 | /**
18 | * Remove build directory.
19 | */
20 | gulp.task('clean', (cb: any) => del([ 'dist' ], cb));
21 |
22 | /**
23 | * Lint all custom TypeScript files.
24 | */
25 | gulp.task('tslint', () =>
26 | gulp.src('client/app/**/*.ts')
27 | .pipe(tslint({
28 | formatter: 'prose'
29 | }))
30 | .pipe(tslint.report())
31 | );
32 |
33 | /**
34 | * Build Express server
35 | */
36 | gulp.task('build:server', () => {
37 | const tsResult = gulp.src('server/src/**/*.ts')
38 | .pipe(sourcemaps.init())
39 | .pipe(tsProject());
40 | return tsResult.js
41 | .pipe(sourcemaps.write())
42 | .pipe(gulp.dest('dist/server'));
43 | });
44 |
45 | /*
46 | * Build frontend app
47 | */
48 | gulp.task('build:client', () => {
49 | const tsResult = gulp.src('client/**/*.ts')
50 | .pipe(sourcemaps.init({loadMaps: true}))
51 | .pipe(tsProject());
52 | return tsResult.js
53 | .pipe(sourcemaps.write())
54 | .pipe(gulp.dest('dist/client'))
55 | .pipe(browserSync.stream());
56 | });
57 |
58 | /**
59 | * Copy all resources that are not TypeScript files into build directory. e.g. index.html, css, images
60 | */
61 | gulp.task('clientResources', () =>
62 | gulp.src([ 'client/**/*', '!**/*.ts', '!client/*.json' ])
63 | .pipe(gulp.dest('dist/client'))
64 | .pipe(browserSync.stream())
65 | );
66 |
67 | /**
68 | * Copy bin directory for www
69 | */
70 | gulp.task('serverResources', () =>
71 | gulp.src([ 'server/src/bin/**' ])
72 | .pipe(gulp.dest('dist/server/bin'))
73 | );
74 |
75 | /**
76 | * Copy all required libraries into build directory.
77 | */
78 | gulp.task('libs', () =>
79 | gulp.src([
80 | 'core-js/client/**',
81 | 'zone.js/dist/zone.js',
82 | 'reflect-metadata/Reflect.js',
83 | 'reflect-metadata/Reflect.js.map',
84 | 'systemjs/dist/system.src.js',
85 | 'lodash/lodash.min.js',
86 | 'primeng/**'
87 | ], {cwd: 'node_modules/**'}) /* Glob required here. */
88 | .pipe(gulp.dest('dist/client/libs'))
89 | );
90 |
91 | /**
92 | * Copy all required libraries into build directory.
93 | */
94 | gulp.task('font-awesome', () =>
95 | gulp.src([
96 | 'font-awesome/**/**'
97 | ], {cwd: 'node_modules/**'}) /* Glob required here. */
98 | .pipe(gulp.dest('dist/client/vendors'))
99 | );
100 |
101 | /**
102 | * Copy and compile bootstrap
103 | */
104 | gulp.task('sass', () =>
105 | gulp.src([
106 | 'bootstrap/scss/bootstrap.scss'
107 | ], {cwd: 'node_modules/**'})
108 | .pipe(sass())
109 | .pipe(gulp.dest('dist/client/vendors'))
110 | );
111 |
112 | gulp.task('bootstrap', () =>
113 | gulp.src([
114 | 'bootstrap/dist/js/bootstrap.min.js',
115 | 'jquery/dist/jquery.slim.js',
116 | 'popper.js/dist/popper.min.js'
117 | ], {cwd: 'node_modules/**'})
118 | .pipe(gulp.dest('dist/client/vendors'))
119 | );
120 |
121 | /**
122 | * Start the express server
123 | */
124 | gulp.task('start', () => {
125 | nodemon({
126 | script: 'dist/server/bin/www',
127 | ext: 'html js',
128 | ignore: [ 'ignored.js' ],
129 | tasks: [ 'tslint' ]
130 | })
131 | .on('restart', () => {
132 | console.log('restarted!');
133 | });
134 | });
135 |
136 | /**
137 | * Build the project.
138 | * 1. Clean the build directory
139 | * 2. Build Express server
140 | * 3. Build the Angular app
141 | * 4. Copy the resources
142 | * 5. Copy the dependencies.
143 | */
144 |
145 | gulp.task(
146 | 'build',
147 | gulp.series(
148 | 'clean',
149 | 'build:server',
150 | 'build:client',
151 | 'clientResources',
152 | 'serverResources',
153 | 'libs',
154 | 'font-awesome',
155 | 'sass',
156 | 'bootstrap'
157 | )
158 | );
159 |
160 | /**
161 | * Compile TypeScript sources and create sourcemaps in build directory.
162 | */
163 | gulp.task('compile', gulp.series('tslint'), () => {
164 | const tsResult = gulp.src('client/**/*.ts')
165 | .pipe(sourcemaps.init({loadMaps: true}))
166 | .pipe(tsProject());
167 | return tsResult.js
168 | .pipe(sourcemaps.write('.'))
169 | .pipe(gulp.dest('dist/client'))
170 | .pipe(browserSync.stream());
171 | });
172 |
173 | /**
174 | * Watch for changes in TypeScript, HTML and CSS files.
175 | */
176 | gulp.task('watch', () => {
177 | gulp.watch([ 'client/**/*.ts' ], gulp.series('compile')).on('change', browserSync.reload);
178 | gulp.watch([ 'client/**/*.html', 'client/**/*.css' ],
179 | gulp.series('clientResources')).on('change', browserSync.reload);
180 | });
181 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular4-express-mysql",
3 | "version": "1.0.0",
4 | "author": "Laurence Ho",
5 | "description": "Fullstack web application using Angular4, Primeng, Express, Mysql, Gulp and Passport",
6 | "scripts": {
7 | "clean": "gulp clean",
8 | "build": "gulp build",
9 | "start-all": "concurrently --kill-others \"gulp watch\" \"gulp start\""
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://LaurenceHo@bitbucket.org/LaurenceHo/angular4-express-mysql.git"
14 | },
15 | "keywords": [
16 | "Angular4",
17 | "Express",
18 | "Gulp",
19 | "Typescript",
20 | "Mysql"
21 | ],
22 | "license": "MIT",
23 | "dependencies": {
24 | "@angular/animations": "^4.4.7",
25 | "@angular/common": "^4.4.7",
26 | "@angular/compiler": "^4.4.7",
27 | "@angular/core": "^4.4.7",
28 | "@angular/forms": "^4.4.7",
29 | "@angular/http": "^4.4.7",
30 | "@angular/platform-browser": "^4.4.7",
31 | "@angular/platform-browser-dynamic": "^4.4.7",
32 | "@angular/platform-server": "^4.4.7",
33 | "@angular/router": "^4.4.7",
34 | "@angular/upgrade": "^4.4.7",
35 | "bcrypt-nodejs": "0.0.3",
36 | "body-parser": "^1.18.3",
37 | "bootstrap": "^4.2.1",
38 | "express": "^4.16.4",
39 | "express-sanitizer": "^1.0.4",
40 | "express-session": "^1.15.6",
41 | "font-awesome": "^4.7.0",
42 | "jquery": "^3.3.1",
43 | "lodash": "^4.17.11",
44 | "mysql": "^2.16.0",
45 | "passport": "^0.4.0",
46 | "passport-local": "^1.0.0",
47 | "popper.js": "^1.14.6",
48 | "primeng": "^4.3.0",
49 | "request": "^2.88.0",
50 | "rxjs": "^5.5.12"
51 | },
52 | "devDependencies": {
53 | "@types/bcrypt-nodejs": "0.0.30",
54 | "@types/body-parser": "^1.17.0",
55 | "@types/express": "^4.16.0",
56 | "@types/express-session": "^1.15.11",
57 | "@types/lodash": "^4.14.119",
58 | "@types/mysql": "0.0.34",
59 | "@types/node": "^10.12.18",
60 | "@types/passport": "^0.3.5",
61 | "@types/passport-local": "^1.0.33",
62 | "browser-sync": "^2.26.3",
63 | "concurrently": "^3.6.1",
64 | "core-js": "^2.6.2",
65 | "del": "^3.0.0",
66 | "gulp": "^4.0.0",
67 | "gulp-nodemon": "^2.4.2",
68 | "gulp-sass": "^4.0.2",
69 | "gulp-sourcemaps": "^2.6.4",
70 | "gulp-tslint": "^8.1.3",
71 | "gulp-typescript": "^3.2.4",
72 | "reflect-metadata": "^0.1.13",
73 | "systemjs": "^0.21.6",
74 | "ts-node": "^7.0.1",
75 | "tslint": "^5.12.1",
76 | "typescript": "^2.9.2",
77 | "zone.js": "^0.8.28"
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/server/src/Authentication.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by laurence-ho on 21/07/17.
3 | */
4 |
5 | import CampgroundRepository from './repository/CampgroundRepository';
6 | import CommentRepository from './repository/CommentRepository';
7 |
8 | const campgroundRepository = new CampgroundRepository();
9 | const commentRepository = new CommentRepository();
10 |
11 | const authentication: any = {};
12 |
13 | authentication.checkCampOwner = (req: any, res: any, next: any) => {
14 | if (req.isAuthenticated()) {
15 | try {
16 | campgroundRepository.findOneById(req.params.id, (callback: any) => {
17 | if (callback.campground.user_id !== req.user.id) {
18 | res.status(403).send({message: 'You have no permission'});
19 | } else {
20 | next();
21 | }
22 | });
23 | } catch (err) {
24 | res.status(500).send({message: err});
25 | }
26 | } else {
27 | res.status(403).send({message: 'Please Login First'});
28 | }
29 | };
30 |
31 | authentication.checkCommentOwner = (req: any, res: any, next: any) => {
32 | if (req.isAuthenticated()) {
33 | try {
34 | commentRepository.findOneById(req.params.comment_id, (callback: any) => {
35 | if (callback.comment.user_id !== req.user.id) {
36 | res.status(403).send({message: 'You have no permission'});
37 | } else {
38 | next();
39 | }
40 | });
41 | } catch (err) {
42 | res.status(500).send({message: err});
43 | }
44 | } else {
45 | res.status(403).send({message: 'Please Login First'});
46 | }
47 | };
48 |
49 | authentication.isLoggedIn = (req: any, res: any, next: any) => {
50 | if (req.isAuthenticated()) {
51 | return next();
52 | }
53 | res.status(403).send({message: 'Please Login First'});
54 | };
55 |
56 | export = authentication;
57 |
--------------------------------------------------------------------------------
/server/src/Passport.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by laurence-ho on 21/07/17.
3 | */
4 |
5 | import * as bcrypt from 'bcrypt-nodejs';
6 | import { Passport } from 'passport';
7 | import { Strategy } from 'passport-local';
8 | import UserRepository from './repository/UserRepository';
9 |
10 | const userRepository = new UserRepository();
11 |
12 | export = (passport: Passport) => {
13 | // =========================================================================
14 | // passport session setup ==================================================
15 | // =========================================================================
16 | // required for persistent login sessions
17 | // passport needs ability to serialize and unserialize users out of session
18 |
19 | // used to serialize the user for the session
20 | passport.serializeUser((user: any, done: any) => {
21 | done(null, user.id);
22 | });
23 |
24 | // used to deserialize the user
25 | passport.deserializeUser((id: any, done: any) => {
26 | try {
27 | userRepository.findOneById(id, (result: any) => {
28 | done(null, result);
29 | });
30 | } catch (err) {
31 | return done(err);
32 | }
33 | });
34 |
35 | // =========================================================================
36 | // LOCAL SIGNUP ============================================================
37 | // =========================================================================
38 | // we are using named strategies since we have one for login and one for signup
39 | // by default, if there was no name, it would just be called 'local'
40 |
41 | passport.use('local-signup',
42 | new Strategy({
43 | usernameField: 'username',
44 | passwordField: 'password',
45 | passReqToCallback: true // allows us to pass back the entire request to the callback
46 | },
47 | (req: any, username: string, password: string, done: any) => {
48 | try {
49 | userRepository.findOneByUsername(username, (result: any) => {
50 | if (result) {
51 | return done(false, {message: 'This username is already taken.'});
52 | } else {
53 | // if there is no user with that username, create the user
54 | const newUser = {
55 | id: 0,
56 | username,
57 | password: bcrypt.hashSync(password, null)
58 | };
59 | userRepository.createOne(newUser, (result: any) => {
60 | newUser.id = result.user_id;
61 | done(null, newUser);
62 | });
63 | return done;
64 | }
65 | });
66 | } catch (err) {
67 | return done(err);
68 | }
69 | })
70 | );
71 |
72 | // =========================================================================
73 | // LOCAL LOGIN =============================================================
74 | // =========================================================================
75 | // we are using named strategies since we have one for login and one for signup
76 | // by default, if there was no name, it would just be called 'local'
77 |
78 | passport.use('local-login',
79 | new Strategy({
80 | // by default, local strategy uses username and password, we will override with email
81 | usernameField: 'username',
82 | passwordField: 'password',
83 | passReqToCallback: true // allows us to pass back the entire request to the callback
84 | },
85 | (req: any, username: string, password: string, done: any) => { // callback with email and password from our form
86 | try {
87 | userRepository.findOneByUsername(username, (result: any) => {
88 | if (result) {
89 | // if the user is found but the password is wrong
90 | if (!bcrypt.compareSync(password, result.password)) {
91 | return done(null, false, {message: 'User name or password is wrong'});
92 | } else {
93 | // all is well, return successful user
94 | return done(null, result);
95 | }
96 | } else {
97 | return done(null, false, {message: 'User name or password is wrong'});
98 | }
99 | });
100 | } catch (err) {
101 | return done(err);
102 | }
103 | })
104 | );
105 | };
106 |
--------------------------------------------------------------------------------
/server/src/Server.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by laurence-ho on 21/07/17.
3 | */
4 | import * as bodyParser from 'body-parser';
5 | import * as express from 'express';
6 | import * as session from 'express-session';
7 | import * as passport from 'passport';
8 | import * as path from 'path';
9 |
10 | import * as campgroundRoutes from './routes/Campground';
11 | import * as commentRoutes from './routes/Comment';
12 | import * as userRoutes from './routes/User';
13 |
14 | const expressSanitizer = require('express-sanitizer');
15 |
16 | // initial database schema
17 | require('./database/DatabaseSchema').schema();
18 | require('./Passport')(passport);
19 |
20 | const app = express();
21 |
22 | app.use(bodyParser.urlencoded({extended: false}));
23 | app.use(bodyParser.json());
24 |
25 | app.use('/app', express.static(path.resolve(__dirname, '../client/app')));
26 | app.use('/libs', express.static(path.resolve(__dirname, '../client/libs')));
27 |
28 | // for system.js to work. Can be removed if bundling.
29 | app.use(express.static(path.resolve(__dirname, '../client')));
30 | app.use(express.static(path.resolve(__dirname, '../../node_modules')));
31 |
32 | app.use(expressSanitizer());
33 |
34 | app.use(session({
35 | secret: 'mySecretKey',
36 | resave: true,
37 | saveUninitialized: true
38 | }));
39 | app.use(passport.initialize());
40 | app.use(passport.session());
41 |
42 | // initial routes
43 | app.use('/api', userRoutes);
44 | app.use('/api', campgroundRoutes);
45 | app.use('/api', commentRoutes);
46 |
47 | app.get('/', (req: any, res: any) => {
48 | res.sendFile(path.resolve(__dirname, '../client/index.html'));
49 | });
50 |
51 | app.get('*', (req: any, res: any) => {
52 | res.send('Sorry, page not found!');
53 | });
54 |
55 | export = app;
56 |
--------------------------------------------------------------------------------
/server/src/bin/www:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const http = require('http');
4 | const app = require('../Server');
5 |
6 | const server = http.createServer(app);
7 |
8 | server.listen(8080, () => {
9 | const port = server.address().port;
10 | console.log('This express angular app is listening on port:' + port);
11 | });
12 |
--------------------------------------------------------------------------------
/server/src/database/DatabaseSchema.ts:
--------------------------------------------------------------------------------
1 | import DatabaseService from './DatabaseService';
2 |
3 | const database = new DatabaseService();
4 |
5 | export const schema = () => {
6 | let sql_user = 'CREATE TABLE IF NOT EXISTS `users` (' +
7 | '`id` int(11) NOT NULL AUTO_INCREMENT,' +
8 | '`username` varchar(255) NOT NULL,' +
9 | '`password` varchar(255) NOT NULL,' +
10 | 'PRIMARY KEY(`id`)' +
11 | ') ENGINE = InnoDB DEFAULT CHARSET = utf8';
12 |
13 | let sql_camp = 'CREATE TABLE IF NOT EXISTS `campgrounds` (' +
14 | '`id` int(11) NOT NULL AUTO_INCREMENT,' +
15 | '`name` varchar(255) NOT NULL,' +
16 | '`image` varchar(255) NOT NULL,' +
17 | '`username` varchar(255) NOT NULL,' +
18 | '`price` int(11),' +
19 | '`user_id` int(11) NOT NULL,' +
20 | '`description` TEXT,' +
21 | 'PRIMARY KEY(`id`),' +
22 | 'CONSTRAINT `FK_USER_CAMP` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)' +
23 | ') ENGINE = InnoDB DEFAULT CHARSET = utf8';
24 |
25 | let sql_comment = 'CREATE TABLE IF NOT EXISTS `comments` (' +
26 | '`id` int(11) NOT NULL AUTO_INCREMENT,' +
27 | '`username` varchar(255) NOT NULL,' +
28 | '`user_id` int(11) NOT NULL,' +
29 | '`text` TEXT NOT NULL,' +
30 | '`campground_id` int(11) NOT NULL,' +
31 | 'PRIMARY KEY(`id`),' +
32 | 'CONSTRAINT `FK_USER_COMMENT` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`),' +
33 | 'CONSTRAINT `FK_CAMP_COMMENT` FOREIGN KEY (`campground_id`) REFERENCES `campgrounds` (`id`)' +
34 | ') ENGINE = InnoDB DEFAULT CHARSET = utf8';
35 |
36 | database.query(sql_user, null);
37 | database.query(sql_camp, null);
38 | database.query(sql_comment, null);
39 | };
40 |
--------------------------------------------------------------------------------
/server/src/database/DatabaseService.ts:
--------------------------------------------------------------------------------
1 | import * as mysql from 'mysql';
2 |
3 | export default class DatabaseService {
4 | pool: any = {};
5 |
6 | constructor() {
7 | this.pool = mysql.createPool({
8 | connectionLimit: 12,
9 | host: 'localhost',
10 | user: 'sa',
11 | password: '(IJN8uhb',
12 | database: 'yelpcamp',
13 | charset: 'utf8'
14 | });
15 | }
16 |
17 | getConnection = (callback: any) => {
18 | this.pool.getConnection(callback);
19 | };
20 |
21 | query = (sql: string, values: any) => new Promise((resolve, reject) => {
22 | this.pool.getConnection((err: any, connection: any) => {
23 | if (err) {
24 | reject(err);
25 | throw err;
26 | } else {
27 | connection.query(sql, values, (err: any, results: any) => {
28 | console.log('SQL: ', sql);
29 | console.log('Query values: ', values);
30 | connection.release();
31 |
32 | if (err) {
33 | reject(err);
34 | throw err;
35 | } else {
36 | console.log('Query results:', JSON.stringify(results));
37 | resolve(results);
38 | }
39 | });
40 | }
41 | });
42 | });
43 |
44 | destroy = () => {
45 | this.pool.end((err: any) => console.error(err));
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/server/src/model/Campground.ts:
--------------------------------------------------------------------------------
1 | export interface Campground {
2 | id: number;
3 | name: string;
4 | image: string;
5 | description: string;
6 | price: number;
7 | username: string;
8 | user_id: number;
9 | }
10 |
--------------------------------------------------------------------------------
/server/src/model/Comment.ts:
--------------------------------------------------------------------------------
1 | export interface Comment {
2 | id: number;
3 | text: string;
4 | campground_id: number;
5 | username: string;
6 | user_id: number;
7 | }
8 |
--------------------------------------------------------------------------------
/server/src/model/User.ts:
--------------------------------------------------------------------------------
1 | export interface User {
2 | id: number;
3 | username: string;
4 | password: string;
5 | }
6 |
--------------------------------------------------------------------------------
/server/src/repository/BaseRepository.ts:
--------------------------------------------------------------------------------
1 | export interface BaseRepository {
2 |
3 | findAll(callback: any): void;
4 |
5 | findOneById(id: number, callback: any): void;
6 |
7 | createOne(item: T, callback: any): void;
8 |
9 | updateOne(item: T): void;
10 |
11 | deleteOne(id: number): void;
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/server/src/repository/CampgroundRepository.ts:
--------------------------------------------------------------------------------
1 | import DatabaseService from '../database/DatabaseService';
2 | import { Campground } from '../model/Campground';
3 | import { Comment } from '../model/Comment';
4 | import { BaseRepository } from './BaseRepository';
5 |
6 | const database = new DatabaseService();
7 |
8 | export default class CampgroundRepository implements BaseRepository {
9 | findAll(callback: any): void {
10 | database.query('SELECT * FROM campgrounds', null).then(
11 | (results: Campground[]) => callback(results)
12 | );
13 | }
14 |
15 | findOneById(id: number, callback: any): void {
16 | database.query('SELECT * FROM campgrounds WHERE id = ?', [ id ]).then((campResult: Campground) => {
17 | let campground: any = [];
18 | let comments: any = [];
19 |
20 | database.query('SELECT * FROM comments WHERE campground_id = ?', [ id ]).then((commentResult: Comment[]) => {
21 | campground = campResult[ 0 ];
22 | comments = commentResult;
23 |
24 | callback({campground, comments});
25 | });
26 | });
27 | }
28 |
29 | createOne(campground: Campground, callback: any): void {
30 | database.query('INSERT INTO campgrounds SET ?', campground).then(
31 | (result: any) => callback({campground_id: result.insertId})
32 | );
33 | }
34 |
35 | updateOne(campground: Campground): void {
36 | database.query('UPDATE campgrounds SET ? WHERE id = ?', [ campground, campground.id ]);
37 | }
38 |
39 | deleteOne(id: number): void {
40 | database.query('DELETE FROM campgrounds WHERE id = ?', [ id ]);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/server/src/repository/CommentRepository.ts:
--------------------------------------------------------------------------------
1 | import DatabaseService from '../database/DatabaseService';
2 | import { Comment } from '../model/Comment';
3 | import { BaseRepository } from './BaseRepository';
4 |
5 | const database = new DatabaseService();
6 |
7 | export default class CommentRepository implements BaseRepository {
8 | findAll(callback: any): void {
9 | // TODO
10 | }
11 |
12 | findOneById(id: number, callback: any): void {
13 | database.query('SELECT * FROM comments WHERE id = ?', [ id ]).then(
14 | (result: any) => callback({comment: result[ 0 ]})
15 | );
16 | }
17 |
18 | createOne(item: Comment, callback: any): void {
19 | database.query('INSERT INTO comments SET ?', item).then(
20 | (result: any) => callback({comment_id: result.insertId})
21 | );
22 | }
23 |
24 | updateOne(item: Comment): void {
25 | database.query('UPDATE comments SET ? WHERE id = ?', [ item, item.id ]);
26 | }
27 |
28 | deleteOne(id: number): void {
29 | database.query('DELETE FROM comments WHERE id = ?', [ id ]);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/server/src/repository/UserRepository.ts:
--------------------------------------------------------------------------------
1 | import DatabaseService from '../database/DatabaseService';
2 | import { User } from '../model/User';
3 | import { BaseRepository } from './BaseRepository';
4 |
5 | const database = new DatabaseService();
6 |
7 | export default class UserRepository implements BaseRepository {
8 | findAll(callback: any): void {
9 | // TODO
10 | }
11 |
12 | findOneById(id: number, callback: any): void {
13 | database.query('SELECT * FROM users WHERE id = ?', [ id ]).then(
14 | (result: any) => callback(result[ 0 ])
15 | );
16 | }
17 |
18 | findOneByUsername(username: string, callback: any): void {
19 | database.query('SELECT * FROM users WHERE username = ?', [ username ]).then(
20 | (result: any) => callback(result[ 0 ])
21 | );
22 | }
23 |
24 | createOne(item: User, callback: any): void {
25 | database.query('INSERT INTO users ( username, password ) values (?,?)', [ item.username, item.password ]).then(
26 | (result: any) => callback({user_id: result.insertId})
27 | );
28 | }
29 |
30 | updateOne(item: User): void {
31 | // TODO
32 | }
33 |
34 | deleteOne(id: number): void {
35 | // TODO
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/server/src/routes/Campground.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by laurence-ho on 21/07/17.
3 | */
4 |
5 | import * as express from 'express';
6 | import * as authentication from '../Authentication';
7 | import CampgroundRepository from '../repository/CampgroundRepository';
8 |
9 | const router = express.Router();
10 | const campgroundRepository = new CampgroundRepository();
11 |
12 | // get all campgrounds
13 | router.get('/campground', (req: any, res: any) => {
14 | try {
15 | campgroundRepository.findAll((callback: any) => {
16 | callback.message = 'OK';
17 | res.status(200).send(callback);
18 | });
19 | } catch (err) {
20 | res.status(500).send({message: err});
21 | }
22 | });
23 |
24 | // create one campground
25 | router.post('/campground', authentication.isLoggedIn, (req: any, res: any) => {
26 | req.body.name = req.sanitize(req.body.name);
27 | req.body.image = req.sanitize(req.body.image);
28 | req.body.description = req.sanitize(req.body.description);
29 | req.body.price = req.sanitize(req.body.price);
30 |
31 | try {
32 | campgroundRepository.createOne(req.body, (callback: any) => {
33 | callback.message = 'OK';
34 | res.status(200).send(callback);
35 | });
36 | } catch (err) {
37 | res.status(500).send({message: err});
38 | }
39 | });
40 |
41 | router.get('/campground/:id', (req: any, res: any) => {
42 | try {
43 | campgroundRepository.findOneById(req.params.id, (callback: any) => {
44 | callback.message = 'OK';
45 | res.status(200).send(callback);
46 | });
47 | } catch (err) {
48 | res.status(500).send({message: err});
49 | }
50 | });
51 |
52 | router.get('/campground/:id/edit', authentication.checkCampOwner, (req: any, res: any) => {
53 | try {
54 | campgroundRepository.findOneById(req.params.id, (callback: any) => {
55 | callback.message = 'OK';
56 | res.status(200).send(callback);
57 | });
58 | } catch (err) {
59 | res.status(500).send({message: err});
60 | }
61 | });
62 |
63 | // edit one campground
64 | router.put('/campground/:id/edit', authentication.checkCampOwner, (req: any, res: any) => {
65 | req.body.name = req.sanitize(req.body.name);
66 | req.body.image = req.sanitize(req.body.image);
67 | req.body.description = req.sanitize(req.body.description);
68 | req.body.price = req.sanitize(req.body.price);
69 |
70 | try {
71 | campgroundRepository.updateOne(req.body);
72 | res.status(200).send({message: 'OK'});
73 | } catch (err) {
74 | res.status(500).send({message: err});
75 | }
76 | });
77 |
78 | // delete one campground
79 | router.delete('/campground/:id', authentication.checkCampOwner, (req: any, res: any) => {
80 | try {
81 | campgroundRepository.deleteOne(req.params.id);
82 | res.status(200).send({message: 'OK'});
83 | } catch (err) {
84 | res.status(500).send({message: err});
85 | }
86 | });
87 |
88 | export = router;
89 |
--------------------------------------------------------------------------------
/server/src/routes/Comment.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by laurence-ho on 22/07/17.
3 | */
4 |
5 | import * as express from 'express';
6 | import * as authentication from '../Authentication';
7 | import CommentRepository from '../repository/CommentRepository';
8 |
9 | const router = express.Router();
10 | const commentRepository = new CommentRepository();
11 |
12 | // get one comment for edit
13 | router.get('/comment/:comment_id/edit', authentication.checkCommentOwner, (req: any, res: any) => {
14 | try {
15 | commentRepository.findOneById(req.params.comment_id, (callback: any) => {
16 | callback.message = 'OK';
17 | res.status(200).send(callback);
18 | });
19 | } catch (err) {
20 | res.status(500).send({message: err});
21 | }
22 | });
23 |
24 | // create a new comment by campground id
25 | router.post('/comment', authentication.isLoggedIn, (req: any, res: any) => {
26 | req.body.text = req.sanitize(req.body.text);
27 |
28 | try {
29 | commentRepository.createOne(req.body, (callback: any) => {
30 | callback.message = 'OK';
31 | res.status(200).send(callback);
32 | });
33 | } catch (err) {
34 | res.status(500).send({message: err});
35 | }
36 | });
37 |
38 | // edit one comment by comment id
39 | router.put('/comment/:comment_id/edit', authentication.checkCommentOwner, (req: any, res: any) => {
40 | req.body.text = req.sanitize(req.body.text);
41 |
42 | try {
43 | commentRepository.updateOne(req.body);
44 | res.status(200).send({message: 'OK'});
45 | } catch (err) {
46 | res.status(500).send({message: err});
47 | }
48 | });
49 |
50 | // delete one comment
51 | router.delete('/comment/:comment_id', authentication.checkCommentOwner, (req: any, res: any) => {
52 | try {
53 | commentRepository.deleteOne(req.params.comment_id);
54 | res.status(200).send({message: 'OK'});
55 | } catch (err) {
56 | res.status(500).send({message: err});
57 | }
58 | });
59 |
60 | export = router;
61 |
--------------------------------------------------------------------------------
/server/src/routes/User.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by laurence-ho on 22/07/17.
3 | */
4 |
5 | import * as express from 'express';
6 | import * as passport from 'passport';
7 | import * as authentication from '../Authentication';
8 |
9 | const router = express.Router();
10 |
11 | router.post('/login', (req: any, res: any, next: any) => {
12 | passport.authenticate('local-login', (err: any, user: any, info: any) => {
13 | if (err) {
14 | return res.status(401).send({message: err.message});
15 | }
16 | if (!user) {
17 | return res.status(401).send({message: info.message});
18 | }
19 | req.logIn(user, (err: any) => {
20 | if (err) {
21 | return res.status(401).send({message: err.message});
22 | }
23 |
24 | let milliseconds = 0;
25 | if (req.body.remember) {
26 | milliseconds = 1000 * 60 * 30; // 30 minutes
27 |
28 | req.session.cookie.expires = new Date(Date.now() + milliseconds);
29 | req.session.cookie.maxAge = milliseconds;
30 | } else {
31 | milliseconds = 1000 * 60 * 60 * 24; // 1 day
32 |
33 | req.session.cookie.expires = new Date(Date.now() + milliseconds);
34 | req.session.cookie.maxAge = milliseconds;
35 | }
36 | return res.status(200).json(req.user);
37 | });
38 | })(req, res, next);
39 | });
40 |
41 | router.post('/signup', (req: any, res: any, next: any) => {
42 | passport.authenticate('local-signup', (err: any, user: any, info: any) => {
43 | if (err) {
44 | return res.status(403).send({message: err.message});
45 | }
46 |
47 | if (!user) {
48 | return res.status(403).send({message: info.message});
49 | }
50 |
51 | return res.status(200).send({message: 'OK'});
52 | })(req, res, next);
53 | }
54 | );
55 |
56 | router.get('/profile', authentication.isLoggedIn, (req: any, res: any) => {
57 | res.json(req.user);
58 | });
59 |
60 | router.get('/logout', (req: any) => {
61 | req.session.destroy((err: any) => {
62 | console.error(err);
63 | });
64 | req.logout();
65 | });
66 |
67 | export = router;
68 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "sourceMap": true,
7 | "emitDecoratorMetadata": true,
8 | "experimentalDecorators": true,
9 | "skipLibCheck": true,
10 | "lib": [
11 | "es2015",
12 | "dom"
13 | ],
14 | "types": [
15 | "node",
16 | "lodash"
17 | ],
18 | "noImplicitAny": true,
19 | "suppressImplicitAnyIndexErrors": true,
20 | "typeRoots": [
21 | "./node_modules/@types/"
22 | ]
23 | },
24 | "compileOnSave": true,
25 | "exclude": [
26 | "node_modules/*"
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "tslint:latest"
4 | ],
5 | "rules": {
6 | "arrow-parens": false,
7 | "interface-name": [
8 | true,
9 | "never-prefix"
10 | ],
11 | "member-access": false,
12 | "member-ordering": false,
13 | "no-console": false,
14 | "no-debugger": false,
15 | "no-implicit-dependencies": false,
16 | "no-shadowed-variable": false,
17 | "no-submodule-imports": false,
18 | "no-trailing-whitespace": false,
19 | "no-var-requires": false,
20 | "object-literal-sort-keys": false,
21 | "prefer-for-of": false,
22 | "quotemark": [
23 | true,
24 | "single"
25 | ],
26 | "trailing-comma": false,
27 | "variable-name": false
28 | }
29 | }
30 |
--------------------------------------------------------------------------------