first commit

This commit is contained in:
David Baldwynn 2015-06-29 15:51:29 -07:00
commit 87b351efea
155 changed files with 13318 additions and 0 deletions

3
.bowerrc Executable file
View File

@ -0,0 +1,3 @@
{
"directory": "public/lib"
}

15
.csslintrc Executable file
View File

@ -0,0 +1,15 @@
{
"adjoining-classes": false,
"box-model": false,
"box-sizing": false,
"floats": false,
"font-sizes": false,
"important": false,
"known-properties": false,
"overqualified-elements": false,
"qualified-headings": false,
"regex-selectors": false,
"unique-headings": false,
"universal-selector": false,
"unqualified-attributes": false
}

25
.editorconfig Executable file
View File

@ -0,0 +1,25 @@
# EditorConfig is awesome: http://EditorConfig.org
# How-to with your editor: http://editorconfig.org/#download
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
indent_style = tab
insert_final_newline = true
[{Dockerfile,Procfile}]
trim_trailing_whitespace = true
# Standard at: https://github.com/felixge/node-style-guide
[{*.js,*.json}]
trim_trailing_whitespace = true
quote_type = single
curly_bracket_next_line = false
spaces_around_operators = true
space_after_control_statements = true
space_after_anonymous_functions = false
spaces_in_brackets = false

62
.gitignore vendored Executable file
View File

@ -0,0 +1,62 @@
# iOS / Apple
# ===========
.DS_Store
ehthumbs.db
Icon?
Thumbs.db
# Node and related ecosystem
# ==========================
.nodemonignore
.sass-cache/
npm-debug.log
node_modules/
public/lib/
app/tests/coverage/
.bower-*/
.idea/
# MEAN.js app and assets
# ======================
config/sslcerts/*.pem
access.log
public/dist/
# Sublime editor
# ==============
.sublime-project
*.sublime-project
*.sublime-workspace
# Eclipse project files
# =====================
.project
.settings/
.*.md.html
.metadata
*~.nib
local.properties
# IntelliJ
# ========
*.iml
# Cloud9 IDE
# =========
.c9/
data/
mongod
# General
# =======
*.log
*.csv
*.dat
*.out
*.pid
*.gz
*.tmp
*.bak
*.swp
logs/
build/

42
.jshintrc Executable file
View File

@ -0,0 +1,42 @@
{
"node": true, // Enable globals available when code is running inside of the NodeJS runtime environment.
"browser": true, // Standard browser globals e.g. `window`, `document`.
"esnext": true, // Allow ES.next specific features such as `const` and `let`.
"bitwise": false, // Prohibit bitwise operators (&, |, ^, etc.).
"camelcase": false, // Permit only camelcase for `var` and `object indexes`.
"curly": false, // Require {} for every new block or scope.
"eqeqeq": true, // Require triple equals i.e. `===`.
"immed": true, // Require immediate invocations to be wrapped in parens e.g. `( function(){}() );`
"latedef": true, // Prohibit variable use before definition.
"newcap": true, // Require capitalization of all constructor functions e.g. `new F()`.
"noarg": true, // Prohibit use of `arguments.caller` and `arguments.callee`.
"quotmark": "single", // Define quotes to string values.
"regexp": true, // Prohibit `.` and `[^...]` in regular expressions.
"undef": true, // Require all non-global variables be declared before they are used.
"unused": false, // Warn unused variables.
"strict": true, // Require `use strict` pragma in every file.
"trailing": true, // Prohibit trailing whitespaces.
"smarttabs": false, // Suppresses warnings about mixed tabs and spaces
"globals": { // Globals variables.
"jasmine": true,
"angular": true,
"ApplicationConfiguration": true
},
"predef": [ // Extra globals.
"define",
"require",
"exports",
"module",
"describe",
"before",
"beforeEach",
"after",
"afterEach",
"it",
"inject",
"expect"
],
"indent": 4, // Specify indentation spacing
"devel": true, // Allow development statements e.g. `console.log();`.
"noempty": true // Prohibit use of empty blocks.
}

1
.slugignore Executable file
View File

@ -0,0 +1 @@
/app/tests

8
.travis.yml Executable file
View File

@ -0,0 +1,8 @@
language: node_js
node_js:
- "0.10"
- "0.11"
env:
- NODE_ENV=travis
services:
- mongodb

29
Dockerfile Executable file
View File

@ -0,0 +1,29 @@
FROM dockerfile/nodejs
MAINTAINER Matthias Luebken, matthias@catalyst-zero.com
WORKDIR /home/mean
# Install Mean.JS Prerequisites
RUN npm install -g grunt-cli
RUN npm install -g bower
# Install Mean.JS packages
ADD package.json /home/mean/package.json
RUN npm install
# Manually trigger bower. Why doesnt this work via npm install?
ADD .bowerrc /home/mean/.bowerrc
ADD bower.json /home/mean/bower.json
RUN bower install --config.interactive=false --allow-root
# Make everything available for start
ADD . /home/mean
# currently only works for development
ENV NODE_ENV development
# Port 3000 for server
# Port 35729 for livereload
EXPOSE 3000 35729
CMD ["grunt"]

21
LICENSE.md Executable file
View File

@ -0,0 +1,21 @@
## License
(The MIT License)
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

1
Procfile Executable file
View File

@ -0,0 +1 @@
web: ./node_modules/.bin/forever -m 5 server.js

170
README.md Executable file
View File

@ -0,0 +1,170 @@
[![MEAN.JS Logo](http://meanjs.org/img/logo-small.png)](http://meanjs.org/)
[![Build Status](https://travis-ci.org/meanjs/mean.svg?branch=master)](https://travis-ci.org/meanjs/mean)
[![Dependencies Status](https://david-dm.org/meanjs/mean.svg)](https://david-dm.org/meanjs/mean)
[![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/meanjs/mean?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
MEAN.JS is a full-stack JavaScript open-source solution, which provides a solid starting point for [MongoDB](http://www.mongodb.org/), [Node.js](http://www.nodejs.org/), [Express](http://expressjs.com/), and [AngularJS](http://angularjs.org/) based applications. The idea is to solve the common issues with connecting those frameworks, build a robust framework to support daily development needs, and help developers use better practices while working with popular JavaScript components.
## Before You Begin
Before you begin we recommend you read about the basic building blocks that assemble a MEAN.JS application:
* MongoDB - Go through [MongoDB Official Website](http://mongodb.org/) and proceed to their [Official Manual](http://docs.mongodb.org/manual/), which should help you understand NoSQL and MongoDB better.
* Express - The best way to understand express is through its [Official Website](http://expressjs.com/), which has a [Getting Started](http://expressjs.com/starter/installing.html) guide, as well as an [ExpressJS Guide](http://expressjs.com/guide/error-handling.html) guide for general express topics. You can also go through this [StackOverflow Thread](http://stackoverflow.com/questions/8144214/learning-express-for-node-js) for more resources.
* AngularJS - Angular's [Official Website](http://angularjs.org/) is a great starting point. You can also use [Thinkster Popular Guide](http://www.thinkster.io/), and the [Egghead Videos](https://egghead.io/).
* Node.js - Start by going through [Node.js Official Website](http://nodejs.org/) and this [StackOverflow Thread](http://stackoverflow.com/questions/2353818/how-do-i-get-started-with-node-js), which should get you going with the Node.js platform in no time.
## Prerequisites
Make sure you have installed all of the following prerequisites on your development machine:
* Node.js - [Download & Install Node.js](http://www.nodejs.org/download/) and the npm package manager. If you encounter any problems, you can also use this [GitHub Gist](https://gist.github.com/isaacs/579814) to install Node.js.
* MongoDB - [Download & Install MongoDB](http://www.mongodb.org/downloads), and make sure it's running on the default port (27017).
* Bower - You're going to use the [Bower Package Manager](http://bower.io/) to manage your front-end packages. Make sure you've installed Node.js and npm first, then install bower globally using npm:
```bash
$ npm install -g bower
```
* Grunt - You're going to use the [Grunt Task Runner](http://gruntjs.com/) to automate your development process. Make sure you've installed Node.js and npm first, then install grunt globally using npm:
```bash
$ npm install -g grunt-cli
```
## Downloading MEAN.JS
There are several ways you can get the MEAN.JS boilerplate:
### Yo Generator
The recommended way would be to use the [Official Yo Generator](http://meanjs.org/generator.html), which generates the latest stable copy of the MEAN.JS boilerplate and supplies multiple sub-generators to ease your daily development cycles.
### Cloning The GitHub Repository
You can also use Git to directly clone the MEAN.JS repository:
```bash
$ git clone https://github.com/meanjs/mean.git meanjs
```
This will clone the latest version of the MEAN.JS repository to a **meanjs** folder.
### Downloading The Repository Zip File
Another way to use the MEAN.JS boilerplate is to download a zip copy from the [master branch on GitHub](https://github.com/meanjs/mean/archive/master.zip). You can also do this using `wget` command:
```bash
$ wget https://github.com/meanjs/mean/archive/master.zip -O meanjs.zip; unzip meanjs.zip; rm meanjs.zip
```
Don't forget to rename **mean-master** after your project name.
## Quick Install
Once you've downloaded the boilerplate and installed all the prerequisites, you're just a few steps away from starting to develop your MEAN application.
The first thing you should do is install the Node.js dependencies. The boilerplate comes pre-bundled with a package.json file that contains the list of modules you need to start your application. To learn more about the modules installed visit the NPM & Package.json section.
To install Node.js dependencies you're going to use npm again. In the application folder run this in the command-line:
```bash
$ npm install
```
This command does a few things:
* First it will install the dependencies needed for the application to run.
* If you're running in a development environment, it will then also install development dependencies needed for testing and running your application.
* Finally, when the install process is over, npm will initiate a bower install command to install all the front-end modules needed for the application.
## Running Your Application
After the install process is over, you'll be able to run your application using Grunt. Just run grunt default task:
```bash
$ grunt
```
Your application should run on port 3000, so in your browser just go to [http://localhost:3000](http://localhost:3000)
That's it! Your application should be running. To proceed with your development, check the other sections in this documentation.
If you encounter any problems, try the Troubleshooting section.
## Testing Your Application
You can run the full test suite included with MEAN.JS with the test task:
```
$ grunt test
```
This will run both the server-side tests (located in the app/tests/ directory) and the client-side tests (located in the public/modules/*/tests/).
To execute only the server tests, run the test:server task:
```
$ grunt test:server
```
And to run only the client tests, run the test:client task:
```
$ grunt test:client
```
## Development and deployment With Docker
* Install [Docker](http://www.docker.com/)
* Install [Fig](https://github.com/orchardup/fig)
* Local development and testing with fig:
```bash
$ fig up
```
* Local development and testing with just Docker:
```bash
$ docker build -t mean .
$ docker run -p 27017:27017 -d --name db mongo
$ docker run -p 3000:3000 --link db:db_1 mean
$
```
* To enable live reload, forward port 35729 and mount /app and /public as volumes:
```bash
$ docker run -p 3000:3000 -p 35729:35729 -v /Users/mdl/workspace/mean-stack/mean/public:/home/mean/public -v /Users/mdl/workspace/mean-stack/mean/app:/home/mean/app --link db:db_1 mean
```
## Running in a secure environment
To run your application in a secure manner you'll need to use OpenSSL and generate a set of self-signed certificates. Unix-based users can use the following command:
```bash
$ sh ./scripts/generate-ssl-certs.sh
```
Windows users can follow instructions found [here](http://www.websense.com/support/article/kbarticle/How-to-use-OpenSSL-and-Microsoft-Certification-Authority).
After you've generated the key and certificate, place them in the *config/sslcerts* folder.
## Getting Started With MEAN.JS
You have your application running, but there is a lot of stuff to understand. We recommend you go over the [Official Documentation](http://meanjs.org/docs.html).
In the docs we'll try to explain both general concepts of MEAN components and give you some guidelines to help you improve your development process. We tried covering as many aspects as possible, and will keep it updated by your request. You can also help us develop and improve the documentation by checking out the *gh-pages* branch of this repository.
## Community
* Use the [Official Website](http://meanjs.org) to learn about changes and the roadmap.
* Join #meanjs on freenode.
* Discuss it in the new [Google Group](https://groups.google.com/d/forum/meanjs)
* Ping us on [Twitter](http://twitter.com/meanjsorg) and [Facebook](http://facebook.com/meanjs)
## Live Example
Browse the live MEAN.JS example on [http://meanjs.herokuapp.com](http://meanjs.herokuapp.com).
## Credits
Inspired by the great work of [Madhusudhan Srinivasa](https://github.com/madhums/)
The MEAN name was coined by [Valeri Karpov](http://blog.mongodb.org/post/49262866911/the-mean-stack-mongodb-expressjs-angularjs-and)
## License
(The MIT License)
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,120 @@
'use strict';
/**
* Module dependencies.
*/
var mongoose = require('mongoose'),
errorHandler = require('./errors.server.controller'),
Article = mongoose.model('Article'),
_ = require('lodash');
/**
* Create a article
*/
exports.create = function(req, res) {
var article = new Article(req.body);
article.user = req.user;
article.save(function(err) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.json(article);
}
});
};
/**
* Show the current article
*/
exports.read = function(req, res) {
res.json(req.article);
};
/**
* Update a article
*/
exports.update = function(req, res) {
var article = req.article;
article = _.extend(article, req.body);
article.save(function(err) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.json(article);
}
});
};
/**
* Delete an article
*/
exports.delete = function(req, res) {
var article = req.article;
article.remove(function(err) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.json(article);
}
});
};
/**
* List of Articles
*/
exports.list = function(req, res) {
Article.find().sort('-created').populate('user', 'displayName').exec(function(err, articles) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.json(articles);
}
});
};
/**
* Article middleware
*/
exports.articleByID = function(req, res, next, id) {
if (!mongoose.Types.ObjectId.isValid(id)) {
return res.status(400).send({
message: 'Article is invalid'
});
}
Article.findById(id).populate('user', 'displayName').exec(function(err, article) {
if (err) return next(err);
if (!article) {
return res.status(404).send({
message: 'Article not found'
});
}
req.article = article;
next();
});
};
/**
* Article authorization middleware
*/
exports.hasAuthorization = function(req, res, next) {
if (req.article.user.id !== req.user.id) {
return res.status(403).send({
message: 'User is not authorized'
});
}
next();
};

View File

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

View File

@ -0,0 +1,42 @@
'use strict';
/**
* Get unique error field name
*/
var getUniqueErrorMessage = function(err) {
var output;
try {
var fieldName = err.err.substring(err.err.lastIndexOf('.$') + 2, err.err.lastIndexOf('_1'));
output = fieldName.charAt(0).toUpperCase() + fieldName.slice(1) + ' already exists';
} catch (ex) {
output = 'Unique field already exists';
}
return output;
};
/**
* Get the error message from error object
*/
exports.getErrorMessage = function(err) {
var message = '';
if (err.code) {
switch (err.code) {
case 11000:
case 11001:
message = getUniqueErrorMessage(err);
break;
default:
message = 'Something went wrong';
}
} else {
for (var errName in err.errors) {
if (err.errors[errName].message) message = err.errors[errName].message;
}
}
return message;
};

View File

@ -0,0 +1,254 @@
/**
* Module dependencies.
*/
var mongoose = require('mongoose'),
errorHandler = require('./errors.server.controller'),
Form = mongoose.model('Form'),
FormSubmission = mongoose.model('FormSubmission'),
pdfFiller = require( 'pdffiller' ),
PDFParser = require('pdf2json/pdfparser'),
config = require('../../config/config'),
fs = require('fs-extra'),
async = require('async'),
_ = require('lodash');
/**
* Create a new form manually
*/
exports.create = function(req, res) {
var form = new Form(req.body);
form.admin = req.user;
form.save(function(err) {
if (err) {
console.log(err);
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
return res.json(form);
}
});
};
/**
* Upload PDF
*/
exports.uploadPDF = function(req, res) {
var parser = new PDFParser(),
pdfFile = req.files.file;
console.log(pdfFile);
var form = Form.findById(req.body.form._id);
console.log(req.files);
if (req.files) {
if (pdfFile.size === 0) {
return next(new Error('Hey, first would you select a file?'));
}
fs.exists(pdfFile.path, function(exists) {
if(exists) {
console.log("UPLOADING FILE \N\N");
// res.end('Got your file!');
// next()
return res.status(200);
} else {
// res.end('DID NOT get your file!');
// next()
return res.status(400);
}
});
}
// next(new Error('FILE NOT UPLOADED'));
return res.status(400);
// res.json(pdfFile);
};
/**
* Show the current form
*/
exports.read = function(req, res) {
// console.log(req.form);
res.json(req.form);
};
/**
* Submit a form entry
*/
exports.createSubmission = function(req, res) {
var submission = new FormSubmission(),
form = req.form, fdfData;
submission.form = form;
submission.admin = req.user;
submission.form_fields = req.body.form_fields;
submission.title = req.body.title;
submission.timeElapsed = req.body.timeElapsed;
if (form.isGenerated){
fdfData = form.convertToJSON();
} else {
try {
fdfData = pdfFiller.mapForm2PDF(form.convertToJSON(), form.pdfFieldMap);
} catch(err){
throw new Error(err.message);
}
}
submission.fdfData = fdfData;
//Create new file
pdfFiller.fillForm( form.pdf.path, config.pdfUploadPath+form.title+"/"+form.title+"_"+Date.now()+"_submission.pdf", fdfData, function() {
console.log("\n\n\n fdfData");
console.log(fdfData);
});
// submission.ipAddr = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
submission.save(function(err){
if (err) {
console.error(err);
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
return res.status(200);
}
});
};
/**
* Get List of Submissions for a given Template Form
*/
exports.listSubmissions = function(req, res) {
var _form = req.form;
FormSubmission.find({ form: req.form }).exec(function(err, submissions) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
return res.json(submissions);
}
});
};
/**
* Update a form
*/
exports.update = function(req, res) {
var form = req.form;
form = _.extend(form, req.body);
form.admin = req.user;
form.save(function(err) {
if (err) {
console.log(err);
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
console.log('updated form');
return res.json(form);
}
});
};
/**
* Delete a form
*/
exports.delete = function(req, res) {
var form = req.form;
form.remove(function(err) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
return res.status(200);
// res.json(form);
}
});
};
/**
* Get List of Template Forms
*/
exports.list = function(req, res) {
Form.find({ type: 'template' }).sort('-created').populate('admin').exec(function(err, forms) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
console.log(forms);
return res.json(forms);
}
});
};
/**
* Form middleware
*/
exports.formByID = function(req, res, next, id) {
if (!mongoose.Types.ObjectId.isValid(id)) {
return res.status(400).send({
message: 'Form is invalid'
});
}
Form.findById(id).populate('admin').exec(function(err, form) {
if (err) return next(err);
if (!form) {
return res.status(404).send({
message: 'Form not found'
});
}
//Remove sensitive information from User object
form.admin.password = null;
form.admin.created = null;
form.admin.salt = null;
req.form = form;
next();
});
};
/**
* Form authorization middleware
*/
exports.hasAuthorization = function(req, res, next) {
var form = req.form;
// console.log('\n\n\nreq.form:\n');
// console.log(form);
// console.log('req.user.id: '+req.user.id);
if (req.form.admin.id !== req.user.id) {
return res.status(403).send({
message: 'User is not authorized'
});
}
next();
};

