Moving to MEAN.JS

This commit is contained in:
Amos Haviv
2014-02-10 13:09:31 +02:00
parent 17e089ec69
commit eae6f2d6bc
39 changed files with 1086 additions and 0 deletions

10
app/controllers/core.js Normal file
View File

@@ -0,0 +1,10 @@
'use strict';
/**
* Module dependencies.
*/
exports.index = function(req, res) {
res.render('index.html', {
user: req.user || null
});
};

7
app/routes/core.js Normal file
View File

@@ -0,0 +1,7 @@
'use strict';
module.exports = function(app) {
// Root routing
var core = require('../../app/controllers/core');
app.get('/', core.index);
};

63
app/tests/articles.js Normal file
View File

@@ -0,0 +1,63 @@
'use strict';
/**
* Module dependencies.
*/
var should = require('should'),
mongoose = require('mongoose'),
User = mongoose.model('User'),
Article = mongoose.model('Article');
//Globals
var user;
var article;
//The tests
describe('<Unit Test>', function() {
describe('Model Article:', function() {
beforeEach(function(done) {
user = new User({
firstName: 'Full',
lastName: 'Name',
displayName: 'Full Name',
email: 'test@test.com',
username: 'username',
password: 'password'
});
user.save(function() {
article = new Article({
title: 'Article Title',
content: 'Article Content',
user: user
});
done();
});
});
describe('Method Save', function() {
it('should be able to save without problems', function(done) {
return article.save(function(err) {
should.not.exist(err);
done();
});
});
it('should be able to show an error when try to save without title', function(done) {
article.title = '';
return article.save(function(err) {
should.exist(err);
done();
});
});
});
afterEach(function(done) {
Article.remove().exec();
User.remove().exec();
done();
});
});
});

73
app/tests/users.js Normal file
View File

@@ -0,0 +1,73 @@
'use strict';
/**
* Module dependencies.
*/
var should = require('should'),
mongoose = require('mongoose'),
User = mongoose.model('User');
//Globals
var user, user2;
//The tests
describe('<Unit Test>', function() {
describe('Model User:', function() {
before(function(done) {
user = new User({
firstName: 'Full',
lastName: 'Name',
displayName: 'Full Name',
email: 'test@test.com',
username: 'username',
password: 'password',
provider: 'local'
});
user2 = new User({
firstName: 'Full',
lastName: 'Name',
displayName: 'Full Name',
email: 'test@test.com',
username: 'username',
password: 'password',
provider: 'local'
});
done();
});
describe('Method Save', function() {
it('should begin with no users', function(done) {
User.find({}, function(err, users) {
users.should.have.length(0);
done();
});
});
it('should be able to save whithout problems', function(done) {
user.save(done);
});
it('should fail to save an existing user again', function(done) {
user.save();
return user2.save(function(err) {
should.exist(err);
done();
});
});
it('should be able to show an error when try to save without first name', function(done) {
user.firstName = '';
return user.save(function(err) {
should.exist(err);
done();
});
});
});
after(function(done) {
User.remove().exec();
done();
});
});
});

85
app/views/layout.html Normal file
View File

