Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Angular Full Stack Universal #227

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions controllers/base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
abstract class BaseCtrl {

abstract model: any;

// Get all
getAll = async (req, res) => {
try {
console.log('GET ALL')
const all = await this.model.find({});
res.status(200).json(all);
} catch (err) {
return res.status(400).json({ error: err.message });
}
}

// Count all
count = async (req, res) => {
try {
const count = await this.model.count();
res.status(200).json(count);
} catch (err) {
return res.status(400).json({ error: err.message });
}
}

// Insert
insert = async (req, res) => {
try {
console.log('show US THIS DAMN REQ BODY '+req.body)
const obj = await new this.model(req.body).save();
res.status(201).json(obj);
} catch (err) {
return res.status(400).json({ error: err.message });
}
}

// Get by id
get = async (req, res) => {
try {
const obj = await this.model.findOne({ _id: req.params.id });
res.status(200).json(obj);
} catch (err) {
return res.status(500).json({ error: err.message });
}
}

// Update by id
update = async (req, res) => {
try {
await this.model.findOneAndUpdate({ _id: req.params.id }, req.body);
res.sendStatus(200);
} catch (err) {
return res.status(400).json({ error: err.message });
}
}

// Delete by id
delete = async (req, res) => {
try {
await this.model.findOneAndRemove({ _id: req.params.id });
res.sendStatus(200);
} catch (err) {
return res.status(400).json({ error: err.message });
}
}
}

export default BaseCtrl;
8 changes: 8 additions & 0 deletions controllers/cat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import Cat from '../models/cat';
import BaseCtrl from './base';

class CatCtrl extends BaseCtrl {
model = Cat;
}

export default CatCtrl;
22 changes: 22 additions & 0 deletions controllers/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import * as jwt from 'jsonwebtoken';

import User from '../models/user';
import BaseCtrl from './base';

class UserCtrl extends BaseCtrl {
model = User;

login = (req, res) => {
this.model.findOne({ email: req.body.email }, (err, user) => {
if (!user) { return res.sendStatus(403); }
user.comparePassword(req.body.password, (error, isMatch) => {
if (!isMatch) { return res.sendStatus(403); }
const token = jwt.sign({ user }, process.env.SECRET_TOKEN); // , { expiresIn: 10 } seconds
res.status(200).json({ token });
});
});
}

}

export default UserCtrl;
11 changes: 11 additions & 0 deletions models/cat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import * as mongoose from 'mongoose';

const catSchema = new mongoose.Schema({
name: String,
weight: Number,
age: Number
});

const Cat = mongoose.model('Cat', catSchema);

export default Cat;
42 changes: 42 additions & 0 deletions models/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import * as bcrypt from 'bcryptjs';
import * as mongoose from 'mongoose';

const userSchema = new mongoose.Schema({
username: String,
email: { type: String, unique: true, lowercase: true, trim: true },
password: String,
role: String
});

// Before saving the user, hash the password
userSchema.pre('save', function(next): void {
const user = this;
if (!user.isModified('password')) { return next(); }
bcrypt.genSalt(10, (err, salt) => {
if (err) { return next(err); }
bcrypt.hash(user.password, salt, (error, hash) => {
if (error) { return next(error); }
user.password = hash;
next();
});
});
});

userSchema.methods.comparePassword = function(candidatePassword, callback): void {
bcrypt.compare(candidatePassword, this.password, (err, isMatch) => {
if (err) { return callback(err); }
callback(null, isMatch);
});
};

// Omit the password when returning a user
userSchema.set('toJSON', {
transform: (doc, ret, options) => {
delete ret.password;
return ret;
}
});

const User = mongoose.model('User', userSchema);

export default User;
15 changes: 15 additions & 0 deletions mongo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import * as mongoose from 'mongoose';

async function setMongo() {
const mongodbURI = 'mongodb://localhost:27017/angularfullstack'
mongoose.Promise = global.Promise;
mongoose.set('useCreateIndex', true);
mongoose.set('useNewUrlParser', true);
mongoose.set('useFindAndModify', false);
mongoose.set('useUnifiedTopology', true);
// Connect to MongoDB using Mongoose
await mongoose.connect(mongodbURI);
console.log('Connected to MongoDB');
}

export default setMongo;
24 changes: 24 additions & 0 deletions routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import * as express from 'express';

import CatCtrl from './controllers/cat';
import UserCtrl from './controllers/user';
function setRoutes(app) {
const router = express.Router();
const catCtrl = new CatCtrl();
const userCtrl = new UserCtrl();

// Cats
router.route('/cats').get(catCtrl.getAll);

// Users
router.route('/login').post(userCtrl.login);
router.route('/users').get(userCtrl.getAll);
router.route('/user').post(userCtrl.insert);
router.route('/user/:id').get(userCtrl.get);

// Apply the routes to our application with the prefix /api
app.use('/api', router);

}