View File

@ -0,0 +1,16 @@
'use strict';
/**
* Module dependencies.
*/
var _ = require('lodash');
/**
* Extend user's controller
*/
module.exports = _.extend(
require('./users/users.authentication.server.controller'),
require('./users/users.authorization.server.controller'),
require('./users/users.password.server.controller'),
require('./users/users.profile.server.controller')
);

View File

@ -0,0 +1,206 @@
'use strict';
/**
* Module dependencies.
*/
var _ = require('lodash'),
errorHandler = require('../errors.server.controller'),
mongoose = require('mongoose'),
passport = require('passport'),
User = mongoose.model('User');
/**
* Signup
*/
exports.signup = function(req, res) {
// For security measurement we remove the roles from the req.body object
delete req.body.roles;
// Init Variables
var user = new User(req.body);
var message = null;
// Add missing user fields
user.provider = 'local';
user.displayName = user.firstName + ' ' + user.lastName;
// Then save the user
user.save(function(err) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
// Remove sensitive data before login
user.password = undefined;
user.salt = undefined;
req.login(user, function(err) {
if (err) {
res.status(400).send(err);
} else {
res.json(user);
}
});
}
});
};
/**
* Signin after passport authentication
*/
exports.signin = function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err || !user) {
res.status(400).send(info);
} else {
// Remove sensitive data before login
user.password = undefined;
user.salt = undefined;
req.login(user, function(err) {
if (err) {
res.status(400).send(err);
} else {
res.json(user);
}
});
}
})(req, res, next);
};
/**
* Signout
*/
exports.signout = function(req, res) {
req.logout();
res.redirect('/');
};
/**
* OAuth callback
*/
exports.oauthCallback = function(strategy) {
return function(req, res, next) {
passport.authenticate(strategy, function(err, user, redirectURL) {
if (err || !user) {
return res.redirect('/#!/signin');
}
req.login(user, function(err) {
if (err) {
return res.redirect('/#!/signin');
}
return res.redirect(redirectURL || '/');
});
})(req, res, next);
};
};
/**
* Helper function to save or update a OAuth user profile
*/
exports.saveOAuthUserProfile = function(req, providerUserProfile, done) {
if (!req.user) {
// Define a search query fields
var searchMainProviderIdentifierField = 'providerData.' + providerUserProfile.providerIdentifierField;
var searchAdditionalProviderIdentifierField = 'additionalProvidersData.' + providerUserProfile.provider + '.' + providerUserProfile.providerIdentifierField;
// Define main provider search query
var mainProviderSearchQuery = {};
mainProviderSearchQuery.provider = providerUserProfile.provider;
mainProviderSearchQuery[searchMainProviderIdentifierField] = providerUserProfile.providerData[providerUserProfile.providerIdentifierField];
// Define additional provider search query
var additionalProviderSearchQuery = {};
additionalProviderSearchQuery[searchAdditionalProviderIdentifierField] = providerUserProfile.providerData[providerUserProfile.providerIdentifierField];
// Define a search query to find existing user with current provider profile
var searchQuery = {
$or: [mainProviderSearchQuery, additionalProviderSearchQuery]
};
User.findOne(searchQuery, function(err, user) {
if (err) {
return done(err);
} else {
if (!user) {
var possibleUsername = providerUserProfile.username || ((providerUserProfile.email) ? providerUserProfile.email.split('@')[0] : '');
User.findUniqueUsername(possibleUsername, null, function(availableUsername) {
user = new User({
firstName: providerUserProfile.firstName,
lastName: providerUserProfile.lastName,
username: availableUsername,
displayName: providerUserProfile.displayName,
email: providerUserProfile.email,
provider: providerUserProfile.provider,
providerData: providerUserProfile.providerData
});
// And save the user
user.save(function(err) {
return done(err, user);
});
});
} else {
return done(err, user);
}
}
});
} else {
// User is already logged in, join the provider data to the existing user
var user = req.user;
// Check if user exists, is not signed in using this provider, and doesn't have that provider data already configured
if (user.provider !== providerUserProfile.provider && (!user.additionalProvidersData || !user.additionalProvidersData[providerUserProfile.provider])) {
// Add the provider data to the additional provider data field
if (!user.additionalProvidersData) user.additionalProvidersData = {};
user.additionalProvidersData[providerUserProfile.provider] = providerUserProfile.providerData;
// Then tell mongoose that we've updated the additionalProvidersData field
user.markModified('additionalProvidersData');
// And save the user
user.save(function(err) {
return done(err, user, '/#!/settings/accounts');
});
} else {
return done(new Error('User is already connected using this provider'), user);
}
}
};
/**
* Remove OAuth provider
*/
exports.removeOAuthProvider = function(req, res, next) {
var user = req.user;
var provider = req.param('provider');
if (user && provider) {
// Delete the additional provider
if (user.additionalProvidersData[provider]) {
delete user.additionalProvidersData[provider];
// Then tell mongoose that we've updated the additionalProvidersData field
user.markModified('additionalProvidersData');
}
user.save(function(err) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
req.login(user, function(err) {
if (err) {
res.status(400).send(err);
} else {
res.json(user);
}
});
}
});
}
};

View File

@ -0,0 +1,53 @@
'use strict';
/**
* Module dependencies.
*/
var _ = require('lodash'),
mongoose = require('mongoose'),
User = mongoose.model('User');
/**
* User middleware
*/
exports.userByID = function(req, res, next, id) {
User.findById(id).exec(function(err, user) {
if (err) return next(err);
if (!user) return next(new Error('Failed to load User ' + id));
req.profile = user;
next();
});
};
/**
* Require login routing middleware
*/
exports.requiresLogin = function(req, res, next) {
if (!req.isAuthenticated()) {
return res.status(401).send({
message: 'User is not logged in'
});
}
next();
};
/**
* User authorizations routing middleware
*/
exports.hasAuthorization = function(roles) {
var _this = this;
return function(req, res, next) {
_this.requiresLogin(req, res, function() {
if (_.intersection(req.user.roles, roles).length) {
return next();
} else {
return res.status(403).send({
message: 'User is not authorized'
});
}
});
};
};

View File

@ -0,0 +1,249 @@
'use strict';
/**
* Module dependencies.
*/
var _ = require('lodash'),
errorHandler = require('../errors.server.controller'),
mongoose = require('mongoose'),
passport = require('passport'),
User = mongoose.model('User'),
config = require('../../../config/config'),
nodemailer = require('nodemailer'),
async = require('async'),
crypto = require('crypto');
var smtpTransport = nodemailer.createTransport(config.mailer.options);
/**
* Forgot for reset password (forgot POST)
*/
exports.forgot = function(req, res, next) {
async.waterfall([
// Generate random token
function(done) {
crypto.randomBytes(20, function(err, buffer) {
var token = buffer.toString('hex');
done(err, token);
});
},
// Lookup user by username
function(token, done) {
if (req.body.username) {
User.findOne({
username: req.body.username
}, '-salt -password', function(err, user) {
if (!user) {
return res.status(400).send({
message: 'No account with that username has been found'
});
} else if (user.provider !== 'local') {
return res.status(400).send({
message: 'It seems like you signed up using your ' + user.provider + ' account'
});
} else {
user.resetPasswordToken = token;
user.resetPasswordExpires = Date.now() + 3600000; // 1 hour
user.save(function(err) {
done(err, token, user);
});
}
});
} else {
return res.status(400).send({
message: 'Username field must not be blank'
});
}
},
function(token, user, done) {
res.render('templates/reset-password-email', {
name: user.displayName,
appName: config.app.title,
url: 'http://' + req.headers.host + '/auth/reset/' + token
}, function(err, emailHTML) {
done(err, emailHTML, user);
});
},
// If valid email, send reset email using service
function(emailHTML, user, done) {
var mailOptions = {
to: user.email,
from: config.mailer.from,
subject: 'Password Reset',
html: emailHTML
};
smtpTransport.sendMail(mailOptions, function(err) {
if (!err) {
res.send({
message: 'An email has been sent to ' + user.email + ' with further instructions.'
});
} else {
return res.status(400).send({
message: 'Failure sending email'
});
}
done(err);
});
}
], function(err) {
if (err) return next(err);
});
};
/**
* Reset password GET from email token
*/
exports.validateResetToken = function(req, res) {
User.findOne({
resetPasswordToken: req.params.token,
resetPasswordExpires: {
$gt: Date.now()
}
}, function(err, user) {
if (!user) {
return res.redirect('/#!/password/reset/invalid');
}
res.redirect('/#!/password/reset/' + req.params.token);
});
};
/**
* Reset password POST from email token
*/
exports.reset = function(req, res, next) {
// Init Variables
var passwordDetails = req.body;
async.waterfall([
function(done) {
User.findOne({
resetPasswordToken: req.params.token,
resetPasswordExpires: {
$gt: Date.now()
}
}, function(err, user) {
if (!err && user) {
if (passwordDetails.newPassword === passwordDetails.verifyPassword) {
user.password = passwordDetails.newPassword;
user.resetPasswordToken = undefined;
user.resetPasswordExpires = undefined;
user.save(function(err) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
req.login(user, function(err) {
if (err) {
res.status(400).send(err);
} else {
// Return authenticated user
res.json(user);
done(err, user);
}
});
}
});
} else {
return res.status(400).send({
message: 'Passwords do not match'
});
}
} else {
return res.status(400).send({
message: 'Password reset token is invalid or has expired.'
});
}
});
},
function(user, done) {
res.render('templates/reset-password-confirm-email', {
name: user.displayName,
appName: config.app.title
}, function(err, emailHTML) {
done(err, emailHTML, user);
});
},
// If valid email, send reset email using service
function(emailHTML, user, done) {
var mailOptions = {
to: user.email,
from: config.mailer.from,
subject: 'Your password has been changed',
html: emailHTML
};
smtpTransport.sendMail(mailOptions, function(err) {
done(err, 'done');
});
}
], function(err) {
if (err) return next(err);
});
};
/**
* Change Password
*/
exports.changePassword = function(req, res) {
// Init Variables
var passwordDetails = req.body;
if (req.user) {
if (passwordDetails.newPassword) {
User.findById(req.user.id, function(err, user) {
if (!err && user) {
if (user.authenticate(passwordDetails.currentPassword)) {
if (passwordDetails.newPassword === passwordDetails.verifyPassword) {
user.password = passwordDetails.newPassword;
user.save(function(err) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
req.login(user, function(err) {
if (err) {
res.status(400).send(err);
} else {
res.send({
message: 'Password changed successfully'
});
}
});
}
});
} else {
res.status(400).send({
message: 'Passwords do not match'
});
}
} else {
res.status(400).send({
message: 'Current password is incorrect'
});
}
} else {
res.status(400).send({
message: 'User is not found'
});
}
});
} else {
res.status(400).send({
message: 'Please provide a new password'
});
}
} else {
res.status(400).send({
message: 'User is not signed in'
});
}
};

View File

@ -0,0 +1,56 @@
'use strict';
/**
* Module dependencies.
*/
var _ = require('lodash'),
errorHandler = require('../errors.server.controller.js'),
mongoose = require('mongoose'),
passport = require('passport'),
User = mongoose.model('User');
/**
* Update user details
*/
exports.update = function(req, res) {
// Init Variables
var user = req.user;
var message = null;
// For security measurement we remove the roles from the req.body object
delete req.body.roles;
if (user) {
// Merge existing user
user = _.extend(user, req.body);
user.updated = Date.now();
user.displayName = user.firstName + ' ' + user.lastName;
user.save(function(err) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
req.login(user, function(err) {
if (err) {
res.status(400).send(err);
} else {
res.json(user);
}
});
}
});
} else {
res.status(400).send({
message: 'User is not signed in'
});
}
};
/**
* Send User
*/
exports.me = function(req, res) {
res.json(req.user || null);
};

View File

@ -0,0 +1,34 @@
'use strict';
/**
* Module dependencies.
*/
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
/**
* Article Schema
*/
var ArticleSchema = new Schema({
created: {
type: Date,
default: Date.now
},
title: {
type: String,
default: '',
trim: true,
required: 'Title cannot be blank'
},
content: {
type: String,
default: '',
trim: true
},
user: {
type: Schema.ObjectId,
ref: 'User'
}
});
mongoose.model('Article', ArticleSchema);

View File