@@ -0,0 +1,85 @@
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>{{title}}</title>
<!-- General META -->
<meta charset="utf-8">
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width,initial-scale=1">
<!-- Semantic META -->
<meta name="keywords" content="{{keywords}}">
<meta name="description" content="{{description}}">
<!-- Social META -->
<meta property="fb:app_id" content="{{facebookAppId}}">
<meta property="og:site_name" content="{{title}}">
<meta property="og:title" content="{{title}}">
<meta property="og:description" content="{{description}}">
<meta property="og:url" content="{{url}}">
<meta property="og:image" content="/img/brand/logo.png">
<meta property="og:type" content="website">
<!-- Fav Icon -->
<link href="/img/brand/favicon.ico" rel="shortcut icon" type="image/x-icon">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="/lib/bootstrap/dist/css/bootstrap.css">
<link rel="stylesheet" href="/lib/bootstrap/dist/css/bootstrap-theme.css">
<!-- Application CSS -->
<link rel="stylesheet" href="/css/common.css">
<!--Application Modules CSS-->
{% for modulesCSSFile in modulesCSSFiles %}
<link rel="stylesheet" href="{{modulesCSSFile}}">
{% endfor %}
<!-- HTML5 Shim -->
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
</head>
<body>
<header data-ng-include="'/modules/core/views/header.html'" class="navbar navbar-fixed-top navbar-inverse"></header>
<section class="content">
<section class="container">
{% block content %}{% endblock %}
</section>
</section>
<!--Embedding The User Object-->
<script type="text/javascript">
var user = {{user | json | safe}};
</script>
<!--AngularJS-->
<script type="text/javascript" src="/lib/angular/angular.js"></script>
<script type="text/javascript" src="/lib/angular-route/angular-route.js"></script>
<script type="text/javascript" src="/lib/angular-resource/angular-resource.js"></script>
<script type="text/javascript" src="/lib/angular-cookies/angular-cookies.js"></script>
<script type="text/javascript" src="/lib/angular-animate/angular-animate.js"></script>
<!--Angular UI-->
<script type="text/javascript" src="/lib/angular-bootstrap/ui-bootstrap.js"></script>
<script type="text/javascript" src="/lib/angular-ui-utils/ui-utils.js"></script>
<!--AngularJS Application Init-->
<script type="text/javascript" src="/js/config.js"></script>
<script type="text/javascript" src="/js/application.js"></script>
<!--Application Modules-->
{% for modulesJSFile in modulesJSFiles %}
<script type="text/javascript" src="{{modulesJSFile}}"></script>
{% endfor %}
{% if process.env.NODE_ENV === 'development' %}
<!--Livereload script rendered -->
<script type="text/javascript" src="http://localhost:35729/livereload.js"></script>
{% endif %}
</body>
</html>

29
config/env/travis.js vendored Normal file
View File

@@ -0,0 +1,29 @@
'use strict';
module.exports = {
db: 'mongodb://localhost/mean-travis',
port: 3001,
app: {
title: 'MEAN.JS - Travis Environment'
},
facebook: {
clientID: 'APP_ID',
clientSecret: 'APP_SECRET',
callbackURL: 'http://localhost:3000/auth/facebook/callback'
},
twitter: {
clientID: 'CONSUMER_KEY',
clientSecret: 'CONSUMER_SECRET',
callbackURL: 'http://localhost:3000/auth/twitter/callback'
},
google: {
clientID: 'APP_ID',
clientSecret: 'APP_SECRET',
callbackURL: 'http://localhost:3000/auth/google/callback'
},
linkedin: {
clientID: 'APP_ID',
clientSecret: 'APP_SECRET',
callbackURL: 'http://localhost:3000/auth/linkedin/callback'
}
};

38
config/utilities.js Normal file
View File

@@ -0,0 +1,38 @@
'use strict';
/**
* Module dependencies.
*/
var fs = require('fs');
// Walk function to recursively get files
var _walk = function(root, regex, exclude, removePath) {
var output = [];
var directories = [];
// First read through files
fs.readdirSync(root).forEach(function(file) {
var newPath = root + '/' + file;
var stat = fs.statSync(newPath);
if (stat.isFile()) {
if (regex.test(file) && (!exclude || !exclude.test(file))) {
output.push(newPath.replace(removePath, ''));
}
} else if (stat.isDirectory()) {
directories.push(newPath);
}
});
// Then recursively add directories
directories.forEach(function(directory) {
output = output.concat(_walk(directory, regex, exclude, removePath));
});
return output;
};
/**
* Exposing the walk function
*/
exports.walk = _walk;

65
karma.conf.js Normal file
View File