export default setRoutes;
84 changes: 84 additions & 0 deletions server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import 'zone.js/dist/zone-node';

import { ngExpressEngine } from '@nguniversal/express-engine';
import * as express from 'express';
import { join } from 'path';

import { AppServerModule } from './src/main.server';
import { APP_BASE_HREF } from '@angular/common';
import { existsSync } from 'fs';

import setRoutes from './routes';
import setMongo from './mongo';
import * as dotenv from 'dotenv';
// add to use POST ROUTES
const bodyParser = require("body-parser");
// The Express app is exported so that it can be used by serverless Functions.
export function app() {
const server = express();
dotenv.config();
const distFolder = join(process.cwd(), 'dist/browser');
const indexHtml = existsSync(join(distFolder, 'index.original.html'))
? 'index.original.html'
: 'index';

// Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
server.engine(
'html',
ngExpressEngine({
bootstrap: AppServerModule,
})
);

server.set('view engine', 'html');
server.set('views', distFolder);
server.use(bodyParser.urlencoded({ extended: false }))
server.use(bodyParser.json())
// TODO: implement data requests securely
/* server.get('/api/**', (req, res) => {
res.status(404).send('data requests are not yet supported');
});*/
setMongo();
setRoutes(server)

// Serve static files from /browser
server.get(
'*.*',
express.static(distFolder, {
maxAge: '1y',
})
);

// All regular routes use the Universal engine
server.get('*', (req, res) => {
console.log(req.url);
res.render(indexHtml, {
req,
providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }],
});
});

return server;
}

function run() {
const port = process.env.PORT || 4000;

// Start up the Node server
const server = app();
server.listen(port, () => {
console.log(`Node Express server listening on http://localhost:${port}`);
});
}

// Webpack will replace 'require' with '__webpack_require__'
// '__non_webpack_require__' is a proxy to Node 'require'
// The below code is to ensure that the server is run only when not requiring the bundle.
declare const __non_webpack_require__: NodeRequire;
const mainModule = __non_webpack_require__.main;
const moduleFilename = (mainModule && mainModule.filename) || '';
if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
run();
}

export * from './src/main.server';
53 changes: 53 additions & 0 deletions src/app/about/about.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<div class="card">
<h4 class="card-header">About</h4>
<div class="card-body">
<ul>
<li>
<b>Author: </b><a href="https://github.com/DavideViolante" target="_blank">Davide Violante</a>
</li>
<li>
<b>GitHub: </b><a href="https://github.com/DavideViolante/Angular-Full-Stack" target="_blank">project repository</a>
</li>
<li>
<b>This project uses the MEAN stack:</b>
</li>
<ul>
<li>
<a href="http://www.mongoosejs.com" target="_blank"><b>M</b>ongoose.js</a> (<a href="https://www.mongodb.com" target="_blank">MongoDB</a>)
</li>
<li>
<a href="http://www.expressjs.com" target="_blank"><b>E</b>xpress.js</a>
</li>
<li>
<a href="https://www.angular.io" target="_blank"><b>A</b>ngular 2+</a>
</li>
<li>
<a href="https://www.nodejs.org" target="_blank"><b>N</b>ode.js</a>
</li>
</ul>
<li>
<b>Other tools and technologies used:</b>
</li>
<ul>
<li>
<a href="https://cli.angular.io" target="_blank">Angular CLI</a>
</li>
<li>
<a href="http://www.getbootstrap.com" target="_blank">Bootstrap</a>
</li>
<li>
<a href="http://www.fontawesome.io" target="_blank">Font Awesome</a>
</li>
<li>
<a href="https://jwt.io" target="_blank">JSON Web Token</a>
</li>
<li>
<a href="https://github.com/auth0/angular2-jwt" target="_blank">Angular 2 JWT</a>
</li>
<li>
<a href="https://github.com/dcodeIO/bcrypt.js" target="_blank">Bcrypt.js</a>
</li>
</ul>
</ul>
</div>
</div>
Empty file.
32 changes: 32 additions & 0 deletions src/app/about/about.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';

import { AboutComponent } from './about.component';

describe('Component: About', () => {
let component: AboutComponent;
let fixture: ComponentFixture<AboutComponent>;

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ AboutComponent ]
})
.compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(AboutComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});

it('should display the page header text', () => {
const el = fixture.debugElement.query(By.css('h4')).nativeElement;
expect(el.textContent).toContain('About');
});

});
12 changes: 12 additions & 0 deletions src/app/about/about.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Component } from '@angular/core';

@Component({
selector: 'app-about',
templateUrl: './about.component.html',
styleUrls: ['./about.component.scss']
})
export class AboutComponent {

constructor() { }

}
Loading