@ -0,0 +1,182 @@
'use strict';
/**
* Module dependencies.
*/
var mongoose = require('mongoose'),
FieldSchema = require('./form_field.server.model.js'),
Schema = mongoose.Schema,
pdfFiller = require('pdfFiller'),
_ = require('lodash'),
config = require('../../config/config'),
path = require('path'),
fs = require('fs-extra');
var Field = mongoose.model('Field', FieldSchema);
/**
* Form Schema
*/
var FormSchema = new Schema({
created: {
type: Date,
default: Date.now
},
// type: {
// type: String,
// default: 'template',
// enum: ['submission', 'template']
// },
title: {
type: String,
default: '',
trim: true,
unique: true,
required: 'Title cannot be blank'
},
description: {
type: String,
default: '',
},
form_fields: [Schema.Types.Mixed],
submission: [{
type: Schema.Types.ObjectId,
ref: 'FormSubmission'
}],
admin: {
type: Schema.Types.ObjectId,
ref: 'User'
},
pdf: {
type: Schema.Types.Mixed
},
pdfFieldMap: {
type: Schema.Types.Mixed
},
isGenerated: {
type: Boolean,
default: false,
},
autofillPDFs: {
type: Boolean,
default: false,
},
});
FormSchema.pre('save', function (next) {
// console.log(this.pdf);
// debugger;
//Move PDF to permanent location after first save
if(this.pdf){
if(this.pdf.modified){
var new_filename = this.pdf.title.trim()+'_template.pdf';
// TODO: DAVID - need to remove dependence on relative paths
var newDestination = path.join(config.pdfUploadPath+this.title+"/", this.pdf.title.trim()),
stat = null;
try {
stat = fs.statSync(newDestination);
} catch (err) {
fs.mkdirSync(newDestination);
}
if (stat && !stat.isDirectory()) {
console.log('Directory cannot be created');
next( new Error('Directory cannot be created because an inode of a different type exists at "' + config.pdfUploadPath + '"') );
}
console.log('about to move PDF');
fs.move(this.pdf.path, path.join(newDestination, new_filename), function (err) {
if (err) {
console.error(err);
next( new Error(err.message) );
}
console.log('PDF file successfully moved');
this.pdf.path = path.join(newDestination, new_filename);
this.pdf.name = new_filename;
next();
});
}
}else {
next();
}
});
// FormSchema.pre('save', function (next) {
// //Autogenerate FORM from PDF
// if(this.isGenerated && this.pdf && this.autofillPDFs){
// this.autofillPDFs = false;
// var _pdfConvMap = {
// 'Text': 'textfield',
// 'Button': 'checkbox'
// };
// var that = this;
// console.log('autogenerating form');
// try {
// pdfFiller.generateFieldJson(this.pdf.path, function(_form_fields){
// _form_fields.forEach(function(field){
// if(_pdfConvMap[ field.fieldType+'' ]){
// field.fieldType = _pdfConvMap[ field.fieldType+'' ];
// }
// field.created = Date.now();
// field.fieldValue = '';
// field.required = true;
// field.disabled = false;
// // field = new Field(field);
// // field.save()
// });
// // console.log('NEW FORM_FIELDS: ');
// // console.log(_form_fields);
// // console.log('\n\nOLD FORM_FIELDS: ');
// // console.log(that.form_fields);
// that.form_fields = _form_fields;
// next();
// });
// } catch(err){
// next( new Error(err.message) );
// }
// }
// //Throw error if we encounter form with invalid type
// next();
// });
FormSchema.methods.convertToJSON = function (cb) {
var _keys = _.pluck(this.form_fields, 'title'),
_values = _.pluck(this.form_fields, 'fieldValue');
_values.forEach(function(val){
if(val === true){
val = 'Yes';
}else if(val === false) {
val = 'Off';
}
});
var jsonObj = _.zipObject(_keys, _values);
return jsonObj;
};
mongoose.model('Form', FormSchema);

View File

@ -0,0 +1,69 @@
'use strict';
/**
* Module dependencies.
*/
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
// questionType Validation
function validateFormFieldType(value) {
if (!value || typeof myVar !== 'string' ) { return false; }
var validTypes = [
'textfield',
'email',
'url',
'textarea',
'checkbox',
'date',
'dropdown',
'hidden',
'password',
'radio'
];
if (validTypes.indexOf(value) > -1) {
return true;
}
return false;
}
/**
* Question Schema
*/
var FormFieldSchema = new Schema({
created: {
type: Date,
default: Date.now
},
title: {
type: String,
default: '',
trim: true,
required: 'Title cannot be blank'
},
description: {
type: String,
default: '',
},
required: {
type: Boolean,
default: false,
},
disabled: {
type: Boolean,
default: false,
},
fieldType: {
type: String,
required: 'Field type cannot be blank',
validate: [validateFormFieldType, 'Invalid field type']
},
fieldValue: Schema.Types.Mixed
});
module.exports = FormFieldSchema;
// mongoose.model('Field', FormFieldSchema);

View File

@ -0,0 +1,114 @@
'use strict';
/**
* Module dependencies.
*/
var mongoose = require('mongoose'),
Schema = mongoose.Schema,
pdfFiller = require('pdfFiller'),
satelize = require('satelize'),
_ = require('lodash'),
config = require('../../config/config'),
path = require('path'),
Form = mongoose.model('Form'),
fs = require('fs-extra');
/**
* Form Submission Schema
*/
var FormSubmissionSchema = new Schema({
title: {
type: String,
required: true,
},
created: {
type: Date,
default: Date.now
},
admin: {
type: Schema.Types.ObjectId,
ref: 'User',
},
form_fields: [Schema.Types.Mixed],
form: {
type: Schema.Types.ObjectId,
ref: 'Form',
required: true
},
ipAddr: {
type: String,
required: false
},
geoLocation: {
type: Schema.Types.Mixed,
},
pdfFilePath: {
type: Schema.Types.Mixed,
},
fdfData: {
type: Schema.Types.Mixed,
},
timeElapsed: { //time (in seconds) it took for user to submit form
type: Number,
},
});
FormSubmissionSchema.pre('save', function (next){
if(this.ipAddr){
if(this.ipAddr.modified){
satelize.satelize({ip: this.ipAddr}, function(err, geoData){
if (err) next( new Error(err.message) );
this.geoLocation = JSON.parse(geoData);
next();
});
}
}
console.log('ipAddr check');
next();
});
FormSubmissionSchema.pre('save', function (next) {
// debugger;
var fdfData, dest_filename, dest_path;
var that = this;
Form.findById(that.form, function(err, _form){
if(err) next( new Error(err.mesasge) );
this.title = _form.title;
console.log(_form);
//Create filled-out PDF, if there is a pdf template
if(_form.autofillPDFs){
dest_filename = this.title.trim()+'_submission_'+Date.now()+'.pdf';
dest_path = path.join(config.pdfUploadPath, this.title.trim(), dest_filename);
this.pdfFilePath = dest_path;
console.log('autofillPDFs check');
pdfFiller.fillForm(_form.pdf.path, dest_path, this.fdfData, function(err){
if(err) next( new Error(err.message) );
console.log('Field data from Form: '+this.title.trim()+' outputed to new PDF: '+dest_path);
next();
});
} else {
next();
}
});
});
mongoose.model('FormSubmission', FormSubmissionSchema);

147
app/models/user.server.model.js Executable file
View File

@ -0,0 +1,147 @@
'use strict';
/**
* Module dependencies.
*/
var mongoose = require('mongoose'),
Schema = mongoose.Schema,
crypto = require('crypto');
/**
* A Validation function for local strategy properties
*/
var validateLocalStrategyProperty = function(property) {
return ((this.provider !== 'local' && !this.updated) || property.length);
};
/**
* A Validation function for local strategy password
*/
var validateLocalStrategyPassword = function(password) {
return (this.provider !== 'local' || (password && password.length > 6));
};
/**
* User Schema
*/
var UserSchema = new Schema({
firstName: {
type: String,
trim: true,
default: '',
validate: [validateLocalStrategyProperty, 'Please fill in your first name']
},
lastName: {
type: String,
trim: true,
default: '',
validate: [validateLocalStrategyProperty, 'Please fill in your last name']
},
displayName: {
type: String,
trim: true
},
email: {
type: String,
trim: true,
default: '',
validate: [validateLocalStrategyProperty, 'Please fill in your email'],
match: [/.+\@.+\..+/, 'Please fill a valid email address']
},
username: {
type: String,
unique: 'Username already exists',
required: 'Please fill in a username',
trim: true
},
password: {
type: String,
default: '',
validate: [validateLocalStrategyPassword, 'Password should be longer']
},
salt: {
type: String
},
provider: {
type: String,
required: 'Provider is required'
},
providerData: {},
additionalProvidersData: {},
roles: {
type: [{
type: String,
enum: ['user', 'admin']
}],
default: ['user']
},
updated: {
type: Date
},
created: {
type: Date,
default: Date.now
},
/* For reset password */
resetPasswordToken: {
type: String
},
resetPasswordExpires: {
type: Date
},
token: String
});
/**
* Hook a pre save method to hash the password
*/
UserSchema.pre('save', function(next) {
if (this.password && this.password.length > 6) {
this.salt = crypto.randomBytes(16).toString('base64');
this.password = this.hashPassword(this.password);
}
next();
});
/**
* Create instance method for hashing a password
*/
UserSchema.methods.hashPassword = function(password) {
if (this.salt && password) {
return crypto.pbkdf2Sync(password, new Buffer(this.salt, 'base64'), 10000, 64).toString('base64');
} else {
return password;
}
};
/**
* Create instance method for authenticating user
*/
UserSchema.methods.authenticate = function(password) {
return this.password === this.hashPassword(password);
};
/**
* Find possible not used username
*/
UserSchema.statics.findUniqueUsername = function(username, suffix, callback) {
var _this = this;
var possibleUsername = username + (suffix || '');
_this.findOne({
username: possibleUsername
}, function(err, user) {
if (!err) {
if (!user) {
callback(possibleUsername);
} else {
return _this.findUniqueUsername(username, (suffix || 0) + 1, callback);
}
} else {
callback(null);
}
});
};
mongoose.model('User', UserSchema);

View File

@ -0,0 +1,22 @@
'use strict';
/**
* Module dependencies.
*/
var users = require('../../app/controllers/users.server.controller'),
articles = require('../../app/controllers/articles.server.controller');
module.exports = function(app) {
// Article Routes
app.route('/articles')
.get(articles.list)
.post(users.requiresLogin, articles.create);
app.route('/articles/:articleId')
.get(articles.read)
.put(users.requiresLogin, articles.hasAuthorization, articles.update)
.delete(users.requiresLogin, articles.hasAuthorization, articles.delete);
// Finish by binding the article middleware
app.param('articleId', articles.articleByID);
};

View File

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

View File

@ -0,0 +1,26 @@
'use strict';
/**
* Module dependencies.
*/
var users = require('../../app/controllers/users.server.controller'),
forms = require('../../app/controllers/forms.server.controller');
module.exports = function(app) {
// Form Routes
app.route('/upload/pdf')
.post(forms.uploadPDF);
app.route('/forms')
.get(forms.list)
.post(users.requiresLogin, forms.create);
app.route('/forms/:formId')
.get(forms.read)
.post(forms.createSubmission)
.put(users.requiresLogin, forms.hasAuthorization, forms.update)
.delete(users.requiresLogin, forms.delete);
// Finish by binding the form middleware
app.param('formId', forms.formByID);
};

View File

@ -0,0 +1,57 @@
'use strict';
/**
* Module dependencies.
*/
var passport = require('passport');
module.exports = function(app) {
// User Routes
var users = require('../../app/controllers/users.server.controller');
// Setting up the users profile api
app.route('/users/me').get(users.me);
app.route('/users').put(users.update);
app.route('/users/accounts').delete(users.removeOAuthProvider);
// Setting up the users password api
app.route('/users/password').post(users.changePassword);
app.route('/auth/forgot').post(users.forgot);
app.route('/auth/reset/:token').get(users.validateResetToken);
app.route('/auth/reset/:token').post(users.reset);
// Setting up the users authentication api
app.route('/auth/signup').post(users.signup);
app.route('/auth/signin').post(users.signin);
app.route('/auth/signout').get(users.signout);
// Setting the facebook oauth routes
app.route('/auth/facebook').get(passport.authenticate('facebook', {
scope: ['email']
}));
app.route('/auth/facebook/callback').get(users.oauthCallback('facebook'));
// Setting the twitter oauth routes
app.route('/auth/twitter').get(passport.authenticate('twitter'));
app.route('/auth/twitter/callback').get(users.oauthCallback('twitter'));
// Setting the google oauth routes
app.route('/auth/google').get(passport.authenticate('google', {
scope: [
'https://www.googleapis.com/auth/userinfo.profile',
'https://www.googleapis.com/auth/userinfo.email'
]
}));
app.route('/auth/google/callback').get(users.oauthCallback('google'));
// Setting the linkedin oauth routes
app.route('/auth/linkedin').get(passport.authenticate('linkedin'));
app.route('/auth/linkedin/callback').get(users.oauthCallback('linkedin'));
// Setting the github oauth routes
app.route('/auth/github').get(passport.authenticate('github'));
app.route('/auth/github/callback').get(users.oauthCallback('github'));
// Finish by binding the user middleware
app.param('userId', users.userByID);
};

View File

@ -0,0 +1,64 @@
'use strict';
/**
* Module dependencies.
*/
var should = require('should'),
mongoose = require('mongoose'),
User = mongoose.model('User'),
Article = mongoose.model('Article');
/**
* Globals
*/
var user, article;
/**
* Unit tests
*/
describe('Article Model Unit Tests:', 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(function() {
User.remove().exec(done);
});
});
});

View File

@ -0,0 +1,280 @@
'use strict';
var should = require('should'),
request = require('supertest'),
app = require('../../server'),
mongoose = require('mongoose'),
User = mongoose.model('User'),
Article = mongoose.model('Article'),
agent = request.agent(app);
/**
* Globals
*/
var credentials, user, article;
/**
* Article routes tests
*/
describe('Article CRUD tests', function() {
beforeEach(function(done) {
// Create user credentials
credentials = {
username: 'username',
password: 'password'
};
// Create a new user
user = new User({
firstName: 'Full',
lastName: 'Name',
displayName: 'Full Name',
email: 'test@test.com',
username: credentials.username,
password: credentials.password,
provider: 'local'
});
// Save a user to the test db and create new article
user.save(function() {
article = {
title: 'Article Title',
content: 'Article Content'
};
done();
});
});
it('should be able to save an article if logged in', function(done) {
agent.post('/auth/signin')
.send(credentials)
.expect(200)
.end(function(signinErr, signinRes) {
// Handle signin error
if (signinErr) done(signinErr);
// Get the userId
var userId = user.id;
// Save a new article
agent.post('/articles')
.send(article)
.expect(200)
.end(function(articleSaveErr, articleSaveRes) {
// Handle article save error
if (articleSaveErr) done(articleSaveErr);
// Get a list of articles
agent.get('/articles')
.end(function(articlesGetErr, articlesGetRes) {
// Handle article save error
if (articlesGetErr) done(articlesGetErr);
// Get articles list
var articles = articlesGetRes.body;
// Set assertions
(articles[0].user._id).should.equal(userId);
(articles[0].title).should.match('Article Title');
// Call the assertion callback
done();
});
});
});
});
it('should not be able to save an article if not logged in', function(done) {
agent.post('/articles')
.send(article)
.expect(401)
.end(function(articleSaveErr, articleSaveRes) {
// Call the assertion callback
done(articleSaveErr);
});
});
it('should not be able to save an article if no title is provided', function(done) {
// Invalidate title field
article.title = '';
agent.post('/auth/signin')
.send(credentials)
.expect(200)
.end(function(signinErr, signinRes) {
// Handle signin error
if (signinErr) done(signinErr);
// Get the userId
var userId = user.id;
// Save a new article
agent.post('/articles')
.send(article)
.expect(400)
.end(function(articleSaveErr, articleSaveRes) {
// Set message assertion
(articleSaveRes.body.message).should.match('Title cannot be blank');
// Handle article save error
done(articleSaveErr);
});
});
});
it('should be able to update an article if signed in', function(done) {
agent.post('/auth/signin')
.send(credentials)
.expect(200)
.end(function(signinErr, signinRes) {
// Handle signin error
if (signinErr) done(signinErr);
// Get the userId
var userId = user.id;
// Save a new article
agent.post('/articles')
.send(article)
.expect(200)
.end(function(articleSaveErr, articleSaveRes) {
// Handle article save error
if (articleSaveErr) done(articleSaveErr);
// Update article title
article.title = 'WHY YOU GOTTA BE SO MEAN?';
// Update an existing article
agent.put('/articles/' + articleSaveRes.body._id)
.send(article)
.expect(200)
.end(function(articleUpdateErr, articleUpdateRes) {
// Handle article update error
if (articleUpdateErr) done(articleUpdateErr);
// Set assertions
(articleUpdateRes.body._id).should.equal(articleSaveRes.body._id);
(articleUpdateRes.body.title).should.match('WHY YOU GOTTA BE SO MEAN?');
// Call the assertion callback
done();
});
});
});
});
it('should be able to get a list of articles if not signed in', function(done) {
// Create new article model instance
var articleObj = new Article(article);
// Save the article
articleObj.save(function() {
// Request articles
request(app).get('/articles')
.end(function(req, res) {
// Set assertion
res.body.should.be.an.Array.with.lengthOf(1);
// Call the assertion callback
done();
});
});
});
it('should be able to get a single article if not signed in', function(done) {
// Create new article model instance
var articleObj = new Article(article);
// Save the article
articleObj.save(function() {
request(app).get('/articles/' + articleObj._id)
.end(function(req, res) {
// Set assertion
res.body.should.be.an.Object.with.property('title', article.title);
// Call the assertion callback
done();
});
});
});
it('should return proper error for single article which doesnt exist, if not signed in', function(done) {
request(app).get('/articles/test')
.end(function(req, res) {
// Set assertion
res.body.should.be.an.Object.with.property('message', 'Article is invalid');
// Call the assertion callback
done();
});
});
it('should be able to delete an article if signed in', function(done) {
agent.post('/auth/signin')
.send(credentials)
.expect(200)
.end(function(signinErr, signinRes) {
// Handle signin error
if (signinErr) done(signinErr);
// Get the userId
var userId = user.id;
// Save a new article
agent.post('/articles')
.send(article)
.expect(200)
.end(function(articleSaveErr, articleSaveRes) {
// Handle article save error
if (articleSaveErr) done(articleSaveErr);
// Delete an existing article
agent.delete('/articles/' + articleSaveRes.body._id)
.send(article)
.expect(200)
.end(function(articleDeleteErr, articleDeleteRes) {
// Handle article error error
if (articleDeleteErr) done(articleDeleteErr);
// Set assertions
(articleDeleteRes.body._id).should.equal(articleSaveRes.body._id);
// Call the assertion callback
done();
});
});
});
});
it('should not be able to delete an article if not signed in', function(done) {
// Set article user
article.user = user;
// Create new article model instance
var articleObj = new Article(article);
// Save the article
articleObj.save(function() {
// Try deleting article
request(app).delete('/articles/' + articleObj._id)
.expect(401)
.end(function(articleDeleteErr, articleDeleteRes) {
// Set message assertion
(articleDeleteRes.body.message).should.match('User is not logged in');
// Handle article error error
done(articleDeleteErr);
});
});
});
afterEach(function(done) {
User.remove().exec(function() {
Article.remove().exec(done);
});
});
});