@@ -0,0 +1,65 @@
'use strict';
/**
* Module dependencies.
*/
var utilities = require('./config/utilities');
// Grabbing module files using the walk function
var modulesJSFiles = utilities.walk('./public/modules', /(.*)\.(js)/, null, null);
// Karma configuration
module.exports = function(config) {
config.set({
// Frameworks to use
frameworks: ['jasmine'],
// List of files / patterns to load in the browser
files: [
'public/lib/angular/angular.js',
'public/lib/angular-mocks/angular-mocks.js',
'public/lib/angular-cookies/angular-cookies.js',
'public/lib/angular-resource/angular-resource.js',
'public/lib/angular-route/angular-route.js',
'public/lib/angular-bootstrap/ui-bootstrap.js',
'public/lib/angular-ui-utils/ui-utils.js',
'public/js/config.js',
'public/js/application.js',
].concat(modulesJSFiles),
// Test results reporter to use
// Possible values: 'dots', 'progress', 'junit', 'growl', 'coverage'
//reporters: ['progress'],
reporters: ['progress'],
// Web server port
port: 9876,
// Enable / disable colors in the output (reporters and logs)
colors: true,
// Level of logging
// Possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,
// Enable / disable watching file and executing tests whenever any file changes
autoWatch: true,
// Start these browsers, currently available:
// - Chrome
// - ChromeCanary
// - Firefox
// - Opera
// - Safari (only Mac)
// - PhantomJS
// - IE (only Windows)
browsers: ['PhantomJS'],
// If browser does not capture in given timeout [ms], kill it
captureTimeout: 60000,
// Continuous Integration mode
// If true, it capture browsers, run tests and exit
singleRun: true
});
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
public/img/brand/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

20
public/js/application.js Normal file
View File

@@ -0,0 +1,20 @@
'use strict';
//Start by defining the main module and adding the module dependencies
angular.module(ApplicationConfiguration.applicationModuleName, ApplicationConfiguration.applicationModuleVendorDependencies);
// Setting HTML5 Location Mode
angular.module(ApplicationConfiguration.applicationModuleName).config(['$locationProvider',
function($locationProvider) {
$locationProvider.hashPrefix('!');
}
]);
//Then define the init function for starting up the application
angular.element(document).ready(function() {
//Fixing facebook bug with redirect
if (window.location.hash === '#_=_') window.location.hash = '#!';
//Then init the app
angular.bootstrap(document, [ApplicationConfiguration.applicationModuleName]);
});

View File

@@ -0,0 +1,4 @@
'use strict';
// Use Applicaion configuration module to register a new module
ApplicationConfiguration.registerModule('mean.articles');

View File

@@ -0,0 +1,20 @@
'use strict';
//Setting up route
angular.module('mean.articles').config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/articles', {
templateUrl: 'modules/articles/views/list.html'
}).
when('/articles/create', {
templateUrl: 'modules/articles/views/create.html'
}).
when('/articles/:articleId', {
templateUrl: 'modules/articles/views/view.html'
}).
when('/articles/:articleId/edit', {
templateUrl: 'modules/articles/views/edit.html'
});
}
]);

View File

@@ -0,0 +1,62 @@
'use strict';
angular.module('mean.articles').controller('ArticlesController', ['$scope', '$routeParams', '$location', 'Authentication', 'Articles',
function($scope, $routeParams, $location, Authentication, Articles) {
$scope.authentication = Authentication;
$scope.create = function() {
var article = new Articles({
title: this.title,
content: this.content
});
article.$save(function(response) {
$location.path('articles/' + response._id);
});
this.title = '';
this.content = '';
};
$scope.remove = function(article) {
if (article) {
article.$remove();
for (var i in $scope.articles) {
if ($scope.articles[i] === article) {
$scope.articles.splice(i, 1);
}
}
} else {
$scope.article.$remove(function() {
$location.path('articles');
});
}
};
$scope.update = function() {
var article = $scope.article;
if (!article.updated) {
article.updated = [];
}
article.updated.push(new Date().getTime());
article.$update(function() {
$location.path('articles/' + article._id);
});
};
$scope.find = function() {
Articles.query(function(articles) {
$scope.articles = articles;
});
};
$scope.findOne = function() {
Articles.get({
articleId: $routeParams.articleId
}, function(article) {
$scope.article = article;
});
};
}
]);