View File

@ -0,0 +1,75 @@
'use strict';
/**
* Module dependencies.
*/
var should = require('should'),
mongoose = require('mongoose'),
User = mongoose.model('User');
/**
* Globals
*/
var user, user2;
/**
* Unit tests
*/
describe('User Model Unit Tests:', 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 without problems', function(done) {
user.save(done);
});
it('should fail to save an existing user again', function(done) {
user.save(function() {
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);
});
});

8
app/views/404.server.view.html Executable file
View File

@ -0,0 +1,8 @@
{% extends 'layout.server.view.html' %}
{% block content %}
<h1>Page Not Found</h1>
<pre>
{{url}} is not a valid path.
</pre>
{% endblock %}

8
app/views/500.server.view.html Executable file
View File

@ -0,0 +1,8 @@
{% extends 'layout.server.view.html' %}
{% block content %}
<h1>Server Error</h1>
<pre>
{{error}}
</pre>
{% endblock %}

View File

@ -0,0 +1,5 @@
{% extends 'layout.server.view.html' %}
{% block content %}
<section data-ui-view></section>
{% endblock %}

View File

@ -0,0 +1,82 @@
<!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,maximum-scale=1">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<!-- Semantic META -->
<meta name="keywords" content="{{keywords}}">
<meta name="description" content="{{description}}">
<!-- Facebook 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">
<!-- Twitter META -->
<meta name="twitter:title" content="{{title}}">
<meta name="twitter:description" content="{{description}}">
<meta name="twitter:url" content="{{url}}">
<meta name="twitter:image" content="/img/brand/logo.png">
<!-- Fav Icon -->
<link href="/modules/core/img/brand/favicon.ico" rel="shortcut icon" type="image/x-icon">
<!--Application CSS Files-->
{% for cssFile in cssFiles %}
<link rel="stylesheet" href="{{cssFile}}">
{% endfor %}
<!-- HTML5 Shim -->
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
</head>
<body class="ng-cloak" >
<header data-ng-include="'/modules/core/views/header.client.view.html'"></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>
<!--Application JavaScript Files-->
{% for jsFile in jsFiles %}
<script type="text/javascript" src="{{jsFile}}"></script>
{% endfor %}
{% if process.env.NODE_ENV === 'development' %}
<!--Livereload script rendered -->
<script type="text/javascript" src="http://{{request.hostname}}:35729/livereload.js"></script>
{% endif %}
<!--[if lt IE 9]>
<section class="browsehappy jumbotron hide">
<h1>Hello there!</h1>
<p>You are using an old browser which we unfortunately do not support.</p>
<p>Please <a href="http://browsehappy.com/">click here</a> to update your browser before using the website.</p>
<p><a href="http://browsehappy.com" class="btn btn-primary btn-lg" role="button">Yes, upgrade my browser!</a></p>
</section>
<![endif]-->
</body>
</html>

View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
</head>
<body>
<p>Dear {{name}},</p>
<p></p>
<p>This is a confirmation that the password for your account has just been changed</p>
<br>
<br>
<p>The {{appName}} Support Team</p>
</body>
</html>

View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
</head>
<body>
<p>Dear {{name}},</p>
<br>
<p>
You have requested to have your password reset for your account at {{appName}}
</p>
<p>Please visit this url to reset your password:</p>
<p>{{url}}</p>
<strong>If you didn't make this request, you can ignore this email.</strong>
<br>
<br>
<p>The {{appName}} Support Team</p>
</body>
</html>

21
bower.json Executable file
View File

@ -0,0 +1,21 @@
{
"name": "meanjs",
"version": "0.3.2",
"description": "Fullstack JavaScript with MongoDB, Express, AngularJS, and Node.js.",
"dependencies": {
"bootstrap": "~3",
"angular": "~1.2",
"angular-resource": "~1.2",
"angular-animate": "~1.2",
"angular-mocks": "~1.2",
"angular-bootstrap": "~0.12.0",
"angular-ui-utils": "~0.1.1",
"angular-ui-router": "~0.2.11",
"angular-strap": "~2.2.1"
},
"resolutions": {
"angular": "^1.2.21",
"angular-resource": "~1.2",
"ng-file-upload": "~4.1.0"
}
}

76
config/config.js Executable file
View File

@ -0,0 +1,76 @@
'use strict';
/**
* Module dependencies.
*/
var _ = require('lodash'),
glob = require('glob');
/**
* Load app configurations
*/
module.exports = _.extend(
require('./env/all'),
require('./env/' + process.env.NODE_ENV) || {}
);
/**
* Get files by glob patterns
*/
module.exports.getGlobbedFiles = function(globPatterns, removeRoot) {
// For context switching
var _this = this;
// URL paths regex
var urlRegex = new RegExp('^(?:[a-z]+:)?\/\/', 'i');
// The output array
var output = [];
// If glob pattern is array so we use each pattern in a recursive way, otherwise we use glob
if (_.isArray(globPatterns)) {
globPatterns.forEach(function(globPattern) {
output = _.union(output, _this.getGlobbedFiles(globPattern, removeRoot));
});
} else if (_.isString(globPatterns)) {
if (urlRegex.test(globPatterns)) {
output.push(globPatterns);
} else {
glob(globPatterns, {
sync: true
}, function(err, files) {
if (removeRoot) {
files = files.map(function(file) {
return file.replace(removeRoot, '');
});
}
output = _.union(output, files);
});
}
}
return output;
};
/**
* Get the modules JavaScript files
*/
module.exports.getJavaScriptAssets = function(includeTests) {
var output = this.getGlobbedFiles(this.assets.lib.js.concat(this.assets.js), 'public/');
// To include tests
if (includeTests) {
output = _.union(output, this.getGlobbedFiles(this.assets.tests));
}
return output;
};
/**
* Get the modules CSS files
*/
module.exports.getCSSAssets = function() {
var output = this.getGlobbedFiles(this.assets.lib.css.concat(this.assets.css), 'public/');
return output;
};

84
config/env/all.js vendored Executable file
View File

@ -0,0 +1,84 @@
'use strict';
module.exports = {
app: {
title: 'MedForms',
description: 'Generate Forms from PDFs',
keywords: 'typeform, pdfs, forms, generator, form generator',
},
port: process.env.PORT || 3000,
templateEngine: 'swig',
// The secret should be set to a non-guessable string that
// is used to compute a session hash
sessionSecret: 'MEAN',
// The name of the MongoDB collection to store sessions in
sessionCollection: 'sessions',
// The session cookie settings
sessionCookie: {
path: '/',
httpOnly: true,
// If secure is set to true then it will cause the cookie to be set
// only when SSL-enabled (HTTPS) is used, and otherwise it won't
// set a cookie. 'true' is recommended yet it requires the above
// mentioned pre-requisite.
secure: false,
// Only set the maxAge to null if the cookie shouldn't be expired
// at all. The cookie will expunge when the browser is closed.
maxAge: null,
// To set the cookie in a specific domain uncomment the following
// setting:
// domain: 'yourdomain.com'
},
/*
* Upload Configuration
*/
//Global upload path
uploadPath : 'uploads/',
//PDF storage path
pdfUploadPath: 'uploads/pdfs/',
//Temp files storage path
tmpUploadPath: 'uploads/tmp/',
// The session cookie name
sessionName: 'connect.sid',
log: {
// Can specify one of 'combined', 'common', 'dev', 'short', 'tiny'
format: 'combined',
// Stream defaults to process.stdout
// Uncomment to enable logging to a log on the file system
options: {
stream: 'access.log'
}
},
assets: {
lib: {
css: [
'public/lib/bootstrap/dist/css/bootstrap.css',
'public/lib/bootstrap/dist/css/bootstrap-theme.css',
],
js: [
'public/lib/angular/angular.js',
'public/lib/angular-resource/angular-resource.js',
'public/lib/angular-animate/angular-animate.js',
'public/lib/angular-ui-router/release/angular-ui-router.js',
'public/lib/angular-ui-utils/ui-utils.js',
'public/lib/angular-bootstrap/ui-bootstrap-tpls.js',
'public/lib/ng-file-upload/ng-file-upload-all.js'
]
},
css: [
'public/modules/**/css/*.css'
],
js: [
'public/config.js',
'public/application.js',
'public/modules/*/*.js',
'public/modules/*/*[!tests]*/*.js'
],
tests: [
'public/lib/angular-mocks/angular-mocks.js',
'public/modules/*/tests/*.js'
]
}
};

58
config/env/development.js vendored Executable file
View File

@ -0,0 +1,58 @@
'use strict';
module.exports = {
db: {
uri: 'mongodb://localhost/mean-dev',
options: {
user: '',
pass: ''
}
},
log: {
// Can specify one of 'combined', 'common', 'dev', 'short', 'tiny'
format: 'dev',
// Stream defaults to process.stdout
// Uncomment to enable logging to a log on the file system
options: {
//stream: 'access.log'
}
},
app: {
title: 'MedForms'
},
facebook: {
clientID: process.env.FACEBOOK_ID || 'APP_ID',
clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET',
callbackURL: '/auth/facebook/callback'
},
twitter: {
clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY',
clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET',
callbackURL: '/auth/twitter/callback'
},
google: {
clientID: process.env.GOOGLE_ID || 'APP_ID',
clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET',
callbackURL: '/auth/google/callback'
},
linkedin: {
clientID: process.env.LINKEDIN_ID || 'APP_ID',
clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET',
callbackURL: '/auth/linkedin/callback'
},
github: {
clientID: process.env.GITHUB_ID || 'APP_ID',
clientSecret: process.env.GITHUB_SECRET || 'APP_SECRET',
callbackURL: '/auth/github/callback'
},
mailer: {
from: process.env.MAILER_FROM || 'MAILER_FROM',
options: {
service: process.env.MAILER_SERVICE_PROVIDER || 'MAILER_SERVICE_PROVIDER',
auth: {
user: process.env.MAILER_EMAIL_ID || 'MAILER_EMAIL_ID',
pass: process.env.MAILER_PASSWORD || 'MAILER_PASSWORD'
}
}
}
};

73
config/env/production.js vendored Executable file
View File

@ -0,0 +1,73 @@
'use strict';
module.exports = {
db: {
uri: process.env.MONGOHQ_URL || process.env.MONGOLAB_URI || 'mongodb://' + (process.env.DB_1_PORT_27017_TCP_ADDR || 'localhost') + '/mean',
options: {
user: '',
pass: ''
}
},
log: {
// Can specify one of 'combined', 'common', 'dev', 'short', 'tiny'
format: 'combined',
// Stream defaults to process.stdout
// Uncomment to enable logging to a log on the file system
options: {
stream: 'access.log'
}
},
assets: {
lib: {
css: [
'public/lib/bootstrap/dist/css/bootstrap.min.css',
'public/lib/bootstrap/dist/css/bootstrap-theme.min.css',
],
js: [
'public/lib/angular/angular.min.js',
'public/lib/angular-resource/angular-resource.min.js',
'public/lib/angular-animate/angular-animate.min.js',
'public/lib/angular-ui-router/release/angular-ui-router.min.js',
'public/lib/angular-ui-utils/ui-utils.min.js',
'public/lib/angular-bootstrap/ui-bootstrap-tpls.min.js'
]
},
css: 'public/dist/application.min.css',
js: 'public/dist/application.min.js'
},
facebook: {
clientID: process.env.FACEBOOK_ID || 'APP_ID',
clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET',
callbackURL: '/auth/facebook/callback'
},
twitter: {
clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY',
clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET',
callbackURL: '/auth/twitter/callback'
},
google: {
clientID: process.env.GOOGLE_ID || 'APP_ID',
clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET',
callbackURL: '/auth/google/callback'
},
linkedin: {
clientID: process.env.LINKEDIN_ID || 'APP_ID',
clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET',
callbackURL: '/auth/linkedin/callback'
},
github: {
clientID: process.env.GITHUB_ID || 'APP_ID',
clientSecret: process.env.GITHUB_SECRET || 'APP_SECRET',
callbackURL: '/auth/github/callback'
},
mailer: {
from: process.env.MAILER_FROM || 'MAILER_FROM',
options: {
service: process.env.MAILER_SERVICE_PROVIDER || 'MAILER_SERVICE_PROVIDER',
auth: {
user: process.env.MAILER_EMAIL_ID || 'MAILER_EMAIL_ID',
pass: process.env.MAILER_PASSWORD || 'MAILER_PASSWORD'
}
}
}
};

74
config/env/secure.js vendored Executable file
View File

@ -0,0 +1,74 @@
'use strict';
module.exports = {
port: 8443,
db: {
uri: process.env.MONGOHQ_URL || process.env.MONGOLAB_URI || 'mongodb://localhost/mean',
options: {
user: '',
pass: ''
}
},
log: {
// Can specify one of 'combined', 'common', 'dev', 'short', 'tiny'
format: 'combined',
// Stream defaults to process.stdout
// Uncomment to enable logging to a log on the file system
options: {
stream: 'access.log'
}
},
assets: {
lib: {
css: [
'public/lib/bootstrap/dist/css/bootstrap.min.css',
'public/lib/bootstrap/dist/css/bootstrap-theme.min.css',
],
js: [
'public/lib/angular/angular.min.js',
'public/lib/angular-resource/angular-resource.min.js',
'public/lib/angular-animate/angular-animate.min.js',
'public/lib/angular-ui-router/release/angular-ui-router.min.js',
'public/lib/angular-ui-utils/ui-utils.min.js',
'public/lib/angular-bootstrap/ui-bootstrap-tpls.min.js'
]
},
css: 'public/dist/application.min.css',
js: 'public/dist/application.min.js'
},
facebook: {
clientID: process.env.FACEBOOK_ID || 'APP_ID',
clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET',
callbackURL: 'https://localhost:443/auth/facebook/callback'
},
twitter: {
clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY',
clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET',
callbackURL: 'https://localhost:443/auth/twitter/callback'
},
google: {
clientID: process.env.GOOGLE_ID || 'APP_ID',
clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET',
callbackURL: 'https://localhost:443/auth/google/callback'
},
linkedin: {
clientID: process.env.LINKEDIN_ID || 'APP_ID',
clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET',
callbackURL: 'https://localhost:443/auth/linkedin/callback'
},
github: {
clientID: process.env.GITHUB_ID || 'APP_ID',
clientSecret: process.env.GITHUB_SECRET || 'APP_SECRET',
callbackURL: 'https://localhost:443/auth/github/callback'
},
mailer: {
from: process.env.MAILER_FROM || 'MAILER_FROM',
options: {
service: process.env.MAILER_SERVICE_PROVIDER || 'MAILER_SERVICE_PROVIDER',
auth: {
user: process.env.MAILER_EMAIL_ID || 'MAILER_EMAIL_ID',
pass: process.env.MAILER_PASSWORD || 'MAILER_PASSWORD'
}
}
}
};