View File

@@ -0,0 +1,12 @@
'use strict';
//Articles service used for articles REST endpoint
angular.module('mean.articles').factory('Articles', ['$resource', function($resource) {
return $resource('articles/:articleId', {
articleId: '@_id'
}, {
update: {
method: 'PUT'
}
});
}]);

View File

@@ -0,0 +1,166 @@
'use strict';
(function() {
// Articles Controller Spec
describe('MEAN controllers', function() {
describe('ArticlesController', function() {
// Initialize global variables
var ArticlesController,
scope,
$httpBackend,
$routeParams,
$location;
// The $resource service augments the response object with methods for updating and deleting the resource.
// If we were to use the standard toEqual matcher, our tests would fail because the test values would not match
// the responses exactly. To solve the problem, we define a new toEqualData Jasmine matcher.
// When the toEqualData matcher compares two objects, it takes only object properties into
// account and ignores methods.
beforeEach(function() {
this.addMatchers({
toEqualData: function(expected) {
return angular.equals(this.actual, expected);
}
});
});
// Then we can start by loading the main application module
beforeEach(module('mean'));
// The injector ignores leading and trailing underscores here (i.e. _$httpBackend_).
// This allows us to inject a service but then attach it to a variable
// with the same name as the service.
beforeEach(inject(function($controller, $rootScope, _$location_, _$routeParams_, _$httpBackend_) {
// Set a new global scope
scope = $rootScope.$new();
// Point global variables to injected services
$routeParams = _$routeParams_;
$httpBackend = _$httpBackend_;
$location = _$location_;
// Initialize the Articles controller.
ArticlesController = $controller('ArticlesController', {
$scope: scope
});
}));
it('$scope.find() should create an array with at least one article object fetched from XHR', inject(function(Articles) {
// Create sample article using the Articles service
var sampleArticle = new Articles({
title: 'An Article about MEAN',
content: 'MEAN rocks!'
});
// Create a sample articles array that includes the new article
var sampleArticles = [sampleArticle];
// Set GET response
$httpBackend.expectGET('articles').respond(sampleArticles);
// Run controller functionality
scope.find();
$httpBackend.flush();
// Test scope value
expect(scope.articles).toEqualData(sampleArticles);
}));
it('$scope.findOne() should create an array with one article object fetched from XHR using a articleId URL parameter', inject(function(Articles) {
// Define a sample article object
var sampleArticle = new Articles({
title: 'An Article about MEAN',
content: 'MEAN rocks!'
});
// Set the URL parameter
$routeParams.articleId = '525a8422f6d0f87f0e407a33';
// Set GET response
$httpBackend.expectGET(/articles\/([0-9a-fA-F]{24})$/).respond(sampleArticle);
// Run controller functionality
scope.findOne();
$httpBackend.flush();
// Test scope value
expect(scope.article).toEqualData(sampleArticle);
}));
it('$scope.create() with valid form data should send a POST request with the form input values and then locate to new object URL', inject(function(Articles) {
// Create a sample article object
var sampleArticlePostData = new Articles({
title: 'An Article about MEAN',
content: 'MEAN rocks!'
});
// Create a sample article response
var sampleArticleResponse = new Articles({
_id: '525cf20451979dea2c000001',
title: 'An Article about MEAN',
content: 'MEAN rocks!'
});
// Fixture mock form input values
scope.title = 'An Article about MEAN';
scope.content = 'MEAN rocks!';
// Set POST response
$httpBackend.expectPOST('articles', sampleArticlePostData).respond(sampleArticleResponse);
// Run controller functionality
scope.create();
$httpBackend.flush();
// Test form inputs are reset
expect(scope.title).toEqual('');
expect(scope.content).toEqual('');
// Test URL redirection after the article was created
expect($location.path()).toBe('/articles/' + sampleArticleResponse._id);
}));
it('$scope.update() should update a valid article', inject(function(Articles) {
// Define a sample article put data
var sampleArticlePutData = new Articles({
_id: '525cf20451979dea2c000001',
title: 'An Article about MEAN',
content: 'MEAN Rocks!'
});
// Mock article in scope
scope.article = sampleArticlePutData;
// Set PUT response
$httpBackend.expectPUT(/articles\/([0-9a-fA-F]{24})$/).respond();
// Run controller functionality
scope.update();
$httpBackend.flush();
// Test URL location to new object
expect($location.path()).toBe('/articles/' + sampleArticlePutData._id);
}));
it('$scope.remove() should send a DELETE request with a valid articleId and remove the article from the scope', inject(function(Articles) {
// Create new article object
var sampleArticle = new Articles({
_id: '525a8422f6d0f87f0e407a33'
});
// Create new articles array and include the article
scope.articles = [sampleArticle];
// Set expected DELETE response
$httpBackend.expectDELETE(/articles\/([0-9a-fA-F]{24})$/).respond(204);
// Run controller functionality
scope.remove(sampleArticle);
$httpBackend.flush();
// Test array after successful delete
expect(scope.articles.length).toBe(0);
}));
});
});
}());