59
config/env/test.js vendored Executable file
View File

@ -0,0 +1,59 @@
'use strict';
module.exports = {
db: {
uri: 'mongodb://localhost/mean-test',
options: {
user: '',
pass: ''
}
},
port: 3001,
log: {
// Can specify one of 'combined', 'common', 'dev', 'short', 'tiny'
format: 'dev',
// Stream defaults to process.stdout
// Uncomment to enable logging to a log on the file system
options: {
//stream: 'access.log'
}
},
app: {
title: 'MedForms'
},
facebook: {
clientID: process.env.FACEBOOK_ID || 'APP_ID',
clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET',
callbackURL: '/auth/facebook/callback'
},
twitter: {
clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY',
clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET',
callbackURL: '/auth/twitter/callback'
},
google: {
clientID: process.env.GOOGLE_ID || 'APP_ID',
clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET',
callbackURL: '/auth/google/callback'
},
linkedin: {
clientID: process.env.LINKEDIN_ID || 'APP_ID',
clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET',
callbackURL: '/auth/linkedin/callback'
},
github: {
clientID: process.env.GITHUB_ID || 'APP_ID',
clientSecret: process.env.GITHUB_SECRET || 'APP_SECRET',
callbackURL: '/auth/github/callback'
},
mailer: {
from: process.env.MAILER_FROM || 'MAILER_FROM',
options: {
service: process.env.MAILER_SERVICE_PROVIDER || 'MAILER_SERVICE_PROVIDER',
auth: {
user: process.env.MAILER_EMAIL_ID || 'MAILER_EMAIL_ID',
pass: process.env.MAILER_PASSWORD || 'MAILER_PASSWORD'
}
}
}
};

196
config/express.js Executable file
View File

@ -0,0 +1,196 @@
'use strict';
/**
* Module dependencies.
*/
var fs = require('fs-extra'),
http = require('http'),
https = require('https'),
express = require('express'),
morgan = require('morgan'),
logger = require('./logger'),
bodyParser = require('body-parser'),
session = require('express-session'),
compression = require('compression'),
methodOverride = require('method-override'),
cookieParser = require('cookie-parser'),
helmet = require('helmet'),
multer = require('multer'),
passport = require('passport'),
mongoStore = require('connect-mongo')({
session: session
}),
flash = require('connect-flash'),
config = require('./config'),
consolidate = require('consolidate'),
path = require('path');
module.exports = function(db) {
// Initialize express app
var app = express();
// Globbing model files
config.getGlobbedFiles('./app/models/**/*.js').forEach(function(modelPath) {
require(path.resolve(modelPath));
});
// Setting application local variables
app.locals.title = config.app.title;
app.locals.description = config.app.description;
app.locals.keywords = config.app.keywords;
app.locals.facebookAppId = config.facebook.clientID;
app.locals.jsFiles = config.getJavaScriptAssets();
app.locals.cssFiles = config.getCSSAssets();
// Passing the request url to environment locals
app.use(function(req, res, next) {
res.locals.url = req.protocol + '://' + req.headers.host + req.url;
next();
});
// Should be placed before express.static
app.use(compression({
// only compress files for the following content types
filter: function(req, res) {
return (/json|text|javascript|css/).test(res.getHeader('Content-Type'));
},
// zlib option for compression level
level: 3
}));
// Showing stack errors
app.set('showStackError', true);
// Set swig as the template engine
app.engine('server.view.html', consolidate[config.templateEngine]);
// Set views path and view engine
app.set('view engine', 'server.view.html');
app.set('views', './app/views');
// Enable logger (morgan)
app.use(morgan(logger.getLogFormat(), logger.getLogOptions()));
// Environment dependent middleware
if (process.env.NODE_ENV === 'development') {
// Disable views cache
app.set('view cache', false);
} else if (process.env.NODE_ENV === 'production') {
app.locals.cache = 'memory';
}
// Request body parsing middleware should be above methodOverride
app.use(bodyParser.urlencoded({
extended: true
}));
app.use(bodyParser.json());
app.use(methodOverride());
// Use helmet to secure Express headers
app.use(helmet.xframe());
app.use(helmet.xssFilter());
app.use(helmet.nosniff());
app.use(helmet.ienoopen());
app.disable('x-powered-by');
// Setting the app router and static folder
app.use(express.static(path.resolve('./public')));
// Setting the pdf upload route and folder
app.use(multer({ dest: config.tmpUploadPath,
rename: function (fieldname, filename) {
return Date.now();
},
// changeDest: function(dest, req, res) {
// console.log(req.body.form);
// var newDestination = dest + req.body.form.title;
// var stat = null;
// try {
// stat = fs.statSync(newDestination);
// } catch (err) {
// fs.mkdirSync(newDestination);
// }
// if (stat && !stat.isDirectory()) {
// console.log('Directory cannot be created');
// throw new Error('Directory cannot be created because an inode of a different type exists at "' + dest + '"');
// }
// return newDestination;
// },
onFileUploadStart: function (file) {
console.log(file.originalname + ' is starting ...');
},
onFileUploadComplete: function (file) {
console.log(file.fieldname + ' uploaded to ' + file.path);
// done=true;
}
}));
// CookieParser should be above session
app.use(cookieParser());
// Express MongoDB session storage
app.use(session({
saveUninitialized: true,
resave: true,
secret: config.sessionSecret,
store: new mongoStore({
db: db.connection.db,
collection: config.sessionCollection
}),
cookie: config.sessionCookie,
name: config.sessionName
}));
// use passport session
app.use(passport.initialize());
app.use(passport.session());
// connect flash for flash messages
app.use(flash());
// Globbing routing files
config.getGlobbedFiles('./app/routes/**/*.js').forEach(function(routePath) {
require(path.resolve(routePath))(app);
});
// Assume 'not found' in the error msgs is a 404. this is somewhat silly, but valid, you can do whatever you like, set properties, use instanceof etc.
app.use(function(err, req, res, next) {
// If the error object doesn't exists
if (!err) return next();
// Log it
console.error(err.stack);
// Error page
res.status(500).render('500', {
error: err.stack
});
});
// Assume 404 since no middleware responded
app.use(function(req, res) {
res.status(404).render('404', {
url: req.originalUrl,
error: 'Not Found'
});
});
if (process.env.NODE_ENV === 'secure') {
// Load SSL key and certificate
var privateKey = fs.readFileSync('./config/sslcerts/key.pem', 'utf8');
var certificate = fs.readFileSync('./config/sslcerts/cert.pem', 'utf8');
// Create HTTPS Server
var httpsServer = https.createServer({
key: privateKey,
cert: certificate
}, app);
// Return HTTPS server instance
return httpsServer;
}
// Return Express server instance
return app;
};

31
config/init.js Executable file
View File

@ -0,0 +1,31 @@
'use strict';
/**
* Module dependencies.
*/
var glob = require('glob'),
chalk = require('chalk');
/**
* Module init function.
*/
module.exports = function() {
/**
* Before we begin, lets set the environment variable
* We'll Look for a valid NODE_ENV variable and if one cannot be found load the development NODE_ENV
*/
glob('./config/env/' + process.env.NODE_ENV + '.js', {
sync: true
}, function(err, environmentFiles) {
if (!environmentFiles.length) {
if (process.env.NODE_ENV) {
console.error(chalk.red('No configuration file found for "' + process.env.NODE_ENV + '" environment using development instead'));
} else {
console.error(chalk.red('NODE_ENV is not defined! Using default development environment'));
}
process.env.NODE_ENV = 'development';
}
});
};

36
config/logger.js Executable file
View File

@ -0,0 +1,36 @@
'use strict';
/**
* Module dependencies.
*/
var morgan = require('morgan');
var config = require('./config');
var fs = require('fs-extra');
/**
* Module init function.
*/
module.exports = {
getLogFormat: function() {
return config.log.format;
},
getLogOptions: function() {
var options = {};
try {
if ('stream' in config.log.options) {
options = {
stream: fs.createWriteStream(process.cwd() + '/' + config.log.options.stream, {flags: 'a'})
};
}
} catch (e) {
options = {};
}
return options;
}
};

33
config/passport.js Executable file
View File

@ -0,0 +1,33 @@
'use strict';
/**
* Module dependencies.
*/
var passport = require('passport'),
User = require('mongoose').model('User'),
path = require('path'),
config = require('./config');
/**
* Module init function.
*/
module.exports = function() {
// Serialize sessions
passport.serializeUser(function(user, done) {
done(null, user.id);
});
// Deserialize sessions
passport.deserializeUser(function(id, done) {
User.findOne({
_id: id
}, '-salt -password', function(err, user) {
done(err, user);
});
});
// Initialize strategies
config.getGlobbedFiles('./config/strategies/**/*.js').forEach(function(strategy) {
require(path.resolve(strategy))();
});
};

41
config/strategies/facebook.js Executable file
View File

@ -0,0 +1,41 @@
'use strict';
/**
* Module dependencies.
*/
var passport = require('passport'),
FacebookStrategy = require('passport-facebook').Strategy,
config = require('../config'),
users = require('../../app/controllers/users.server.controller');
module.exports = function() {
// Use facebook strategy
passport.use(new FacebookStrategy({
clientID: config.facebook.clientID,
clientSecret: config.facebook.clientSecret,
callbackURL: config.facebook.callbackURL,
passReqToCallback: true
},
function(req, accessToken, refreshToken, profile, done) {
// Set the provider data and include tokens
var providerData = profile._json;
providerData.accessToken = accessToken;
providerData.refreshToken = refreshToken;
// Create the user OAuth profile
var providerUserProfile = {
firstName: profile.name.givenName,
lastName: profile.name.familyName,
displayName: profile.displayName,
email: profile.emails[0].value,
username: profile.username,
provider: 'facebook',
providerIdentifierField: 'id',
providerData: providerData
};
// Save the user OAuth profile
users.saveOAuthUserProfile(req, providerUserProfile, done);
}
));
};

46
config/strategies/github.js Executable file
View File

@ -0,0 +1,46 @@
'use strict';
/**
* Module dependencies.
*/
var passport = require('passport'),
GithubStrategy = require('passport-github').Strategy,
config = require('../config'),
users = require('../../app/controllers/users.server.controller');
module.exports = function() {
// Use github strategy
passport.use(new GithubStrategy({
clientID: config.github.clientID,
clientSecret: config.github.clientSecret,
callbackURL: config.github.callbackURL,
passReqToCallback: true
},
function(req, accessToken, refreshToken, profile, done) {
// Set the provider data and include tokens
var providerData = profile._json;
providerData.accessToken = accessToken;
providerData.refreshToken = refreshToken;
// Create the user OAuth profile
var displayName = profile.displayName.trim();
var iSpace = displayName.indexOf(' '); // index of the whitespace following the firstName
var firstName = iSpace !== -1 ? displayName.substring(0, iSpace) : displayName;
var lastName = iSpace !== -1 ? displayName.substring(iSpace + 1) : '';
var providerUserProfile = {
firstName: firstName,
lastName: lastName,
displayName: displayName,
email: profile.emails[0].value,
username: profile.username,
provider: 'github',
providerIdentifierField: 'id',
providerData: providerData
};
// Save the user OAuth profile
users.saveOAuthUserProfile(req, providerUserProfile, done);
}
));
};

41
config/strategies/google.js Executable file
View File

@ -0,0 +1,41 @@
'use strict';
/**
* Module dependencies.
*/
var passport = require('passport'),
GoogleStrategy = require('passport-google-oauth').OAuth2Strategy,
config = require('../config'),
users = require('../../app/controllers/users.server.controller');
module.exports = function() {
// Use google strategy
passport.use(new GoogleStrategy({
clientID: config.google.clientID,
clientSecret: config.google.clientSecret,
callbackURL: config.google.callbackURL,
passReqToCallback: true
},
function(req, accessToken, refreshToken, profile, done) {
// Set the provider data and include tokens
var providerData = profile._json;
providerData.accessToken = accessToken;
providerData.refreshToken = refreshToken;
// Create the user OAuth profile
var providerUserProfile = {
firstName: profile.name.givenName,
lastName: profile.name.familyName,
displayName: profile.displayName,
email: profile.emails[0].value,
username: profile.username,
provider: 'google',
providerIdentifierField: 'id',
providerData: providerData
};
// Save the user OAuth profile
users.saveOAuthUserProfile(req, providerUserProfile, done);
}
));
};

42
config/strategies/linkedin.js Executable file
View File

@ -0,0 +1,42 @@
'use strict';
/**
* Module dependencies.
*/
var passport = require('passport'),
LinkedInStrategy = require('passport-linkedin').Strategy,
config = require('../config'),
users = require('../../app/controllers/users.server.controller');
module.exports = function() {
// Use linkedin strategy
passport.use(new LinkedInStrategy({
consumerKey: config.linkedin.clientID,
consumerSecret: config.linkedin.clientSecret,
callbackURL: config.linkedin.callbackURL,
passReqToCallback: true,
profileFields: ['id', 'first-name', 'last-name', 'email-address']
},
function(req, accessToken, refreshToken, profile, done) {
// Set the provider data and include tokens
var providerData = profile._json;
providerData.accessToken = accessToken;
providerData.refreshToken = refreshToken;
// Create the user OAuth profile
var providerUserProfile = {
firstName: profile.name.givenName,
lastName: profile.name.familyName,
displayName: profile.displayName,
email: profile.emails[0].value,
username: profile.username,
provider: 'linkedin',
providerIdentifierField: 'id',
providerData: providerData
};
// Save the user OAuth profile
users.saveOAuthUserProfile(req, providerUserProfile, done);
}
));
};

38
config/strategies/local.js Executable file
View File

@ -0,0 +1,38 @@
'use strict';
/**
* Module dependencies.
*/
var passport = require('passport'),
LocalStrategy = require('passport-local').Strategy,
User = require('mongoose').model('User');
module.exports = function() {
// Use local strategy
passport.use(new LocalStrategy({
usernameField: 'username',
passwordField: 'password'
},
function(username, password, done) {
User.findOne({
username: username
}, function(err, user) {
if (err) {
return done(err);
}
if (!user) {
return done(null, false, {
message: 'Unknown user or invalid password'
});
}
if (!user.authenticate(password)) {
return done(null, false, {
message: 'Unknown user or invalid password'
});
}
return done(null, user);
});
}
));
};

45
config/strategies/twitter.js Executable file
View File

@ -0,0 +1,45 @@
'use strict';
/**
* Module dependencies.
*/
var passport = require('passport'),
TwitterStrategy = require('passport-twitter').Strategy,
config = require('../config'),
users = require('../../app/controllers/users.server.controller');
module.exports = function() {
// Use twitter strategy
passport.use(new TwitterStrategy({
consumerKey: config.twitter.clientID,
consumerSecret: config.twitter.clientSecret,
callbackURL: config.twitter.callbackURL,
passReqToCallback: true
},
function(req, token, tokenSecret, profile, done) {
// Set the provider data and include tokens
var providerData = profile._json;
providerData.token = token;
providerData.tokenSecret = tokenSecret;
// Create the user OAuth profile
var displayName = profile.displayName.trim();
var iSpace = displayName.indexOf(' '); // index of the whitespace following the firstName
var firstName = iSpace !== -1 ? displayName.substring(0, iSpace) : displayName;
var lastName = iSpace !== -1 ? displayName.substring(iSpace + 1) : '';
var providerUserProfile = {
firstName: firstName,
lastName: lastName,
displayName: displayName,
username: profile.username,
provider: 'twitter',
providerIdentifierField: 'id_str',
providerData: providerData
};
// Save the user OAuth profile
users.saveOAuthUserProfile(req, providerUserProfile, done);
}
));
};

12
fig.yml Executable file
View File

@ -0,0 +1,12 @@
web:
build: .
links:
- db
ports:
- "3000:3000"
environment:
NODE_ENV: development
db:
image: mongo
ports:
- "27017:27017"

183
gruntfile.js Executable file
View File