View File

@@ -0,0 +1,26 @@
<section data-ng-controller="ArticlesController">
<div class="page-header">
<h1>New Article</h1>
</div>
<div class="col-md-12">
<form class="form-horizontal" data-ng-submit="create()">
<fieldset>
<div class="form-group">
<label class="control-label" for="title">Title</label>
<div class="controls">
<input type="text" data-ng-model="title" id="title" class="form-control" placeholder="Title" required>
</div>
</div>
<div class="form-group">
<label class="control-label" for="content">Content</label>
<div class="controls">
<textarea data-ng-model="content" id="content" class="form-control" cols="30" rows="10" placeholder="Content"></textarea>
</div>
</div>
<div class="form-group">
<input type="submit" class="btn btn-default">
</div>
</fieldset>
</form>
</div>
</section>

View File

@@ -0,0 +1,26 @@
<section data-ng-controller="ArticlesController" data-ng-init="findOne()">
<div class="page-header">
<h1>Edit Article</h1>
</div>
<div class="col-md-12">
<form class="form-horizontal" data-ng-submit="update()">
<fieldset>
<div class="form-group">
<label class="control-label" for="title">Title</label>
<div class="controls">
<input type="text" data-ng-model="article.title" id="title" class="form-control" placeholder="Title" required>
</div>
</div>
<div class="form-group">
<label class="control-label" for="content">Content</label>
<div class="controls">
<textarea data-ng-model="article.content" id="content" class="form-control" cols="30" rows="10" placeholder="Content"></textarea>
</div>
</div>
<div class="form-group">
<input type="submit" value="Update" class="btn btn-default">
</div>
</fieldset>
</form>
</div>
</section>

View File

@@ -0,0 +1,15 @@
<section data-ng-controller="ArticlesController" data-ng-init="find()">
<div class="page-header">
<h1>Articles</h1>
</div>
<div class="list-group">
<a data-ng-repeat="article in articles" data-ng-href="#!/articles/{{article._id}}" class="list-group-item">
<small class="list-group-item-text">{{article.created | date:'medium'}} / {{article.user.displayName}}</small>
<h4 class="list-group-item-heading">{{article.title}}</h4>
<p class="list-group-item-text">{{article.content}}</p>
</a>
</div>
<div class="alert alert-warning text-center" data-ng-hide="!articles || articles.length">
No articles yet, why don't you <a href="/#!/articles/create">create one</a>?
</div>
</section>

View File