@ -0,0 +1,183 @@
'use strict';
module.exports = function(grunt) {
// Unified Watch Object
var watchFiles = {
serverViews: ['app/views/**/*.*'],
serverJS: ['gruntfile.js', 'server.js', 'config/**/*.js', 'app/**/*.js', '!app/tests/'],
clientViews: ['public/modules/**/views/**/*.html'],
clientJS: ['public/js/*.js', 'public/modules/**/*.js'],
clientCSS: ['public/modules/**/*.css'],
mochaTests: ['app/tests/**/*.js']
};
// Project Configuration
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
watch: {
serverViews: {
files: watchFiles.serverViews,
options: {
livereload: true
}
},
serverJS: {
files: watchFiles.serverJS,
tasks: ['jshint'],
options: {
livereload: true
}
},
clientViews: {
files: watchFiles.clientViews,
options: {
livereload: true
}
},
clientJS: {
files: watchFiles.clientJS,
tasks: ['jshint'],
options: {
livereload: true
}
},
clientCSS: {
files: watchFiles.clientCSS,
tasks: ['csslint'],
options: {
livereload: true
}
},
mochaTests: {
files: watchFiles.mochaTests,
tasks: ['test:server'],
}
},
jshint: {
all: {
src: watchFiles.clientJS.concat(watchFiles.serverJS),
options: {
jshintrc: true
}
}
},
csslint: {
options: {
csslintrc: '.csslintrc'
},
all: {
src: watchFiles.clientCSS
}
},
uglify: {
production: {
options: {
mangle: false
},
files: {
'public/dist/application.min.js': 'public/dist/application.js'
}
}
},
cssmin: {
combine: {
files: {
'public/dist/application.min.css': '<%= applicationCSSFiles %>'
}
}
},
nodemon: {
dev: {
script: 'server.js',
options: {
nodeArgs: ['--debug'],
ext: 'js,html',
watch: watchFiles.serverViews.concat(watchFiles.serverJS)
}
}
},
'node-inspector': {
custom: {
options: {
'web-port': 1337,
'web-host': 'localhost',
'debug-port': 5858,
'save-live-edit': true,
'no-preload': true,
'stack-trace-limit': 50,
'hidden': []
}
}
},
ngAnnotate: {
production: {
files: {
'public/dist/application.js': '<%= applicationJavaScriptFiles %>'
}
}
},
concurrent: {
default: ['nodemon', 'watch'],
debug: ['nodemon', 'watch', 'node-inspector'],
options: {
logConcurrentOutput: true,
limit: 10
}
},
env: {
test: {
NODE_ENV: 'test'
},
secure: {
NODE_ENV: 'secure'
}
},
mochaTest: {
src: watchFiles.mochaTests,
options: {
reporter: 'spec',
require: 'server.js'
}
},
karma: {
unit: {
configFile: 'karma.conf.js'
}
}
});
// Load NPM tasks
require('load-grunt-tasks')(grunt);
// Making grunt default to force in order not to break the project.
grunt.option('force', true);
// A Task for loading the configuration object
grunt.task.registerTask('loadConfig', 'Task that loads the config into a grunt option.', function() {
var init = require('./config/init')();
var config = require('./config/config');
grunt.config.set('applicationJavaScriptFiles', config.assets.js);
grunt.config.set('applicationCSSFiles', config.assets.css);
});
// Default task(s).
grunt.registerTask('default', ['lint', 'concurrent:default']);
// Debug task.
grunt.registerTask('debug', ['lint', 'concurrent:debug']);
// Secure task(s).
grunt.registerTask('secure', ['env:secure', 'lint', 'concurrent:default']);
// Lint task(s).
grunt.registerTask('lint', ['jshint', 'csslint']);
// Build task(s).
grunt.registerTask('build', ['lint', 'loadConfig', 'ngAnnotate', 'uglify', 'cssmin']);
// Test task.
grunt.registerTask('test', ['test:server', 'test:client']);
grunt.registerTask('test:server', ['env:test', 'mochaTest']);
grunt.registerTask('test:client', ['env:test', 'karma:unit']);
};

51
karma.conf.js Executable file
View File

@ -0,0 +1,51 @@
'use strict';
/**
* Module dependencies.
*/
var applicationConfiguration = require('./config/config');
// Karma configuration
module.exports = function(config) {
config.set({
// Frameworks to use
frameworks: ['jasmine'],
// List of files / patterns to load in the browser
files: applicationConfiguration.assets.lib.js.concat(applicationConfiguration.assets.js, applicationConfiguration.assets.tests),
// Test results reporter to use
// Possible values: 'dots', 'progress', 'junit', 'growl', 'coverage'
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
});
};

75
package.json Normal file
View File

@ -0,0 +1,75 @@
{
"name": "meanjs",
"description": "Full-Stack JavaScript with MongoDB, Express, AngularJS, and Node.js.",
"version": "0.3.3",
"private": false,
"author": "https://github.com/meanjs/mean/graphs/contributors",
"repository": {
"type": "git",
"url": "https://github.com/meanjs/mean.git"
},
"engines": {
"node": ">=0.10.28",
"npm": ">=1.4.28"
},
"scripts": {
"start": "grunt",
"test": "grunt test",
"postinstall": "bower install --config.interactive=false"
},
"dependencies": {
"async": "~0.9.0",
"body-parser": "~1.9.0",
"bower": "~1.3.8",
"chalk": "~1.0.0",
"compression": "~1.2.0",
"connect-flash": "~0.1.1",
"connect-mongo": "~0.4.1",
"consolidate": "~0.10.0",
"cookie-parser": "~1.3.2",
"express": "~4.10.1",
"express-session": "~1.9.1",
"forever": "~0.11.0",
"fs-extra": "^0.18.3",
"glob": "~4.0.5",
"grunt-cli": "~0.1.13",
"helmet": "~0.5.0",
"lodash": "~2.4.1",
"method-override": "~2.3.0",
"mongoose": "~3.8.8",
"morgan": "~1.4.1",
"nodemailer": "~1.3.0",
"passport": "~0.2.0",
"passport-facebook": "~1.0.2",
"passport-github": "~0.1.5",
"passport-google-oauth": "~0.1.5",
"passport-linkedin": "~0.1.3",
"passport-local": "~1.0.0",
"passport-twitter": "~1.0.2",
"satelize": "^0.1.1",
"swig": "~1.4.1"
},
"devDependencies": {
"supertest": "~0.14.0",
"should": "~4.1.0",
"grunt-env": "~0.4.1",
"grunt-node-inspector": "~0.1.3",
"grunt-contrib-watch": "~0.6.1",
"grunt-contrib-jshint": "~0.10.0",
"grunt-contrib-csslint": "^0.3.1",
"grunt-ng-annotate": "~0.4.0",
"grunt-contrib-uglify": "~0.6.0",
"grunt-contrib-cssmin": "~0.10.0",
"grunt-nodemon": "~0.3.0",
"grunt-concurrent": "~1.0.0",
"grunt-mocha-test": "~0.12.1",
"grunt-karma": "~0.9.0",
"load-grunt-tasks": "~1.0.0",
"karma": "~0.12.0",
"karma-jasmine": "~0.2.1",
"karma-coverage": "~0.2.0",
"karma-chrome-launcher": "~0.1.2",
"karma-firefox-launcher": "~0.1.3",
"karma-phantomjs-launcher": "~0.1.2"
}
}

32
public/application.js Executable file
View File

@ -0,0 +1,32 @@
'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('!');
}
]);
angular.module(ApplicationConfiguration.applicationModuleName).run(['$rootScope', 'Authorization', 'Principal',
function($rootScope, Authorization, Principal) {
$rootScope.$on('$stateChangeStart', function(event, toState, toStateParams) {
// track the state the user wants to go to; authorization service needs this
$rootScope.toState = toState;
$rootScope.toStateParams = toStateParams;
// if the principal is resolved, do an authorization check immediately. otherwise,
// it'll be done when the state it resolved.
if (Principal.isIdentityResolved()) Authorization.authorize();
});
}
]);
//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]);
});

23
public/config.js Executable file
View File

@ -0,0 +1,23 @@
'use strict';
// Init the application configuration module for AngularJS application
var ApplicationConfiguration = (function() {
// Init module configuration options
var applicationModuleName = 'medform';
var applicationModuleVendorDependencies = ['ngResource', 'ngAnimate', 'ui.router', 'ui.bootstrap', 'ui.utils'];
// Add a new vertical module
var registerModule = function(moduleName, dependencies) {
// Create angular module
angular.module(moduleName, dependencies || []);
// Add the module to the AngularJS configuration file
angular.module(applicationModuleName).requires.push(moduleName);
};
return {
applicationModuleName: applicationModuleName,
applicationModuleVendorDependencies: applicationModuleVendorDependencies,
registerModule: registerModule
};
})();

15
public/humans.txt Executable file
View File

@ -0,0 +1,15 @@
# humanstxt.org/
# The humans responsible & technology colophon
# TEAM
<name> -- <role> -- <twitter>
# THANKS
<name>
# TECHNOLOGY COLOPHON
HTML5, CSS3
jQuery, Modernizr

View File

@ -0,0 +1,26 @@
'use strict';
// Setting up route
angular.module('core').config(['$stateProvider', '$urlRouterProvider',
function($stateProvider, $urlRouterProvider, Authorization) {
// Redirect to home view when route not found
$urlRouterProvider.otherwise('/');
// Home state routing
$stateProvider.
state('home', {
url: '/',
templateUrl: 'modules/core/views/home.client.view.html'
}).
state('restricted', {
'abstract': true,
resolve: {
authorize: ['Authorization',
function(Authorization) {
return Authorization.authorize();
}
]
}
});
}
]);

View File

@ -0,0 +1,40 @@
'use strict';
angular.module('core').controller('HeaderController', ['$scope', 'Principal', 'Menus', '$state',
function($scope, Principal, Menus, $state) {
$scope.authentication = Principal;
$scope.isCollapsed = false;
$scope.hideNav = false;
$scope.menu = Menus.getMenu('topbar');
Principal.identity().then(function(user){
$scope.authentication.user = user;
}).then(function(){
$scope.signout = function() {
var response_obj = Principal.signout();
if( angular.isDefined(response_obj.error) ){
$scope.error = response_obj.error;
} else{
$state.go('home');
}
};
$scope.toggleCollapsibleMenu = function() {
$scope.isCollapsed = !$scope.isCollapsed;
};
// Collapsing the menu after navigation
$scope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams) {
$scope.isCollapsed = false;
$scope.hideNav = false;
if ( angular.isDefined( toState.data ) ) {
if ( angular.isDefined( toState.data.hideNav ) ) {
$scope.hideNav = toState.data.hideNav;
}
}
});
});
}
]);

View File

@ -0,0 +1,15 @@
'use strict';
angular.module('core').controller('HomeController', ['$scope', 'Principal',
function($scope, Principal) {
// This provides Principal context.
$scope.authentication = Principal;
$scope.authentication.user = undefined;
Principal.identity().then(function(user){
$scope.authentication.user = user;
});
// console.log("user.displayName: "+Principal.user()._id);
}
]);

View File

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

View File