@@ -0,0 +1,20 @@
<section data-ng-controller="ArticlesController" data-ng-init="findOne()">
<div class="page-header">
<h1>{{article.title}}</h1>
</div>
<div class="pull-right" data-ng-show="authentication.user._id == article.user._id">
<a class="btn btn-primary" href="/#!/articles/{{article._id}}/edit">
<i class="glyphicon glyphicon-edit"></i>
</a>
<a class="btn btn-primary" data-ng-click="remove();">
<i class="glyphicon glyphicon-trash"></i>
</a>
</div>
<small>
<em class="text-muted">Posted on {{article.created | date:'mediumDate'}} by {{article.user.displayName}}</em>
</small>
<p class="lead">
{{article.content}}
</p>
</section>

View File

@@ -0,0 +1,11 @@
'use strict';
// Setting up route
angular.module('mean.core').config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/', {
templateUrl: 'modules/core/views/home.html'
});
}
]);

View File

@@ -0,0 +1,19 @@
'use strict';
angular.module('mean.core').controller('HeaderController', ['$scope', 'Authentication',
function($scope, Authentication) {
$scope.authentication = Authentication;
$scope.menu = [{
title: 'Articles',
link: 'articles',
uiRoute: '/articles'
}, {
title: 'New Article',
link: 'articles/create',
uiRoute: '/articles/create'
}];
$scope.isCollapsed = false;
}
]);

View File

@@ -0,0 +1,5 @@
'use strict';
angular.module('mean.core').controller('HomeController', ['$scope', 'Authentication', function ($scope, Authentication) {
$scope.authentication = Authentication;
}]);

4
public/modules/core/core.js Executable file
View File

@@ -0,0 +1,4 @@
'use strict';
// Use Applicaion configuration module to register a new module
ApplicationConfiguration.registerModule('mean.core');

View File

@@ -0,0 +1,26 @@
'use strict';
(function() {
describe('MEAN controllers', function() {
describe('HeaderController', function() {
//Initialize global variables
var scope,
HeaderController;
// Load the main application module
beforeEach(module('mean'));
beforeEach(inject(function($controller, $rootScope) {
scope = $rootScope.$new();
HeaderController = $controller('HeaderController', {
$scope: scope
});
}));
it('should expose the authentication service', function() {
expect(scope.authentication).toBeTruthy();
});
});
});
})();

View File

@@ -0,0 +1,26 @@
'use strict';
(function() {
describe('MEAN controllers', function() {
describe('HomeController', function() {
//Initialize global variables
var scope,
HomeController;
// Load the main application module
beforeEach(module('mean'));
beforeEach(inject(function($controller, $rootScope) {
scope = $rootScope.$new();
HomeController = $controller('HomeController', {
$scope: scope
});
}));
it('should expose the authentication service', function() {
expect(scope.authentication).toBeTruthy();
});
});
});
})();

View File

@@ -0,0 +1,39 @@
<div class="container" data-ng-controller="HeaderController">
<div class="navbar-header">
<button class="navbar-toggle" type="button" data-ng-click="isCollapsed = !isCollapsed">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a href="/#!/" class="navbar-brand">MEAN.JS</a>
</div>
<nav class="collapse navbar-collapse" collapse="!isCollapsed" role="navigation">
<ul class="nav navbar-nav" data-ng-show="authentication.user">
<li data-ng-repeat="item in menu" data-ng-show="authentication.user" ui-route="{{item.uiRoute}}" ng-class="{active: $uiRoute}">
<a href="/#!/{{item.link}}">{{item.title}}</a>
</li>
</ul>
<ul class="nav navbar-nav navbar-right" data-ng-hide="authentication.user">
<li ui-route="/signup" ng-class="{active: $uiRoute}">
<a href="/#!/signup">Signup</a>
</li>
<li class="divider-vertical"></li>
<li ui-route="/signin" ng-class="{active: $uiRoute}">
<a href="/#!/signin">Signin</a>
</li>
</ul>
<ul class="nav navbar-nav navbar-right" data-ng-show="authentication.user">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
{{authentication.user.displayName}} <b class="caret"></b>
</a>
<ul class="dropdown-menu">
<li>
<a href="/auth/signout">Signout</a>
</li>
</ul>
</li>
</ul>
</nav>
</div>

View File

@@ -0,0 +1,32 @@
<section data-ng-controller="HomeController">
<h1 class="text-center">THANK YOU FOR DOWNLOADING MEAN.JS</h1>
<section>
<p>
Before you begin we recommend you read about the basic building blocks that assemble a MEAN.JS application:
</p>
<dl>
<dt>MongoDB</dt>
<dd>
Go through <a target="_blank" href="http://mongodb.org">MongoDB Official Website</a> and proceed to its <a target="_blank" href="http://docs.mongodb.org/manual/">Great Manual</a>, which should help you understand NoSQL and MongoDB better.
</dd>
<dt>Express</dt>
<dd>
The best way to understand Express is through its <a target="_blank" href="http://expressjs.com"> Official Website</a>, particularly <a target="_blank" href="http://expressjs.com/guide.html">The Express Guide</a>; you can also go through this <a target="_blank" href="http://stackoverflow.com/questions/8144214/learning-express-for-node-js">StackOverflow Thread</a> for more resources.
</dd>
<dt>AngularJS</dt>
<dd>
Angular's <a target="_blank" href="http://angularjs.org">Official Website</a> is a great starting point. You can also use <a target="_blank" href="http://www.thinkster.io/">Thinkster Popular Guide</a>, and the <a target="_blank" href="https://egghead.io/">Egghead Videos</a>.
</dd>
<dt>Node.js</dt>
<dd>
Start by going through <a target="_blank" href="http://nodejs.org">Node.js Official Website</a> and this <a target="_blank" href="http://stackoverflow.com/questions/2353818/how-do-i-get-started-with-node-js">StackOverflow Thread</a>, which should get you going with the Node.js platform in no time.
</dd>
</dl>
<p class="alert alert-info">
When you're done with those resources and feel you understand the basic principals continue to the <a target="_blank" href="http://meanjs.org/docs.html">MEAN.JS Documentation</a>.
</p>
<br>
<br>Enjoy &amp; Keep Us Updated,
<br>The MEAN.JS Team.
</section>
</section>

View File

@@ -0,0 +1,30 @@
'use strict';
// Config HTTP Error Handling
angular.module('mean.users').config(['$httpProvider',
function($httpProvider) {
// Set the httpProvider "not authorized" interceptor
$httpProvider.interceptors.push(['$q', '$location', 'Authentication',
function($q, $location, Authentication) {
return {
responseError: function(rejection) {
switch (rejection.status) {
case 401:
// Deauthenticate the global user
Authentication.user = null;
// Redirect to signin page
$location.path('signin');
break;
case 403:
// Add unauthorized behaviour
break;
}
return $q.reject(rejection);
}
};
}
]);
}
]);

View File

@@ -0,0 +1,14 @@
'use strict';
// Setting up route
angular.module('mean.users').config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/signup', {
templateUrl: 'modules/users/views/signup.html'
}).
when('/signin', {
templateUrl: 'modules/users/views/signin.html'
});
}
]);

View File

@@ -0,0 +1,31 @@
'use strict';
angular.module('mean.users').controller('AuthenticationController', ['$scope', '$http', '$location', 'Authentication',
function($scope, $http, $location, Authentication) {
$scope.authentication = Authentication;
$scope.signup = function() {
$http.post('/auth/signup', $scope.credentials).success(function(data) {
//If successful we assign the data to the global user model
$scope.authentication.user = data;
//And redirect to the index page
$location.path('/');
}).error(function(data) {
$scope.error = data.message;
});
};
$scope.signin = function() {
$http.post('/auth/signin', $scope.credentials).success(function(data) {
//If successful we assign the data to the global user model
$scope.authentication.user = data;
//And redirect to the index page
$location.path('/');
}).error(function(data) {
$scope.error = data.message;
});
};
}
]);

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,14 @@
'use strict';
//Authentication service for user variables
angular.module('mean.users').factory('Authentication', [
function() {
var _this = this;
_this._data = {
user: window.user
};
return _this._data;
}
]);