@ -0,0 +1,20 @@
.content {
margin-top: 50px;
}
.undecorated-link:hover {
text-decoration: none;
}
[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
display: none !important;
}
.ng-invalid.ng-dirty {
border-color: #FA787E;
}
.ng-valid.ng-dirty {
border-color: #78FA89;
}
.browsehappy.jumbotron.hide,
body.ng-cloak
{
display: block;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -0,0 +1,171 @@
'use strict';
//Menu service used for managing menus
angular.module('core').service('Menus', [
function() {
// Define a set of default roles
this.defaultRoles = ['*'];
// Define the menus object
this.menus = {};
// A private function for rendering decision
var shouldRender = function(user) {
if (user) {
if (!!~this.roles.indexOf('*')) {
return true;
} else {
for (var userRoleIndex in user.roles) {
for (var roleIndex in this.roles) {
console.log(this.roles[roleIndex]);
console.log( this.roles[roleIndex] === user.roles[userRoleIndex]);
if (this.roles[roleIndex] === user.roles[userRoleIndex]) {
return true;
}
}
}
}
} else {
return this.isPublic;
}
return false;
};
// Validate menu existance
this.validateMenuExistance = function(menuId) {
if (menuId && menuId.length) {
if (this.menus[menuId]) {
return true;
} else {
throw new Error('Menu does not exists');
}
} else {
throw new Error('MenuId was not provided');
}
return false;
};
// Get the menu object by menu id
this.getMenu = function(menuId) {
// Validate that the menu exists
this.validateMenuExistance(menuId);
// Return the menu object
return this.menus[menuId];
};
// Add new menu object by menu id
this.addMenu = function(menuId, isPublic, roles) {
// Create the new menu
this.menus[menuId] = {
isPublic: isPublic || false,
roles: roles || this.defaultRoles,
items: [],
shouldRender: shouldRender
};
// Return the menu object
return this.menus[menuId];
};
// Remove existing menu object by menu id
this.removeMenu = function(menuId) {
// Validate that the menu exists
this.validateMenuExistance(menuId);
// Return the menu object
delete this.menus[menuId];
};
// Add menu item object
this.addMenuItem = function(menuId, menuItemTitle, menuItemURL, menuItemType, menuItemUIRoute, isPublic, roles, position) {
// Validate that the menu exists
this.validateMenuExistance(menuId);
// Push new menu item
this.menus[menuId].items.push({
title: menuItemTitle,
link: menuItemURL,
menuItemType: menuItemType || 'item',
menuItemClass: menuItemType,
uiRoute: menuItemUIRoute || ('/' + menuItemURL),
isPublic: ((isPublic === null || typeof isPublic === 'undefined') ? this.menus[menuId].isPublic : isPublic),
roles: ((roles === null || typeof roles === 'undefined') ? this.menus[menuId].roles : roles),
position: position || 0,
items: [],
shouldRender: shouldRender
});
// Return the menu object
return this.menus[menuId];
};
// Add submenu item object
this.addSubMenuItem = function(menuId, rootMenuItemURL, menuItemTitle, menuItemURL, menuItemUIRoute, isPublic, roles, position) {
// Validate that the menu exists
this.validateMenuExistance(menuId);
// Search for menu item
for (var itemIndex in this.menus[menuId].items) {
if (this.menus[menuId].items[itemIndex].link === rootMenuItemURL) {
// Push new submenu item
this.menus[menuId].items[itemIndex].items.push({
title: menuItemTitle,
link: menuItemURL,
uiRoute: menuItemUIRoute || ('/' + menuItemURL),
isPublic: ((isPublic === null || typeof isPublic === 'undefined') ? this.menus[menuId].items[itemIndex].isPublic : isPublic),
roles: ((roles === null || typeof roles === 'undefined') ? this.menus[menuId].items[itemIndex].roles : roles),
position: position || 0,
shouldRender: shouldRender
});
}
}
// Return the menu object
return this.menus[menuId];
};
// Remove existing menu object by menu id
this.removeMenuItem = function(menuId, menuItemURL) {
// Validate that the menu exists
this.validateMenuExistance(menuId);
// Search for menu item to remove
for (var itemIndex in this.menus[menuId].items) {
if (this.menus[menuId].items[itemIndex].link === menuItemURL) {
this.menus[menuId].items.splice(itemIndex, 1);
}
}
// Return the menu object
return this.menus[menuId];
};
// Remove existing menu object by menu id
this.removeSubMenuItem = function(menuId, submenuItemURL) {
// Validate that the menu exists
this.validateMenuExistance(menuId);
// Search for menu item to remove
for (var itemIndex in this.menus[menuId].items) {
for (var subitemIndex in this.menus[menuId].items[itemIndex].items) {
if (this.menus[menuId].items[itemIndex].items[subitemIndex].link === submenuItemURL) {
this.menus[menuId].items[itemIndex].items.splice(subitemIndex, 1);
}
}
}
// Return the menu object
return this.menus[menuId];
};
//Adding the topbar menu
this.addMenu('topbar', false, ['*']);
//Adding the bottombar menu for the Form-Footer view
this.addMenu('bottombar', false, ['*']);
}
]);

View File

@ -0,0 +1,24 @@
'use strict';
(function() {
describe('HeaderController', function() {
//Initialize global variables
var scope,
HeaderController;
// Load the main application module
beforeEach(module(ApplicationConfiguration.applicationModuleName));
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,24 @@
'use strict';
(function() {
describe('HomeController', function() {
//Initialize global variables
var scope,
HomeController;
// Load the main application module
beforeEach(module(ApplicationConfiguration.applicationModuleName));
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,60 @@
<section class="navbar navbar-fixed-top navbar-inverse"data-ng-controller="HeaderController" ng-hide="hideNav"
<div class="container" >
<div class="navbar-header">
<button class="navbar-toggle" type="button" data-ng-click="toggleCollapsibleMenu()">
<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">MedForm</a>
</div>
<nav class="collapse navbar-collapse" collapse="!isCollapsed" role="navigation">
<ul class="nav navbar-nav" data-ng-if="menu.shouldRender(authentication.user);">
<li data-ng-repeat="item in menu.items | orderBy: 'position'" data-ng-if="item.shouldRender(authentication.isAuthenticated());" ng-switch="item.menuItemType" ui-route="{{item.uiRoute}}" class="{{item.menuItemClass}}" ng-class="{active: ($uiRoute)}" dropdown="item.menuItemType === 'dropdown'">
<a ng-switch-when="dropdown" class="dropdown-toggle" dropdown-toggle>
<span data-ng-bind="item.title"></span>
<b class="caret"></b>
</a>
<ul ng-switch-when="dropdown" class="dropdown-menu">
<li data-ng-repeat="subitem in item.items | orderBy: 'position'" data-ng-if="subitem.shouldRender(authentication.isAuthenticated());" ui-route="{{subitem.uiRoute}}" ng-class="{active: $uiRoute}">
<a href="/#!/{{subitem.link}}" data-ng-bind="subitem.title"></a>
</li>
</ul>
<a ng-switch-default href="/#!/{{item.link}}" data-ng-bind="item.title"></a>
</li>
</ul>
<ul class="nav navbar-nav navbar-right" data-ng-hide="authentication.isAuthenticated()">
<li ui-route="/signup" ng-class="{active: $uiRoute}">
<a href="/#!/signup">Sign Up</a>
</li>
<li class="divider-vertical"></li>
<li ui-route="/signin" ng-class="{active: $uiRoute}">
<a href="/#!/signin">Sign In</a>
</li>
</ul>
<ul class="nav navbar-nav navbar-right" data-ng-show="authentication.isAuthenticated()">
<li class="dropdown" dropdown>
<a href="#" class="dropdown-toggle" data-toggle="dropdown" dropdown-toggle>
<span data-ng-bind="authentication.user.displayName"></span> <b class="caret"></b>
</a>
<ul class="dropdown-menu">
<li>
<a href="/#!/settings/profile">Edit Profile</a>
</li>
<li>
<a href="/#!/settings/password">Change Password</a>
</li>
<li data-ng-show="authentication.isAuthenticated().provider === 'local'">
<a href="/#!/settings/password">Change Password</a>
</li>
<li class="divider"></li>
<li>
<a ng-click="signout()">Signout</a>
</li>
</ul>
</li>
</ul>
</nav>
</div>
</section>

View File

@ -0,0 +1,105 @@
<section data-ng-controller="HomeController">
<div class="jumbotron text-center">
<div class="row">
<div class="col-md-6 col-md-offset-3 col-sm-6 col-sm-offset-3 col-xs-12">
<!-- <img alt="MEAN.JS" class="img-responsive text-center" src="modules/core/img/brand/logo.png" /> -->
</div>
</div>
<br>
<div class="row" data-ng-if="!authentication.isAuthenticated()">
<p class="lead">
Make beautiful forms in a snap.
</p>
<br><br> <br><br>
<a class="btn btn-info">
Signup now
</a>
</div>
<div class="row" data-ng-if="authentication.isAuthenticated()">
<p class="lead">
Hi there {{authentication.user.displayName}}
</p>
</div>
<div class="row" data-ng-if="authentication.isAuthenticated()">
<p>
<a class="btn btn-primary btn-lg" href="http://meanjs.org" target="_blank">Learn more</a>
</p>
</div>
</div>
<!-- <div>
<h2>Congrats! You've configured and ran the sample application successfully.</h2>
<p>MEAN.JS is a web application boilerplate, which means you should start changing everything :-)</p>
<p>This sample application tracks users and articles.</p>
<ul>
<li>
Click
<em>Signup</em>
to get started.
</li>
<li>
Configure your app to work with your social accounts, by editing the
<em>/config/env/*.js</em>
files.
</li>
<li>
Edit your users module.
</li>
<li>
Add new CRUD modules.
</li>
<li>
Have fun...
</li>
</ul>
</div>
<div class="row">
<div class="col-md-3">
<h2>
<strong>M</strong>ongoDB
</h2>
<p><a target="_blank" href="http://mongodb.org/">MongoDB</a> is a database. MongoDB's <a target="_blank" href="http://docs.mongodb.org/manual/">great manual</a> is the place to get started with NoSQL and MongoDB.</p>
</div>
<div class="col-md-3">
<h2>
<strong>E</strong>xpress
</h2>
<p><a target="_blank" href="http://expressjs.com/"> Express</a> is an app server. Check out <a target="_blank" href="http://expressjs.com/4x/api.html">The ExpressJS API reference for more information</a> or <a target="_blank" href="http://stackoverflow.com/questions/8144214/learning-express-for-node-js">StackOverflow</a> for more info.</p>
</div>
<div class="col-md-3">
<h2>
<strong>A</strong>ngularJS
</h2>
<p>AngularJS is web app framework. <a target="_blank" href="http://angularjs.org/">Angular's website</a> offers a lot. The <a target="_blank" href="http://www.thinkster.io/">Thinkster Popular Guide</a> and <a target="_blank" href="https://egghead.io/">Egghead Videos</a> are great resources.</p>
</div>
<div class="col-md-3">
<h2>
<strong>N</strong>ode.js
</h2>
<p><a target="_blank" href="http://nodejs.org/">Node.js</a> is a web server. Node's website and this <a target="_blank" href="http://stackoverflow.com/questions/2353818/how-do-i-get-started-with-node-js">stackOverflow thread</a> offer excellent starting points to get to grasps with node.</p>
</div>
</div>
<div class="well">
<h2>MEAN.JS Documentation</h2>
<p>
Once you're familiar with the foundation technology, check out the MEAN.JS Documentation:
<ul>
<li><a target="_blank" href="http://meanjs.org/docs.html">MEAN.JS Documentation</a>
</li>
<li><a target="_blank" href="http://meanjs.org/generator.html">Yeoman Generator</a>
</li>
<li><a target="_blank" href="http://meanjs.org/modules.html">Modules</a>
</li>
<li><a target="_blank" href="http://meanjs.org/changelog.html">Changelog</a>
</li>
<li><a target="_blank" href="http://meanjs.org/community.html">Community</a>
</li>
<li><a target="_blank" href="http://blog.meanjs.org">Blog</a>
</li>
</ul>
</p>
</div> -->
<br>Enjoy &amp; Keep Us Updated,
<br>The MedForms Team.
</section>

View File

@ -0,0 +1,35 @@
'use strict';
// Configuring the Articles module
angular.module('forms').run(['Menus',
function(Menus) {
// Set top bar menu items
Menus.addMenuItem('topbar', 'Forms', 'forms', 'dropdown', '/forms(/create)?');
Menus.addSubMenuItem('topbar', 'forms', 'List Forms', 'forms');
Menus.addSubMenuItem('topbar', 'forms', 'Create Form', 'forms/create');
}
]).filter('formValidity',
function(){
return function(formObj){
//get keys
var formKeys = Object.keys(formObj);
// console.log(formKeys);
//we only care about things that don't start with $
var fieldKeys = formKeys.filter(function(key){
return key[0] !== '$';
});
var fields = formObj.form_fields;
// fieldKeys.map(function(key){
// return formObj[key];
// });
var valid_count = fields.filter(function(field){
if(typeof field === 'object'){
return !!(field.fieldValue);
}
}).length;
return valid_count;
};
});

View File

@ -0,0 +1,41 @@
'use strict';
// Setting up route
angular.module('forms').config(['$stateProvider',
function($stateProvider) {
// Forms state routing
$stateProvider.
state('listForms', {
url: '/forms',
templateUrl: 'modules/forms/views/list-forms.client.view.html',
}).
state('createForm', {
url: '/forms/create',
templateUrl: 'modules/forms/views/create-form.client.view.html',
// parent: 'restricted',
// data: {
// roles: ['user', 'admin'],
// },
}).
state('viewForm', {
url: '/forms/:formId/admin',
templateUrl: 'modules/forms/views/view-form.client.view.html',
}).
state('viewPublicForm', {
url: '/forms/:formId',
templateUrl: 'modules/forms/views/view-public-form.client.view.html',
data: {
hideNav: true,
hideFooter: false
},
}).
state('editForm', {
url: '/forms/:formId/edit',
templateUrl: 'modules/forms/views/create-form.client.view.html',
// parent: 'restricted',
// data: {
// roles: ['user', 'admin'],
// },
});
}
]);

View File

@ -0,0 +1,234 @@
'use strict';
angular.module('forms').controller('EditFormController', ['$scope', '$state', 'Upload', '$stateParams', 'Principal', 'FormFields', 'Forms', 'CurrentForm', '$modal', '$location',
function ($scope, $state, Upload, $stateParams, Principal, FormFields, Forms, CurrentForm, $modal, $location) {
// Principal.identity().then(function(user){
// $scope.authentication.user = user;
// }).then(function(){
// console.log('aeouaoeuaoeuaou');
// console.log('isAuthenticated(): '+Principal.isAuthenticated());\
$scope.isNewForm = false;
$scope.log = '';
// Get current form if it exists, or create new one
if($stateParams.formId){
$scope.form = {};
var _form = Forms.get({ formId: $stateParams.formId}, function(form){
_form.pdf = form.pdf;
_form.$save();
$scope.form = angular.fromJson(angular.toJson(_form));
console.log(JSON.stringify($scope.form.pdf));
});
} else {
$scope.form = {};
$scope.form.form_fields = [];
$scope.isNewForm = true;
}
//PDF Functions
$scope.cancelUpload = function(){
//TBD
};
$scope.removePDF = function(){
$scope.form.pdf = null;
console.log('form.pdf exists: '+!!$scope.form.pdf);
};
$scope.uploadPDF = function(files) {
if (files && files.length) {
for (var i = 0; i < files.length; i++) {
var file = files[i];
Upload.upload({
url: '/upload/pdf',
fields: {
'user': $scope.form.admin,
'form': $scope.form
},
file: file
}).progress(function (evt) {
var progressPercentage = parseInt(100.0 * evt.loaded / evt.total);
$scope.log = 'progress: ' + progressPercentage + '% ' +
evt.config.file.name + '\n' + $scope.log;
}).success(function (data, status, headers, config) {
$scope.log = 'file ' + data.originalname + 'uploaded as '+ data.name +'. JSON: ' + JSON.stringify(data) + '\n' + $scope.log;
$scope.pdf = data;
$scope.form.pdf = data;
if(!$scope.$$phase) {
$scope.$apply();
}
console.log($scope.log);
console.log('$scope.pdf: '+$scope.pdf.name);
});
}
}
};
$scope.goToWithId = function(route, id) {
$state.transitionTo(route, { 'formId' : id }, { reload: true });
};
// Create new Form
$scope.createOrUpdate = function() {
if($scope.isNewForm){
// Create new Form object
var form = new Forms($scope.form);
form.$save(function(response) {
console.log('form created');
// console.log(response.pdf);
// Clear form fields
$scope.form = {};
// Redirect after save
$location.path('forms/' + response._id + '/admin');
}, function(errorResponse) {
console.log(errorResponse.data.message);
$scope.error = errorResponse.data.message;
});
} else{
console.log('update form');
$scope.update();
}
};
// Update existing Form
$scope.update = function() {
var form = new Forms($scope.form);
form.$update(function(response) {
console.log('form updated');
// console.log(response.pdf);
$location.path('forms/' + response._id + '/admin');
}, function(errorResponse) {
console.log(errorResponse.data.message);
$scope.error = errorResponse.data.message;
});
};
//Populate AddField with all available form field types
$scope.addField = {};
$scope.addField.types = FormFields.fields;
$scope.addField.new = $scope.addField.types[0].name;
$scope.addField.lastAddedID = 0;
// preview form mode
$scope.previewMode = false;
// previewForm - for preview purposes, form will be copied into this
// otherwise, actual form might get manipulated in preview mode
$scope.previewForm = {};
// accordion settings
$scope.accordion = {};
$scope.accordion.oneAtATime = true;
// create new field button click
$scope.addNewField = function(){
// incr field_id counter
$scope.addField.lastAddedID++;
var newField = {
'title' : 'New field - ' + ($scope.addField.lastAddedID),
'fieldType' : $scope.addField.new,
'fieldValue' : '',
'required' : true,
'disabled' : false
};
// put newField into fields array
$scope.form.form_fields.push(newField);
// console.log($scope.form.form_fields);
};
// deletes particular field on button click
$scope.deleteField = function (field_id){
for(var i = 0; i < $scope.form.form_fields.length; i++){
if($scope.form.form_fields[i].field_id === field_id){
$scope.form.form_fields.splice(i, 1);
break;
}
}
};
// add new option to the field
$scope.addOption = function (field){
if(!field.field_options)
field.field_options = [];
var lastOptionID = 0;
if(field.field_options[field.field_options.length-1])
lastOptionID = field.field_options[field.field_options.length-1].option_id;
// new option's id
var option_id = lastOptionID + 1;
var newOption = {
'option_id' : option_id,
'option_title' : 'Option ' + option_id,
'option_value' : option_id
};
// put new option into field_options array
field.field_options.push(newOption);
};
// delete particular option
$scope.deleteOption = function (field, option){
for(var i = 0; i < field.field_options.length; i++){
if(field.field_options[i].option_id === option.option_id){
field.field_options.splice(i, 1);
break;
}
}
};
// preview form
$scope.previewOn = function(){
if($scope.form.form_fields === null || $scope.form.form_fields.length === 0) {
var title = 'Error';
var msg = 'No fields added yet, please add fields to the form before preview.';
var btns = [{result:'ok', label: 'OK', cssClass: 'btn-primary'}];
// $dialog.messageBox(title, msg, btns).open();
}
else {
$scope.previewMode = !$scope.previewMode;
$scope.form.submitted = false;
angular.copy($scope.form, $scope.previewForm);
}
};
// hide preview form, go back to create mode
$scope.previewOff = function(){
$scope.previewMode = !$scope.previewMode;
$scope.form.submitted = false;
};
// decides whether field options block will be shown (true for dropdown and radio fields)
$scope.showAddOptions = function (field){
if(field.field_type === 'radio' || field.field_type === 'dropdown')
return true;
else
return false;
};
// });
}
]);

View File

@ -0,0 +1,22 @@
'use strict';
// Forms controller
angular.module('forms').controller('SubmitFormController', ['$scope', '$stateParams', '$state', 'Principal', 'Forms', 'CurrentForm','$http',
function($scope, $stateParams, $state, Principal, Forms, CurrentForm, $http) {
// Principal.identity().then(function(user){
// $scope.authentication.user = user;
// }).then(function(){
$scope.form = Forms.get({
formId: $stateParams.formId
});
CurrentForm.setForm($scope.form);
// console.log($scope.form);
// });
}
]);

View File

@ -0,0 +1,55 @@
'use strict';
// Forms controller
angular.module('forms').controller('ViewFormController', ['$scope', '$stateParams', '$state', 'Principal', 'Forms', 'CurrentForm','$http',
function($scope, $stateParams, $state, Principal, Forms, CurrentForm, $http) {
// Principal.identity().then(function(user){
// $scope.authentication.user = user;
// }).then(function(){
// Return all user's Forms
$scope.find = function() {
$scope.forms = Forms.query();
};
// Find a specific Form
$scope.findOne = function() {
$scope.form = Forms.get({
formId: $stateParams.formId
});
CurrentForm.setForm($scope.form);
};
// Remove existing Form
$scope.remove = function(form) {
if (form) {
form.$remove();
$http.delete('/forms/'+$scope.form._id).
success(function(data, status, headers){
console.log('form deleted successfully');
alert('Form deleted..');
$state.go('listForms');
});
} else {
$scope.form.$remove(function() {
console.log('remove');
$state.path('forms');
$http.delete('/forms/'+$scope.form._id).
success(function(data, status, headers){
console.log('form deleted successfully');
alert('Form deleted..');
$state.go('listForms');
});
});
}
};
// });
}
]);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,36 @@
'use strict';
angular.module('forms').directive('changeFocus', function() {
return {
scope:{
focusDownId: '@',
focusUpId: '@',
},
link: function(scope, elem, attrs) {
// console.log('aoeuaoeuaoeuaou');
scope.focusUp = function(){
if(!scope.$first) {
// console.log('aoeuaoeu');
elem[0].previousElementSibling.find('input').focus();
}
scope.apply();
};
scope.focusDown = function(){
if(!scope.$last) {
elem[0].nextElementSibling.focus();
}
scope.apply();
};
//Bind 'focus-down' click event to given dom element
angular.element('#' + scope.focusDownId).bind('click', function() {
scope.focusDown();
});
//Bind 'focus-up' click event to given dom element
angular.element('#' + scope.focusUpId).bind('click', function() {
scope.focusUp();
});
}
};
});

View File

@ -0,0 +1,51 @@
'use strict';
// coffeescript's for in loop
var __indexOf = [].indexOf || function(item) {
for (var i = 0, l = this.length; i < l; i++) {
if (i in this && this[i] === item) return i;
}
return -1;
};
angular.module('forms').directive('fieldDirective', function($http, $compile) {
var getTemplateUrl = function(field) {
var type = field.fieldType;
var templateUrl = './modules/forms/views/directiveViews/field/';
var supported_fields = [
'textfield',
'email',
'textarea',
'checkbox',
'date',
'dropdown',
'hidden',
'password',
'radio'
];
if (__indexOf.call(supported_fields, type) >= 0) {
return templateUrl += type + '.html';
}
};
var linker = function(scope, element) {
// GET template content from path
var templateUrl = getTemplateUrl(scope.field);
$http.get(templateUrl).success(function(data) {
element.html(data);
$compile(element.contents())(scope);
});
};
return {
template: '<div>{{field.title}}</div>',
restrict: 'E',
scope: {
field: '='
},
link: linker
};
});

View File

@ -0,0 +1,36 @@
'use strict';
angular.module('forms').directive('formDirective', ['$http', '$timeout', 'timeCounter',
function ($http, $timeout, timeCounter) {
return {
controller: function($scope){
timeCounter.startClock();
$scope.submit = function(){
var _timeElapsed = timeCounter.stopClock();
$scope.form.timeElapsed = _timeElapsed;
console.log($scope.form.timeElapsed);
$http.post('/forms/'+$scope.form._id,$scope.form).
success(function(data, status, headers){
console.log('form submitted successfully');
alert('Form submitted..');
$scope.form.submitted = true;
});
};
$scope.cancel = function(){
alert('Form canceled..');
};
},
templateUrl: './modules/forms/views/directiveViews/form/form.html',
restrict: 'E',
scope: {
form:'='
}
};
}
]);

View File

@ -0,0 +1,4 @@
'use strict';
// Use Application configuration module to register a new module
ApplicationConfiguration.registerModule('forms', ['ngFileUpload']);

View File

@ -0,0 +1,18 @@
'use strict';
//Forms service used for communicating with the forms REST endpoints
angular.module('forms').service('CurrentForm', ['Forms',
function(Forms){
//Private variables
var _form = {};
//Public Methods
this.getForm = function() {
return _form;
};
this.setForm = function(form) {
_form = form;
};
}
]);

View File

@ -0,0 +1,45 @@
'use strict';
angular.module('forms').service('FormFields', [
function() {
this.fields = [
{
name : 'textfield',
value : 'Textfield'
},
{
name : 'email',
value : 'E-mail'
},
{
name : 'password',
value : 'Password'
},
{
name : 'radio',
value : 'Radio Buttons'
},
{
name : 'dropdown',
value : 'Dropdown List'
},
{
name : 'date',
value : 'Date'
},
{
name : 'textarea',
value : 'Text Area'
},
{
name : 'checkbox',
value : 'Checkbox'
},
{
name : 'hidden',
value : 'Hidden'
}
];
}
]);

View File

@ -0,0 +1,28 @@
'use strict';
//Forms service used for communicating with the forms REST endpoints
angular.module('forms').factory('Forms', ['$resource',
function($resource) {
return $resource('forms/:formId', {
formId: '@_id'
}, {
'query' : {
method: 'GET',
isArray: false,
// "transformResponse": function (data) {
// var _data = JSON.parse(data);
// var _pdf = JSON.parse(data).pdf;
// _data.pdf = _pdf;
// return _data;
// }
},
'update': {
method: 'PUT'
},
'save': {
method: 'POST'
}
});
}
]);

View File

@ -0,0 +1,22 @@
'use strict';
angular.module('forms').service('timeCounter', [
function(){
var _startTime, _endTime, that=this;
this.timeSpent;
this.startClock = function(){
_startTime = Date.now();
console.log('Clock Started');
};
this.stopClock = function(){
_endTime = Date.now();
that.timeSpent = Math.abs(_endTime.valueOf() - _startTime.valueOf())/1000;
console.log('Clock Ended');
return that.timeSpent;
};
}
]);

View File

@ -0,0 +1,227 @@
<section data-ng-controller="EditFormController">
<div ng-if="isNewForm">
<h1>Create your form</h1> <br>
<blockquote>
<p>Select field type you want to add to the form below and click on 'Add Field' button. Don't forget to set field properties. After you finish creating the form, you can preview the form by clicking Preview Form button.</p>
</blockquote>
</div>
<div ng-if="!isNewForm">
<h2>Edit your form</h2> <br>
</div>
<div class="well">
<div class="form-fields" ng-hide="previewMode">
<div class="form-properties row">
<div class="col-sm-12"><h3>Form Title</h3></div>
</div>
<div class="row">
<div class="col-sm-4"><p style="margin-top:20px;"><input type="text" name="form-name" ng-disabled="previewMode" ng-model="form.title" style="width:200px; height:30px;"></p></div>
</div>
<div class="row">
<div class="col-sm-12">
<h3>Fields</h3>
</div>
</div>
<div class="row">
<div class="add-field col-md-3 col-sm-12">
<select ng-model="addField.new" ng-options="type.name as type.value for type in addField.types"></select>
<button type="submit" class="btn" ng-click="addNewField()">
<i class="icon-plus"></i> Add Field
</button>
</div>
<div class="col-sm-3 col-md-8 col-md-offset-1">
<p ng-show="form.form_fields.length == 0">No fields added yet.</p>
<accordion close-others="accordion.oneAtATime">
<accordion-group heading="{{field.title}}" ng-repeat="field in form.form_fields">
<div class="accordion-edit">
<button class="btn btn-danger pull-right" type="button" ng-click="deleteField(field.client_id)"><i class="icon-trash icon-white"></i> Delete</button>
<div class="row">
<div class="span2">Field ID: </div>
<div class="span4">{{field.client_id}}</div>
</div>
<div class="row">
<div class="span2">Field Type:</div>
<div class="span4">{{field.fieldType}}</div>
</div>
<div class="clear"></div> <hr>
<div class="row">
<div class="span2">Field Title:</div>
<div class="span4"><input type="text" ng-model="field.title" value="{{field.title}}"></div>
</div>
<div class="row">
<div class="span2">Field Default Value:</div>
<div class="span4"><input type="text" ng-model="field.fieldValue" value="{{field.fieldValue}}"></div>
</div>
<div class="row" ng-show="showAddOptions(field)">
<div class="span2">Field Options:</div>
<div class="span6">
<div ng-repeat="option in field.field_options">
<input type="text" ng-model="option.option_title" value="{{option.option_title}}">
<a class="btn btn-danger btn-mini right" type="button" ng-click="deleteOption(field, option)"><i class="icon-minus icon-white"></i></a>
<span class="label label-inverse">Value: {{ option.option_value }}</span>
</div>
<button class="btn btn-primary btn-small" type="button" ng-click="addOption(field)"><i class="icon-plus icon-white"></i> Add Option</button>
</div>
</div>
<div class="clear"></div> <hr>
<div class="row">
<div class="span2">Required:</div>
<div class="span4">
<label>
<input type="radio" ng-value="true" ng-selected ng-model="field.required"/>
&nbsp; Yes
</label>
<label>
<input type="radio" ng-value="false" ng-model="field.required"/>
&nbsp; No
</label>
</div>
</div>
<div class="clear"></div> <hr>
<div class="row">
<div class="span2">Disabled:</div>
<div class="span4">
<label>
<input type="radio" ng-value="true" ng-selected ng-model="field.disabled"/>
&nbsp; Yes
</label>
<label>
<input type="radio" ng-value="false" ng-model="field.disabled"/>
&nbsp; No
</label>
</div>
</div>
</div>
</accordion-group>
</accordion>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<h3>Form PDF</h3>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<h5>Upload your PDF</h5>
</div>
<div class="col-sm-12">
<div class="input-group ">
<div tabindex="-1" class="form-control file-caption">
<span class="file-caption-ellipsis" ng-if="!form.pdf"></span>
<div class="file-caption-name" ng-if="form.pdf">
{{form.pdf.originalname}}
</div>
</div>
<div class="input-group-btn">
<button type="button" ng-if="form.pdf" ng-click="removePDF();" title="Clear selected files" class="btn btn-danger fileinput-remove fileinput-remove-button">
<i class="glyphicon glyphicon-trash" ></i>
Delete
</button>
<button type="button" ng-if="form.pdfLoading" title="Abort ongoing upload" class="btn btn-default" ng-click="cancelUpload()">
<i class="glyphicon glyphicon-ban-circle"></i>
Cancel
</button>
<div class="btn btn-success btn-file" ngf-select ngf-change="uploadPDF($files)" ng-if="!form.pdf">
<i class="glyphicon glyphicon-upload"></i>
Upload your PDF
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<h5>Autogenerate Form?</h5>
</div>
<br><br>
<div class="col-sm-4">
<label>
<input type="radio" data-ng-value="true" ng-model="form.isGenerated" ng-required="true" />
&nbsp;<span>Yes</span>
</label>
<label>
<input type="radio" data-ng-value="false" ng-model="form.isGenerated" ng-required="true" />
&nbsp;<span>No</span>
</label>
<!-- <span class="required-error" ng-show="field.required && !field.fieldValue">* required</span> -->
</div>
</div>
<div class="row" ng-if="form.isGenerated">
<div class="col-sm-12">
<h5>Save Submissions as PDFs?</h5>
</div>
<br><br>
<div class="col-sm-4">
<label>
<input type="radio" data-ng-value="true" ng-model="form.autofillPDFs" ng-required="true" />
&nbsp;<span>Yes</span>
</label>
<label>
<input type="radio" data-ng-value="false" ng-model="form.autofillPDFs" ng-required="true" />
&nbsp;<span>No</span>
</label>
<!-- <span class="required-error" ng-show="field.required && !field.fieldValue">* required</span> -->
</div>
</div>
<br><br>
<div class="row">
<div class="col-sm-offset-4 col-sm-2">
<button class="btn btn-primary btn-large" type="button" ng-click="createOrUpdate()"><i class="icon-arrow-left icon-white"></i> Save Changes</button>
</div>
<div class="col-sm-2">
<button class="btn btn-primary" type="button" ng-click="previewOn()"><i class="icon-eye-open icon-white"></i> Preview Form</button>
<!-- <button class="btn btn-danger right" type="button" ng-click="reset()"><i class="icon-refresh icon-white"></i> Reset</button> -->
</div>
</div>
<br><hr>
<!-- <div class="text-center">
<a class="btn btn-small btn-primary" ng-show="!showJson" ng-click="showJson = true">Show form json object</a>
<a class="btn btn-small btn-inverse" ng-show="showJson" ng-click="showJson = false">Hide form json object</a><br><br>
</div>
<div ng-show="showJson">
<h4>Form object content:</h4>
<pre>{{ form | json }}</pre>
</div> -->
</div>
<div class="form-fields-preview" ng-show="previewMode">
<form-directive form="previewForm"></form-directive>
<p class="text-center">
<button class="btn btn-primary btn-large right" type="button" ng-click="previewOff()"><i class="icon-arrow-left icon-white"></i> Back to Create Mode</button>
</p>
</div>
</div>
</section>

View File

@ -0,0 +1,7 @@
<div class="field row">
<br>
<input ng-model="field.fieldValue" id="{{field.client_id}}" type="checkbox" ng-true-value="1" ng-false-value="0" ng-required="field.required" ng-disabled="field.disabled"/>
<label class="form-field-label" for="{{field.client_id}}" ng-cloak>{{field.title}}</label>
<span class="required-error" ng-show="field.required && field.fieldValue == 0">(* required)</span>
<div class="field row">

View File

@ -0,0 +1,10 @@
<div class="field row">
<div class="span2">{{field.title}}:</div>
<span class="required-error" ng-show="field.required && !field.fieldValue">* required </span>
<div class="span4">
<div class="control-group input-append">
<input type="text" ng-model="field.fieldValue" data-date-format="mm/dd/yyyy" bs-datepicker ng-required="field.required" ng-disabled="field.disabled">
<button type="button" class="btn" data-toggle="datepicker"><i class="icon-calendar"></i></button>
</div>
</div>
</div>

View File

@ -0,0 +1,14 @@
<div class="field row">
<div class="span2">{{field.title}}:</div>
<div class="span4">
<select ng-model="field.fieldValue" ng-required="field.required" ng-disabled="field.disabled">
<option ng-repeat="option in field.field_options"
ng-selected="option.option_value == field.fieldValue"
value="{{option.option_id}}">
{{option.option_title}}
</option>
</select>
<span class="required-error" ng-show="field.required && !field.fieldValue">* required</span>
</div>
</div>
<br>

View File

@ -0,0 +1,7 @@
<div class="field row">
<div class="span2">{{field.title}}:</div>
<div class="span4">
<input type="email" placeholder="Email" value="{{field.fieldValue}}" ng-model="field.fieldValue" ng-required="field.required" ng-disabled="field.disabled"/>
<span class="required-error" ng-show="field.required && !field.fieldValue">* required</span>
</div>
</div>

View File

@ -0,0 +1 @@
<input type="hidden" ng-model="field.fieldValue" value="{{field.fieldValue}}" ng-disabled="field.disabled">

View File

@ -0,0 +1,7 @@
<div class="field row">
<div class="span2">{{field.title}}:</div>
<div class="span4">
<input type="password" ng-model="field.fieldValue" value="{{field.fieldValue}}" ng-required="field.required" ng-disabled="field.disabled">
<span class="required-error" ng-show="field.required && !field.fieldValue">* required</span>
</div>
</div>

View File

@ -0,0 +1,13 @@
<div class="field row">
<div class="span2">{{field.title}}:</div>
<div class="span4">
<div ng-repeat="option in field.field_options" class="row-fluid">
<label>
<input type="radio" value="{{option.option_value}}" ng-model="field.fieldValue" ng-required="field.required" ng-disabled="field.disabled"/>
&nbsp;<span ng-bind="option.option_title"></span>
</label>
</div>
<span class="required-error" ng-show="field.required && !field.fieldValue">* required</span>
</div>
</div>
<br>

View File

@ -0,0 +1,7 @@
<div class="field row">
<div class="span2">{{field.title}}:</div>
<div class="span4">
<textarea type="text" ng-model="field.fieldValue" value="{{field.fieldValue}}" ng-required="field.required" ng-disabled="field.disabled"></textarea>
<span class="required-error" ng-show="field.required && !field.fieldValue">* required</span>
</div>
</div>

View File

@ -0,0 +1,11 @@
<li class="textfield required active visible field row">
<div class="span2">{{field.title}}:</div>
<div class="span4">
<input type="text"
ng-model="field.fieldValue"
value="field.fieldValue"
ng-required="field.required"
ng-disabled="field.disabled" changeFocus focus-up-id="focusUpButton" focus-down-id="focusDownButton">
<span class="required-error" ng-show="field.required && !field.fieldValue">* required</span>
</div>
</li>

View File

@ -0,0 +1,48 @@
<h2>{{ form.form_name }}</h2>
<div ng-show="!form.submitted">
<div class="field row">
<div class="col-sm-11 col-sm-offset-1"><h1>{{ form.title }}</h1>
<hr>
</div>
</div>
<br>
<br>
<form class="field row" name="form" ng-model="form" ng-repeat="field in form.form_fields" >
<!-- <ul class=" col-sm-11 col-sm-offset-1" style="margin-top: 50px; margin-bottom: 50px;" ng-repeat="field in form.form_fields" > -->
<field-directive field="field" >
</field-directive>
<!-- </ul> -->
</form>
<div class="row form-actions">
<p class="text-left col-sm-2 col-sm-offset-5">
<button class="btn btn-success right" type="button" ng-disabled="myForm.$valid" ng-click="submit()">
<i class="icon-edit icon-white"></i> Submit Form
</button>
</p>
</div>
</div>
<div ng-show="form.submitted">
<h3>Form Successfully submitted</h3>
<br><br><br>
<div class="row form-actions">
<p class="text-left col-sm-2">
<button class="btn btn-primary left" type="button">
<a href="/form/{{form.id}}" style="color:white;"> Submit again?</a>
</button>
</p>
</div>
<!-- <div ng-repeat="field in form.form_fields">
Field Title: {{ field.title }} <br>
Field Value: {{ field.fieldValue }} <br><br>
</div> -->
</div>

Some files were not shown because too many files have changed in this diff Show More