4
public/modules/users/users.js Executable file
View File

@@ -0,0 +1,4 @@
'use strict';
// Use Applicaion configuration module to register a new module
ApplicationConfiguration.registerModule('mean.users');

View File

@@ -0,0 +1,39 @@
<section class="row" data-ng-controller="AuthenticationController">
<h3 class="col-md-12 text-center">Sign in using your social accounts</h3>
<div class="col-md-12 text-center">
<a href="/auth/facebook" class="undecorated-link">
<img src="/modules/users/img/buttons/facebook.png">
</a>
<a href="/auth/twitter" class="undecorated-link">
<img src="/modules/users/img/buttons/twitter.png">
</a>
<a href="/auth/google" class="undecorated-link">
<img src="/modules/users/img/buttons/google.png">
</a>
<a href="/auth/linkedin" class="undecorated-link">
<img src="/modules/users/img/buttons/linkedin.png">
</a>
</div>
<h3 class="col-md-12 text-center">Or with your email</h3>
<div class="col-xs-offset-2 col-xs-8 col-md-offset-5 col-md-2">
<form data-ng-submit="signin()" class="signin form-horizontal" autocomplete="off">
<fieldset>
<div class="form-group">
<label for="email">Email</label>
<input type="text" id="email" name="email" class="form-control" data-ng-model="credentials.email" placeholder="Email">
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" name="password" class="form-control" data-ng-model="credentials.password" placeholder="Password">
</div>
<div class="text-center form-group">
<button type="submit" class="btn btn-primary">Sign in</button>&nbsp; or&nbsp;
<a href="/#!/signup">Sign up</a>
</div>
<div data-ng-show="error" class="text-center text-danger">
<strong>{{error}}</strong>
</div>
</fieldset>
</form>
</div>
</section>

View File

@@ -0,0 +1,51 @@
<section class="row" data-ng-controller="AuthenticationController">
<h3 class="col-md-12 text-center">Sign up using your social accounts</h3>
<div class="col-md-12 text-center">
<a href="/auth/facebook" class="undecorated-link">
<img src="/modules/users/img/buttons/facebook.png">
</a>
<a href="/auth/twitter" class="undecorated-link">
<img src="/modules/users/img/buttons/twitter.png">
</a>
<a href="/auth/google" class="undecorated-link">
<img src="/modules/users/img/buttons/google.png">
</a>
<a href="/auth/linkedin" class="undecorated-link">
<img src="/modules/users/img/buttons/linkedin.png">
</a>
</div>
<h3 class="col-md-12 text-center">Or with your email</h3>
<div class="col-xs-offset-2 col-xs-8 col-md-offset-5 col-md-2">
<form data-ng-submit="signup()" class="signin form-horizontal" autocomplete="off">
<fieldset>
<div class="form-group">
<label for="firstName">First Name</label>
<input type="text" id="firstName" name="firstName" class="form-control" data-ng-model="credentials.firstName" placeholder="First Name">
</div>
<div class="form-group">
<label for="lastName">Last Name</label>
<input type="text" id="lastName" name="lastName" class="form-control" data-ng-model="credentials.lastName" placeholder="Last Name">
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="text" id="email" name="email" class="form-control" data-ng-model="credentials.email" placeholder="Email">
</div>
<div class="form-group">
<label for="username">Username</label>
<input type="text" id="username" name="username" class="form-control" data-ng-model="credentials.username" placeholder="Username">
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" name="password" class="form-control" data-ng-model="credentials.password" placeholder="Password">
</div>
<div class="text-center form-group">
<button type="submit" class="btn btn-large btn-primary">Sign up</button>&nbsp; or&nbsp;
<a href="/#!/signin" class="show-signup">Sign in</a>
</div>
<div data-ng-show="error" class="text-center text-danger">
<strong>{{error}}</strong>
</div>
</fieldset>
</form>
</div>
</section>