Merge pull request #1 from wodka/features/missing
Combine unfinished work and fix it
This commit is contained in:
commit
02fe527e52
@ -1,8 +1,20 @@
|
||||
.git
|
||||
.github
|
||||
.idea
|
||||
.vagrant
|
||||
coverage
|
||||
design
|
||||
e2e_coverage
|
||||
Vagrantfile
|
||||
data
|
||||
docker
|
||||
node_modules
|
||||
selenium
|
||||
.all-contributorsrc
|
||||
.csslintrc
|
||||
.editorconfig
|
||||
.env.example
|
||||
.gitignore
|
||||
.jshintrc
|
||||
.slugignore
|
||||
*.md
|
||||
conf.json
|
||||
docker-compose.yml
|
||||
Dockerfile
|
||||
Procfile
|
||||
protractor.conf.js
|
||||
|
||||
@ -8,7 +8,7 @@ root = true
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
end_of_line = lf
|
||||
indent_style = tab
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
|
||||
[{Dockerfile,Procfile}]
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -22,7 +22,7 @@ Oscar_Credentials.*
|
||||
npm-debug.log
|
||||
node_modules/
|
||||
public/lib/
|
||||
public/dist
|
||||
public/dist/
|
||||
app/tests/coverage/
|
||||
.bower-*/
|
||||
.idea/
|
||||
|
||||
@ -21,6 +21,7 @@
|
||||
"globals": { // Globals variables.
|
||||
"jasmine": true,
|
||||
"angular": true,
|
||||
"devel": false,
|
||||
"_": true,
|
||||
"saveAs": true,
|
||||
"ApplicationConfiguration": true,
|
||||
|
||||
99
Dockerfile
99
Dockerfile
@ -2,76 +2,51 @@ FROM node:10-alpine
|
||||
MAINTAINER OhMyForm <admin@ohmyform.com>
|
||||
|
||||
# Install some needed packages
|
||||
RUN apk add --no-cache \
|
||||
git \
|
||||
&& rm -rf /tmp/*
|
||||
RUN apk add --no-cache git \
|
||||
&& rm -rf /tmp/* \
|
||||
&& npm install --quiet -g grunt bower pm2 \
|
||||
&& npm cache clean --force \
|
||||
&& mkdir -p /opt/app/public/lib
|
||||
|
||||
## TODO: Crush these consecutive RUN's into a single run if possible.
|
||||
# Install NPM Global Libraries
|
||||
RUN npm install --quiet -g grunt bower pm2 && npm cache clean --force
|
||||
# to expose the public folder to other containers
|
||||
# VOLUME /opt/app
|
||||
|
||||
WORKDIR /opt/app
|
||||
RUN mkdir -p /opt/app/public/lib
|
||||
|
||||
## TODO: Optimize layers here as copy layers can be easily reduced if saner COPY usage is achieved.
|
||||
# Add bower.json
|
||||
COPY bower.json /opt/app/bower.json
|
||||
COPY .bowerrc /opt/app/.bowerrc
|
||||
|
||||
COPY ./process.yml /opt/app/process.yml
|
||||
COPY ./app /opt/app/app
|
||||
COPY ./public /opt/app/public
|
||||
COPY ./config /opt/app/config
|
||||
COPY ./gruntfile.js /opt/app/gruntfile.js
|
||||
COPY ./server.js /opt/app/server.js
|
||||
COPY ./scripts/create_admin.js /opt/app/scripts/create_admin.js
|
||||
|
||||
## TODO: Find a method that's better than this for passing ENV's if possible.
|
||||
# Set default ENV
|
||||
ENV NODE_ENV=development
|
||||
ENV SECRET_KEY=ChangeMeChangeMe
|
||||
#ENV MONGODB_URI=mongodb://mongo/ohmyform
|
||||
#ENV REDIS_URL=redis://redis:6379
|
||||
ENV PORT=5000
|
||||
ENV BASE_URL=localhost
|
||||
ENV SOCKET_PORT=20523
|
||||
ENV SIGNUP_DISABLED=FALSE
|
||||
ENV SUBDOMAINS_DISABLED=FALSE
|
||||
ENV ENABLE_CLUSTER_MODE=FALSE
|
||||
ENV MAILER_EMAIL_ID=ohmyform@localhost
|
||||
ENV MAILER_PASSWORD=
|
||||
ENV MAILER_FROM=ohmyform@localhost
|
||||
ENV MAILER_SERVICE_PROVIDER=
|
||||
ENV MAILER_SMTP_HOST=
|
||||
ENV MAILER_SMTP_PORT=
|
||||
ENV MAILER_SMTP_SECURE=
|
||||
ENV NODE_ENV=development \
|
||||
SECRET_KEY=ChangeMeChangeMe \
|
||||
PORT=5000 \
|
||||
BASE_URL=localhost \
|
||||
SOCKET_PORT=20523 \
|
||||
SIGNUP_DISABLED=FALSE \
|
||||
SUBDOMAINS_DISABLED=FALSE \
|
||||
ENABLE_CLUSTER_MODE=FALSE \
|
||||
MAILER_EMAIL_ID=ohmyform@localhost \
|
||||
MAILER_PASSWORD="" \
|
||||
MAILER_FROM=ohmyform@localhost \
|
||||
MAILER_SERVICE_PROVIDER="" \
|
||||
MAILER_SMTP_HOST="" \
|
||||
MAILER_SMTP_PORT="" \
|
||||
MAILER_SMTP_SECURE="" \
|
||||
CREATE_ADMIN=FALSE \
|
||||
ADMIN_EMAIL=admin@ohmyform.com \
|
||||
ADMIN_USERNAME=root \
|
||||
ADMIN_PASSWORD=root \
|
||||
APP_NAME=OhMyForm \
|
||||
APP_KEYWORDS="" \
|
||||
APP_DESC="" \
|
||||
COVERALLS_REPO_TOKEN="" \
|
||||
GOOGLE_ANALYTICS_ID="" \
|
||||
RAVEN_DSN=""
|
||||
|
||||
ENV CREATE_ADMIN=FALSE
|
||||
ENV ADMIN_EMAIL=admin@ohmyform.com
|
||||
ENV ADMIN_USERNAME=root
|
||||
ENV ADMIN_PASSWORD=root
|
||||
# keep .dockerignore up to date
|
||||
COPY . .
|
||||
|
||||
ENV APP_NAME=OhMyForm
|
||||
ENV APP_KEYWORDS=
|
||||
ENV APP_DESC=
|
||||
RUN npm install --only=production \
|
||||
&& bower install --allow-root -f \
|
||||
&& grunt build
|
||||
|
||||
# optional ENV settings
|
||||
ENV COVERALLS_REPO_TOKEN=
|
||||
ENV GOOGLE_ANALYTICS_ID=
|
||||
ENV RAVEN_DSN=
|
||||
|
||||
## TODO: Determine if it's necessary to have this COPY be it's own separate operation.
|
||||
# Copies the local package.json file to the container
|
||||
# and utilities docker container cache to not needing to rebuild
|
||||
# and install node_modules/ everytime we build the docker, but only
|
||||
# when the local package.json file changes.
|
||||
# Add npm package.json
|
||||
COPY ./package.json /opt/app/package.json
|
||||
RUN npm install --only=production --quiet
|
||||
RUN bower install --allow-root
|
||||
RUN grunt build
|
||||
## TODO: Determine if it would be possible to do a multi stage container where the prebuilt app is copied with nothing else from the build step.
|
||||
|
||||
## TODO: Make this configure things on startup in a sane way or don't if the operator passes any configuration files perhaps via a start.sh.
|
||||
# Run OhMyForm server
|
||||
CMD ["node", "server.js"]
|
||||
|
||||
25
Vagrantfile
vendored
25
Vagrantfile
vendored
@ -1,25 +0,0 @@
|
||||
# -*- mode: ruby -*-
|
||||
# vi: set ft=ruby :
|
||||
|
||||
## TODO: Reconsider this as we don't have this configuration anymore.
|
||||
|
||||
# All Vagrant configuration is done below. The "2" in Vagrant.configure
|
||||
# configures the configuration version (we support older styles for
|
||||
# backwards compatibility). Please don't change it unless you know what
|
||||
# you're doing.
|
||||
Vagrant.configure("2") do |config|
|
||||
|
||||
# Every Vagrant development environment requires a box. You can search for
|
||||
# boxes at https://atlas.hashicorp.com/search.
|
||||
config.vm.box = "ubuntu/trusty64"
|
||||
config.vm.network :forwarded_port, guest: 3000, host: 4567
|
||||
|
||||
config.vm.provision "docker" do |d|
|
||||
d.run "mongo",
|
||||
args: "-p 27017:27017 -d --name some-mongo"
|
||||
d.run "redis",
|
||||
args: "-p 6379:6379 -d --name some-redis"
|
||||
d.run "tellform/development",
|
||||
args: "-p 3000:3000 --link some-redis:redis-db --link some-mongo:db"
|
||||
end
|
||||
end
|
||||
37
app.json
37
app.json
@ -1,37 +0,0 @@
|
||||
{
|
||||
"name": "TellForm",
|
||||
"description": "Beautiful, opensource web forms",
|
||||
"repository": "https://github.com/tellform/tellform",
|
||||
"logo": "https://tellform.com/public/img/tellform_logo_black.png",
|
||||
"keywords": ["node", "express", "static", "mean"],
|
||||
"addons": ["mongolab", "sendgrid", "heroku-redis"],
|
||||
"env": {
|
||||
"SUBDOMAINS_DISABLED": {
|
||||
"description": "Disable support for running subdomains. (This should be true if you are not using your own custom domain.",
|
||||
"value": "TRUE"
|
||||
},
|
||||
"ENABLE_CLUSTER_MODE": {
|
||||
"description": "ENABLE support for running in cluster mode on pm2",
|
||||
"value": "FALSE"
|
||||
},
|
||||
"NODE_ENV": {
|
||||
"description": "Choose whether to run app in development or production mode",
|
||||
"value": "production"
|
||||
},
|
||||
"BASE_URL": {
|
||||
"description": "The url of your heroku app."
|
||||
},
|
||||
"SOCKET_URL": {
|
||||
"description": "Where you websockets will connect to (i.e. your heroku app url)"
|
||||
},
|
||||
"MAILER_SERVICE_PROVIDER": {
|
||||
"description": "Which mail service/API you will be using (i.e. SparkPost, Mandrill, etc)",
|
||||
"value": "SendGrid"
|
||||
}
|
||||
},
|
||||
"buildpacks": [
|
||||
{
|
||||
"url": "https://github.com/heroku/heroku-buildpack-nodejs#v111"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -9,7 +9,14 @@ var mongoose = require('mongoose'),
|
||||
FormSubmission = mongoose.model('FormSubmission'),
|
||||
config = require('../../config/config'),
|
||||
diff = require('deep-diff'),
|
||||
_ = require('lodash');
|
||||
_ = require('lodash'),
|
||||
nodemailer = require('nodemailer'),
|
||||
emailNotifications = require('../libs/send-email-notifications'),
|
||||
constants = require('../libs/constants'),
|
||||
helpers = require('./helpers.server.controller'),
|
||||
async = require('async');
|
||||
|
||||
var smtpTransport = nodemailer.createTransport(config.mailer.options);
|
||||
|
||||
/**
|
||||
* Delete a forms submissions
|
||||
@ -28,17 +35,7 @@ exports.deleteSubmissions = function(req, res) {
|
||||
return;
|
||||
}
|
||||
|
||||
form.analytics.visitors = [];
|
||||
form.save(function(formSaveErr){
|
||||
if(formSaveErr){
|
||||
res.status(400).send({
|
||||
message: errorHandler.getErrorMessage(formSaveErr)
|
||||
});
|
||||
return;
|
||||
}
|
||||
res.status(200).send('Form submissions successfully deleted');
|
||||
|
||||
});
|
||||
res.status(200).send('Form submissions successfully deleted');
|
||||
});
|
||||
};
|
||||
|
||||
@ -69,7 +66,54 @@ exports.createSubmission = function(req, res) {
|
||||
message: errorHandler.getErrorMessage(err)
|
||||
});
|
||||
}
|
||||
res.status(200).send('Form submission successfully saved');
|
||||
var form = req.body;
|
||||
var formFieldDict = emailNotifications.createFieldDict(form.form_fields);
|
||||
|
||||
async.waterfall([
|
||||
function(callback) {
|
||||
if (form.selfNotifications && form.selfNotifications.enabled) {
|
||||
if(form.selfNotifications.fromField){
|
||||
form.selfNotifications.fromEmails = formFieldDict[form.selfNotifications.fromField];
|
||||
} else {
|
||||
form.selfNotifications.fromEmails = config.mailer.options.from;
|
||||
}
|
||||
|
||||
emailNotifications.send(form.selfNotifications, formFieldDict, smtpTransport, function(err){
|
||||
if(err){
|
||||
return callback({
|
||||
message: 'Failure sending submission self-notification email'
|
||||
});
|
||||
}
|
||||
|
||||
callback();
|
||||
});
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
function(callback) {
|
||||
if (form.respondentNotifications && form.respondentNotifications.enabled && form.respondentNotifications.toField) {
|
||||
|
||||
form.respondentNotifications.toEmails = formFieldDict[form.respondentNotifications.toField];
|
||||
emailNotifications.send(form.respondentNotifications, formFieldDict, smtpTransport, function(err){
|
||||
if(err){
|
||||
return callback({
|
||||
message: 'Failure sending submission respondent-notification email'
|
||||
});
|
||||
}
|
||||
|
||||
callback();
|
||||
});
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
], function (err) {
|
||||
if(err){
|
||||
return res.status(400).send(err);
|
||||
}
|
||||
res.status(200).send('Form submission successfully saved');
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@ -82,7 +126,7 @@ exports.listSubmissions = function(req, res) {
|
||||
FormSubmission.find({ form: _form._id }).sort('created').lean().exec(function(err, _submissions) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
res.status(500).send({
|
||||
return res.status(500).send({
|
||||
message: errorHandler.getErrorMessage(err)
|
||||
});
|
||||
}
|
||||
@ -90,29 +134,183 @@ exports.listSubmissions = function(req, res) {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get Visitor Analytics Data for a given Form
|
||||
*/
|
||||
exports.getVisitorData = function(req, res) {
|
||||
var results = [];
|
||||
|
||||
Form.aggregate([
|
||||
{
|
||||
$match: {
|
||||
_id: mongoose.Types.ObjectId(req.form.id),
|
||||
admin: mongoose.Types.ObjectId(req.user.id)
|
||||
}
|
||||
},
|
||||
{
|
||||
$facet: {
|
||||
'deviceStatistics': [
|
||||
{
|
||||
$unwind: '$analytics.visitors'
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
_id: 0,
|
||||
deviceType: '$analytics.visitors.deviceType',
|
||||
SubmittedTimeElapsed: {
|
||||
$cond: [
|
||||
{
|
||||
$eq: ['$analytics.visitors.isSubmitted', true]
|
||||
},
|
||||
'$analytics.visitors.timeElapsed',
|
||||
0
|
||||
]
|
||||
},
|
||||
SubmittedResponses: {
|
||||
$cond: [
|
||||
{
|
||||
$eq: ['$analytics.visitors.isSubmitted', true]
|
||||
},
|
||||
1,
|
||||
0
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
$group: {
|
||||
_id: '$deviceType',
|
||||
total_time: { $sum: '$SubmittedTimeElapsed' },
|
||||
responses: { $sum: '$SubmittedResponses' },
|
||||
visits: { $sum: 1 }
|
||||
}
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
total_time: '$total_time',
|
||||
responses: '$responses',
|
||||
visits: '$visits',
|
||||
average_time: {
|
||||
$cond: [
|
||||
{ $eq: [ '$responses', 0 ] },
|
||||
0,
|
||||
{ $divide: ['$total_time', '$responses'] }
|
||||
]
|
||||
},
|
||||
conversion_rate: {
|
||||
$multiply: [
|
||||
100,
|
||||
{
|
||||
$cond: [
|
||||
{ $eq: [ '$visits', 0 ] },
|
||||
0,
|
||||
{ $divide: ['$responses', '$visits'] }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
'globalStatistics': [
|
||||
{
|
||||
$unwind: '$analytics.visitors'
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
_id: 0,
|
||||
deviceType: '$analytics.visitors.deviceType',
|
||||
SubmittedTimeElapsed: {
|
||||
$cond: [
|
||||
{
|
||||
$eq: ['$analytics.visitors.isSubmitted', true]
|
||||
},
|
||||
'$analytics.visitors.timeElapsed',
|
||||
0
|
||||
]
|
||||
},
|
||||
SubmittedResponses: {
|
||||
$cond: [
|
||||
{
|
||||
$eq: ['$analytics.visitors.isSubmitted', true]
|
||||
},
|
||||
1,
|
||||
0
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
$group: {
|
||||
_id: null,
|
||||
total_time: { $sum: '$SubmittedTimeElapsed' },
|
||||
responses: { $sum: '$SubmittedResponses' },
|
||||
visits: { $sum: 1 }
|
||||
}
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
_id: 0,
|
||||
total_time: '$total_time',
|
||||
responses: '$responses',
|
||||
visits: '$visits',
|
||||
average_time: {
|
||||
$cond: [
|
||||
{ $eq: [ '$responses', 0 ] },
|
||||
0,
|
||||
{ $divide: ['$total_time', '$responses'] }
|
||||
]
|
||||
},
|
||||
conversion_rate: {
|
||||
$multiply: [
|
||||
100,
|
||||
{
|
||||
$cond: [
|
||||
{ $eq: [ '$visits', 0 ] },
|
||||
0,
|
||||
{ $divide: ['$responses', '$visits'] }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
])
|
||||
.cursor()
|
||||
.exec()
|
||||
.on('end', function() {
|
||||
res.json(results);
|
||||
})
|
||||
.on('data', function(entry){
|
||||
results.push(entry);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new form
|
||||
*/
|
||||
exports.create = function(req, res) {
|
||||
|
||||
if(!req.body.form){
|
||||
return res.status(400).send({
|
||||
message: 'Invalid Input'
|
||||
});
|
||||
}
|
||||
var form = new Form(req.body.form);
|
||||
|
||||
var form = new Form(req.body.form);
|
||||
form.admin = req.user._id;
|
||||
|
||||
form.save(function(err) {
|
||||
debugger;
|
||||
form.save(function(err, createdForm) {
|
||||
if (err) {
|
||||
return res.status(500).send({
|
||||
message: errorHandler.getErrorMessage(err)
|
||||
});
|
||||
}
|
||||
|
||||
return res.json(form);
|
||||
createdForm = helpers.removeSensitiveModelData('private_form', createdForm.toJSON());
|
||||
return res.json(createdForm);
|
||||
});
|
||||
};
|
||||
|
||||
@ -123,16 +321,14 @@ exports.read = function(req, res) {
|
||||
if(!req.user || (req.form.admin.id !== req.user.id) ){
|
||||
readForRender(req, res);
|
||||
} else {
|
||||
var newForm = req.form.toJSON();
|
||||
|
||||
if (req.userId) {
|
||||
if(req.form.admin._id+'' === req.userId+''){
|
||||
return res.json(newForm);
|
||||
}
|
||||
if(!req.form){
|
||||
return res.status(404).send({
|
||||
message: 'Form Does Not Exist'
|
||||
});
|
||||
}
|
||||
|
||||
var newForm = helpers.removeSensitiveModelData('private_form', req.form.toJSON());
|
||||
|
||||
return res.json(newForm);
|
||||
}
|
||||
};
|
||||
@ -148,9 +344,7 @@ var readForRender = exports.readForRender = function(req, res) {
|
||||
});
|
||||
}
|
||||
|
||||
delete newForm.lastModified;
|
||||
delete newForm.__v;
|
||||
delete newForm.created;
|
||||
newForm = helpers.removeSensitiveModelData('public_form', newForm);
|
||||
|
||||
if(newForm.startPage && !newForm.startPage.showStart){
|
||||
delete newForm.startPage;
|
||||
@ -166,15 +360,12 @@ exports.update = function(req, res) {
|
||||
|
||||
var form = req.form;
|
||||
var updatedForm = req.body.form;
|
||||
if(form.form_fields === undefined){
|
||||
form.form_fields = [];
|
||||
}
|
||||
|
||||
if(form.analytics === undefined){
|
||||
if(!form.analytics && req.body.form.analytics){
|
||||
form.analytics = {
|
||||
visitors: [],
|
||||
gaCode: ''
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (req.body.changes) {
|
||||
@ -184,19 +375,23 @@ exports.update = function(req, res) {
|
||||
diff.applyChange(form._doc, true, change);
|
||||
});
|
||||
} else {
|
||||
if(!updatedForm){
|
||||
res.status(400).send({
|
||||
message: 'Updated Form is empty'
|
||||
});
|
||||
}
|
||||
|
||||
delete updatedForm.lastModified;
|
||||
delete updatedForm.created;
|
||||
delete updatedForm.id;
|
||||
delete updatedForm._id;
|
||||
delete updatedForm.__v;
|
||||
delete updatedForm.created;
|
||||
|
||||
//Unless we have 'admin' privileges, updating the form's admin is disabled
|
||||
if(updatedForm && req.user.roles.indexOf('admin') === -1) {
|
||||
delete updatedForm.admin;
|
||||
}
|
||||
|
||||
if(form.analytics === null){
|
||||
form.analytics.visitors = [];
|
||||
form.analytics.gaCode = '';
|
||||
}
|
||||
|
||||
//Do this so we can create duplicate fields
|
||||
var checkForValidId = new RegExp('^[0-9a-fA-F]{24}$');
|
||||
for(var i=0; i < req.body.form.form_fields.length; i++){
|
||||
@ -214,6 +409,7 @@ exports.update = function(req, res) {
|
||||
message: errorHandler.getErrorMessage(err)
|
||||
});
|
||||
} else {
|
||||
savedForm = helpers.removeSensitiveModelData('private_form', savedForm.toJSON());
|
||||
res.json(savedForm);
|
||||
}
|
||||
});
|
||||
@ -245,24 +441,53 @@ exports.list = function(req, res) {
|
||||
|
||||
Form.find(searchObj)
|
||||
.sort('-created')
|
||||
.select('title language admin submissions isLive')
|
||||
.populate('admin.username', 'admin._id')
|
||||
.select('title language isLive')
|
||||
.lean()
|
||||
.exec(function(err, forms) {
|
||||
if (err) {
|
||||
res.status(400).send({
|
||||
return res.status(400).send({
|
||||
message: errorHandler.getErrorMessage(err)
|
||||
});
|
||||
} else {
|
||||
for(var i=0; i<forms.length; i++){
|
||||
forms[i].numberOfResponses = 0;
|
||||
if(forms[i].submissions){
|
||||
forms[i].numberOfResponses = forms[i].submissions.length;
|
||||
delete forms[i].submissions;
|
||||
}
|
||||
}
|
||||
res.json(forms);
|
||||
}
|
||||
|
||||
var form_ids = forms.map(function(form){
|
||||
return form._id;
|
||||
});
|
||||
|
||||
//Get number of submissions for each form
|
||||
FormSubmission
|
||||
.aggregate()
|
||||
.match({
|
||||
form: {
|
||||
$in: form_ids
|
||||
}
|
||||
})
|
||||
.group({
|
||||
_id: '$form',
|
||||
responses: { $sum: 1 }
|
||||
})
|
||||
.cursor()
|
||||
.exec()
|
||||
.on('end', function() {
|
||||
forms = forms.map(function (form) {
|
||||
if (!form.submissionNum) {
|
||||
form.submissionNum = 0;
|
||||
}
|
||||
|
||||
return helpers.removeSensitiveModelData('private_form', form);
|
||||
});
|
||||
|
||||
res.json(forms);
|
||||
})
|
||||
.on('data', function(result){
|
||||
forms = forms.map(function (form) {
|
||||
if (_.isEqual(form._id, result._id)) {
|
||||
form.submissionNum = result.responses;
|
||||
}
|
||||
|
||||
return form;
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@ -275,7 +500,9 @@ exports.formByID = function(req, res, next, id) {
|
||||
message: 'Form is invalid'
|
||||
});
|
||||
}
|
||||
|
||||
Form.findById(id)
|
||||
.select('admin title language form_fields startPage endPage showFooter isLive design analytics.gaCode respondentNotifications selfNotifications')
|
||||
.populate('admin')
|
||||
.exec(function(err, form) {
|
||||
if (err) {
|
||||
@ -287,12 +514,7 @@ exports.formByID = function(req, res, next, id) {
|
||||
}
|
||||
else {
|
||||
//Remove sensitive information from User object
|
||||
var _form = form;
|
||||
_form.admin.password = null;
|
||||
_form.admin.salt = null;
|
||||
_form.provider = null;
|
||||
|
||||
req.form = _form;
|
||||
req.form = helpers.removeSensitiveModelData('private_form', form.toJSON());
|
||||
return next();
|
||||
}
|
||||
});
|
||||
@ -309,7 +531,7 @@ exports.formByIDFast = function(req, res, next, id) {
|
||||
}
|
||||
Form.findById(id)
|
||||
.lean()
|
||||
.select('title language form_fields startPage endPage hideFooter isLive design analytics.gaCode')
|
||||
.select('title language form_fields startPage endPage showFooter isLive design analytics.gaCode selfNotifications respondentNotifications')
|
||||
.exec(function(err, form) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
@ -320,13 +542,7 @@ exports.formByIDFast = function(req, res, next, id) {
|
||||
}
|
||||
else {
|
||||
//Remove sensitive information from User object
|
||||
var _form = form;
|
||||
if(_form.admin){
|
||||
_form.admin.password = null;
|
||||
_form.admin.salt = null;
|
||||
_form.provider = null;
|
||||
}
|
||||
req.form = _form;
|
||||
req.form = helpers.removeSensitiveModelData('public_form', form);
|
||||
return next();
|
||||
}
|
||||
});
|
||||
@ -334,11 +550,13 @@ exports.formByIDFast = function(req, res, next, id) {
|
||||
|
||||
/**
|
||||
* Form authorization middleware
|
||||
*
|
||||
* reject access if the owner of the form is not the current user and the user is not an admin
|
||||
*/
|
||||
exports.hasAuthorization = function(req, res, next) {
|
||||
var form = req.form;
|
||||
if (req.form.admin.id !== req.user.id && req.user.roles.indexOf('admin') === -1) {
|
||||
res.status(403).send({
|
||||
if (req.form.admin.id !== req.user.id && req.user.roles.indexOf('admin') < 0) {
|
||||
return res.status(403).send({
|
||||
message: 'User '+req.user.username+' is not authorized to edit Form: '+form.title
|
||||
});
|
||||
}
|
||||
|
||||
33
app/controllers/helpers.server.controller.js
Normal file
33
app/controllers/helpers.server.controller.js
Normal file
@ -0,0 +1,33 @@
|
||||
'use strict';
|
||||
|
||||
const constants = require('../libs/constants');
|
||||
const _ = require('lodash');
|
||||
|
||||
module.exports = {
|
||||
removeKeysFromDict: function(dict, keys){
|
||||
for(var i=0; i<keys.length; i++){
|
||||
var curr_key = keys[i];
|
||||
if( dict.hasOwnProperty(curr_key) ){
|
||||
delete dict[curr_key];
|
||||
}
|
||||
}
|
||||
return dict;
|
||||
},
|
||||
removeSensitiveModelData: function(type, actual_object){
|
||||
if (typeof actual_object.toJSON === 'function') {
|
||||
actual_object = actual_object.toJSON();
|
||||
}
|
||||
|
||||
var object = _.cloneDeep(actual_object);
|
||||
|
||||
if(constants.privateFields.hasOwnProperty(type)) {
|
||||
object = this.removeKeysFromDict(object, constants.privateFields[type]);
|
||||
}
|
||||
if(object.admin){
|
||||
object.admin = this.removeKeysFromDict(object.admin, constants.privateFields.private_user);
|
||||
}
|
||||
debugger;
|
||||
|
||||
return object;
|
||||
}
|
||||
};
|
||||
@ -12,7 +12,8 @@ var errorHandler = require('../errors.server.controller'),
|
||||
fs = require('fs'),
|
||||
i18n = require('i18n'),
|
||||
async = require('async'),
|
||||
pug = require('pug');
|
||||
pug = require('pug'),
|
||||
helpers = require('../helpers.server.controller');
|
||||
|
||||
var nev = require('email-verification')(mongoose);
|
||||
|
||||
@ -60,7 +61,7 @@ config_nev();
|
||||
|
||||
exports.validateVerificationToken = function(req, res){
|
||||
|
||||
const fn = pug.compileFile(__dirname + "/../../views/welcome.email.view.pug");
|
||||
const fn = pug.compileFile(__dirname + '/../../views/welcome.email.view.pug');
|
||||
var renderedHtml = fn(res.locals);
|
||||
|
||||
var emailTemplate = {
|
||||
@ -83,7 +84,7 @@ exports.validateVerificationToken = function(req, res){
|
||||
};
|
||||
|
||||
exports.resendVerificationEmail = function(req, res, next){
|
||||
const fn = pug.compileFile(__dirname + "/../../views/verification.email.view.pug");
|
||||
const fn = pug.compileFile(__dirname + '/../../views/verification.email.view.pug');
|
||||
var renderedHtml = fn(res.locals);
|
||||
|
||||
var emailTemplate = {
|
||||
@ -117,7 +118,7 @@ exports.signup = function(req, res) {
|
||||
var user = new User(req.body);
|
||||
|
||||
// Set language to visitor's language
|
||||
user.language = req.cookies['userLang'];
|
||||
user.language = req.cookies.userLang;
|
||||
|
||||
// Add missing user fields
|
||||
user.provider = 'local';
|
||||
@ -125,7 +126,6 @@ exports.signup = function(req, res) {
|
||||
// Then save the temporary user
|
||||
nev.createTempUser(user, function (err, existingPersistentUser, newTempUser) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
return res.status(400).send({
|
||||
message: errorHandler.getErrorMessage(err)
|
||||
});
|
||||
@ -133,7 +133,7 @@ exports.signup = function(req, res) {
|
||||
|
||||
// new user created
|
||||
if (newTempUser) {
|
||||
const fn = pug.compileFile(__dirname + "/../../views/verification.email.view.pug");
|
||||
const fn = pug.compileFile(__dirname + '/../../views/verification.email.view.pug');
|
||||
var renderedHtml = fn(res.locals);
|
||||
|
||||
var URL = newTempUser[nev.options.URLFieldName];
|
||||
@ -179,6 +179,8 @@ exports.signin = function(req, res, next) {
|
||||
}
|
||||
|
||||
res.cookie('langCookie', user.language, { maxAge: 90000, httpOnly: true });
|
||||
|
||||
user = helpers.removeSensitiveModelData('private_user', user.toJSON());
|
||||
return res.json(user);
|
||||
});
|
||||
}
|
||||
@ -198,16 +200,12 @@ exports.signout = function(req, res) {
|
||||
|
||||
/* Generate API Key for User */
|
||||
exports.generateAPIKey = function(req, res) {
|
||||
if (!req.isAuthenticated()){
|
||||
return res.status(400).send({
|
||||
message: 'User is not Authorized'
|
||||
});
|
||||
}
|
||||
|
||||
User.findById(req.user.id)
|
||||
.exec( function(err, user) {
|
||||
if (err) {
|
||||
return res.status(400).send(err);
|
||||
return res.status(400).send({
|
||||
message: errorHandler.getErrorMessage(err)
|
||||
});
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
@ -226,12 +224,8 @@ exports.generateAPIKey = function(req, res) {
|
||||
}
|
||||
|
||||
var newUser = _user.toObject();
|
||||
delete newUser.salt;
|
||||
delete newUser.__v;
|
||||
delete newUser.passwordHash;
|
||||
delete newUser.provider;
|
||||
|
||||
return res.json(newUser);
|
||||
return res.json({ id: newUser._id, apiKey: newUser.apiKey });
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@ -3,36 +3,7 @@
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var _ = require('lodash'),
|
||||
mongoose = require('mongoose'),
|
||||
User = mongoose.model('User');
|
||||
|
||||
/**
|
||||
* User middleware
|
||||
*/
|
||||
exports.userByID = function (req, res, next, id) {
|
||||
if (!mongoose.Types.ObjectId.isValid(id)) {
|
||||
return res.status(400).send({
|
||||
message: 'User is invalid'
|
||||
});
|
||||
}
|
||||
|
||||
User.findOne({
|
||||
_id: id
|
||||
}).exec(function (err, user) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
} else if (!user) {
|
||||
return res.status(404).send({
|
||||
message: 'User does not exist'
|
||||
});
|
||||
}
|
||||
|
||||
req.profile = user;
|
||||
next();
|
||||
});
|
||||
};
|
||||
|
||||
var auth = require('../../../config/passport_helpers');
|
||||
/**
|
||||
* Require login routing middleware
|
||||
*/
|
||||
@ -45,22 +16,3 @@ exports.requiresLogin = function(req, res, next) {
|
||||
return 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'
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
@ -81,10 +81,9 @@ exports.forgot = function(req, res) {
|
||||
}
|
||||
},
|
||||
function(token, user, done) {
|
||||
const fn = pug.compileFile(__dirname + "/../../views/templates/reset-password-email.server.view.pug");
|
||||
res.locals['url'] = 'http://' + req.headers.host + '/auth/reset/' + token;
|
||||
const fn = pug.compileFile(__dirname + '/../../views/templates/reset-password-email.server.view.pug');
|
||||
res.locals.url = 'http://' + req.headers.host + '/auth/reset/' + token;
|
||||
|
||||
console.log(res.locals);
|
||||
var renderedHtml = fn(res.locals);
|
||||
done(null, renderedHtml, user);
|
||||
},
|
||||
@ -98,10 +97,10 @@ exports.forgot = function(req, res) {
|
||||
};
|
||||
|
||||
var userEmail = user.email;
|
||||
var user = userEmail.split('@')[0];
|
||||
var emailUsername = userEmail.split('@')[0];
|
||||
var domain = userEmail.split('@')[1];
|
||||
|
||||
var obfuscatedUser = user.substring(0, 1) + user.substring(1).replace(/./g, '*');
|
||||
var obfuscatedUser = emailUsername.substring(0, 1) + emailUsername.substring(1).replace(/./g, '*');
|
||||
var domainName = domain.split('.')[0];
|
||||
var tld = domain.split('.')[1];
|
||||
|
||||
@ -114,7 +113,6 @@ exports.forgot = function(req, res) {
|
||||
}
|
||||
], function(err, obfuscatedEmail) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
return res.status(400).send({
|
||||
message: 'Couldn\'t send reset password email due to internal server errors. Please contact support at team@tellform.com.'
|
||||
});
|
||||
@ -142,9 +140,9 @@ exports.validateResetToken = function(req, res) {
|
||||
});
|
||||
}
|
||||
if (!user) {
|
||||
return res.redirect('/#!/password/reset/invalid');
|
||||
return res.redirect(400, '/#!/password/reset/invalid');
|
||||
}
|
||||
|
||||
|
||||
res.redirect('/#!/password/reset/' + req.params.token);
|
||||
});
|
||||
};
|
||||
@ -187,13 +185,13 @@ exports.reset = function(req, res, next) {
|
||||
done(null, savedUser);
|
||||
});
|
||||
} else {
|
||||
done('Password reset token is invalid or has expired.', null);
|
||||
done('invalid_reset_token', null);
|
||||
}
|
||||
});
|
||||
},
|
||||
function(user, done) {
|
||||
const fn = pug.compileFile(__dirname + "/../../views/templates/reset-password-confirm-email.server.view.pug");
|
||||
var renderedHtml = fn(res.locals);
|
||||
const fn = pug.compileFile(__dirname + '/../../views/templates/reset-password-confirm-email.server.view.pug');
|
||||
const renderedHtml = fn(res.locals);
|
||||
done(null, renderedHtml, user);
|
||||
},
|
||||
// If valid email, send reset email using service
|
||||
@ -211,12 +209,18 @@ exports.reset = function(req, res, next) {
|
||||
}
|
||||
], function(err) {
|
||||
if (err) {
|
||||
res.status(500).send({
|
||||
if(err === 'invalid_reset_token'){
|
||||
return res.status(400).send({
|
||||
message: 'Password reset token is invalid or has expired.'
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(500).send({
|
||||
message: err.message || err
|
||||
});
|
||||
}
|
||||
|
||||
return res.json({
|
||||
res.json({
|
||||
message: 'Successfully changed your password!'
|
||||
});
|
||||
});
|
||||
|
||||
@ -5,7 +5,8 @@
|
||||
*/
|
||||
var _ = require('lodash'),
|
||||
errorHandler = require('../errors.server.controller.js'),
|
||||
mongoose = require('mongoose');
|
||||
mongoose = require('mongoose'),
|
||||
helpers = require('../helpers.server.controller');
|
||||
|
||||
/**
|
||||
* Update user details
|
||||
@ -14,47 +15,38 @@ exports.update = function(req, res) {
|
||||
// Init Variables
|
||||
var user = req.user;
|
||||
|
||||
// For security measurement we remove the roles from the req.body object
|
||||
// To improve security 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();
|
||||
debugger;
|
||||
|
||||
user.save(function(err) {
|
||||
if (err) {
|
||||
return res.status(500).send({
|
||||
message: errorHandler.getErrorMessage(err)
|
||||
});
|
||||
}
|
||||
req.login(user, function(loginErr) {
|
||||
if (err) {
|
||||
res.status(500).send(loginErr);
|
||||
} else {
|
||||
res.json(user);
|
||||
}
|
||||
// Merge existing user
|
||||
user = _.extend(user, req.body);
|
||||
user.updated = Date.now();
|
||||
|
||||
user.save(function(err) {
|
||||
if (err) {
|
||||
return res.status(500).send({
|
||||
message: errorHandler.getErrorMessage(err)
|
||||
});
|
||||
|
||||
}
|
||||
req.login(user, function(loginErr) {
|
||||
if (err) {
|
||||
res.status(500).send(loginErr);
|
||||
} else {
|
||||
user = helpers.removeSensitiveModelData('private_user', user.toJSON());
|
||||
res.json(user);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
res.status(401).send({
|
||||
message: 'User is not signed in'
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Send User
|
||||
*/
|
||||
exports.getUser = function(req, res) {
|
||||
var _user = req.user;
|
||||
delete _user.password;
|
||||
delete _user.salt;
|
||||
delete _user.provider;
|
||||
delete _user.__v;
|
||||
var user = helpers.removeSensitiveModelData('private_user', req.user.toJSON());
|
||||
|
||||
res.json(req.user || null);
|
||||
|
||||
res.end();
|
||||
return res.json(user);
|
||||
};
|
||||
|
||||
@ -1,42 +1,54 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
fieldTypes: ['textfield',
|
||||
'date',
|
||||
'email',
|
||||
'link',
|
||||
'legal',
|
||||
'url',
|
||||
'textarea',
|
||||
'statement',
|
||||
'welcome',
|
||||
'thankyou',
|
||||
'file',
|
||||
'dropdown',
|
||||
'scale',
|
||||
'rating',
|
||||
'radio',
|
||||
'checkbox',
|
||||
'hidden',
|
||||
'yes_no',
|
||||
'natural',
|
||||
'stripe',
|
||||
'number'],
|
||||
var constants = module.exports = {
|
||||
|
||||
ratingShapeTypes: ['Heart',
|
||||
'Star',
|
||||
'thumbs-up',
|
||||
'thumbs-down',
|
||||
'Circle',
|
||||
'Square',
|
||||
'Check Circle',
|
||||
'Smile Outlined',
|
||||
'Hourglass',
|
||||
'bell',
|
||||
'Paper Plane',
|
||||
'Comment',
|
||||
'Trash'],
|
||||
extraneousFormFieldProps: [
|
||||
'validFieldTypes',
|
||||
'disabled',
|
||||
'required',
|
||||
'isSubmission',
|
||||
'title',
|
||||
'fieldOptions',
|
||||
'ratingOptions',
|
||||
'logicJump',
|
||||
'description',
|
||||
'created',
|
||||
'lastModified',
|
||||
'deletePreserved'
|
||||
],
|
||||
|
||||
fieldTypes: [
|
||||
'textfield',
|
||||
'date',
|
||||
'email',
|
||||
'legal',
|
||||
'textarea',
|
||||
'link',
|
||||
'statement',
|
||||
'dropdown',
|
||||
'rating',
|
||||
'radio',
|
||||
'hidden',
|
||||
'yes_no',
|
||||
'number'
|
||||
],
|
||||
|
||||
ratingShapeTypes: [
|
||||
'Heart',
|
||||
'Star',
|
||||
'thumbs-up',
|
||||
'thumbs-down',
|
||||
'Circle',
|
||||
'Square',
|
||||
'Check Circle',
|
||||
'Smile Outlined',
|
||||
'Hourglass',
|
||||
'bell',
|
||||
'Paper Plane',
|
||||
'Comment',
|
||||
'Trash'
|
||||
],
|
||||
|
||||
deviceTypes: ['desktop', 'phone', 'tablet', 'other'],
|
||||
languageTypes: ['en', 'fr', 'es', 'it', 'de'],
|
||||
|
||||
@ -56,6 +68,13 @@ module.exports = {
|
||||
'Deutsch': 'de'
|
||||
},
|
||||
|
||||
privateFields: {
|
||||
'public_form': ['__v', 'analytics.visitors', 'analytics.views', 'analytics.conversionRate', 'analytics.fields', 'lastModified', 'created'],
|
||||
'private_form': ['__v'],
|
||||
'public_user': ['passwordHash', 'password', 'provider', 'salt', 'lastModified', 'created', 'resetPasswordToken', 'resetPasswordExpires', 'token', 'apiKey', '__v'],
|
||||
'private_user': ['passwordHash', 'password', 'provider', 'salt', 'resetPasswordToken', 'resetPasswordExpires', 'token', '__v']
|
||||
},
|
||||
|
||||
expressionStringTypes: ['field == static',
|
||||
'field != static',
|
||||
'field > static',
|
||||
@ -72,8 +91,9 @@ module.exports = {
|
||||
userRoleTypes: ['user', 'admin', 'superuser'],
|
||||
|
||||
regex: {
|
||||
username: /^[a-zA-Z0-9\-]+$/,
|
||||
url: /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)/,
|
||||
hexCode: /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/,
|
||||
email: /^(([^<>()\[\]\\.,;:\s@']+(\.[^<>()\[\]\\.,;:\s@']+)*)|('.+'))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
||||
email: /^(([^<>()\[\]\\.,;:\s@']+(\.[^<>()\[\]\\.,;:\s@']+)*)|('.+'))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
|
||||
}
|
||||
};
|
||||
48
app/libs/send-email-notifications.js
Normal file
48
app/libs/send-email-notifications.js
Normal file
@ -0,0 +1,48 @@
|
||||
'use strict';
|
||||
const jsdom = require('jsdom');
|
||||
var JSDOM = jsdom.JSDOM;
|
||||
|
||||
module.exports = {
|
||||
send: function(emailSettings, emailTemplateVars, smtpTransport, cb){
|
||||
var parsedTemplate = this.parseTemplate(emailSettings.htmlTemplate, emailTemplateVars, false);
|
||||
var parsedSubject = this.parseTemplate(emailSettings.subject, emailTemplateVars, true);
|
||||
|
||||
var mailOptions = {
|
||||
replyTo: emailSettings.fromEmails,
|
||||
from: 'noreply@tellform.com',
|
||||
cc: emailSettings.toEmails,
|
||||
subject: parsedSubject,
|
||||
html: parsedTemplate
|
||||
};
|
||||
|
||||
smtpTransport.sendMail(mailOptions, function(err){
|
||||
cb(err);
|
||||
});
|
||||
},
|
||||
|
||||
parseTemplate: function(emailTemplate, emailTemplateVars, onlyText){
|
||||
var dom = new JSDOM('<!doctype html>'+emailTemplate);
|
||||
|
||||
Object.keys(emailTemplateVars).forEach(function (key) {
|
||||
var elem = dom.window.document.querySelector('span.placeholder-tag[data-id=\'' + key + '\']');
|
||||
if(elem !== null){
|
||||
elem.outerHTML = emailTemplateVars[key];
|
||||
}
|
||||
});
|
||||
|
||||
if(onlyText){
|
||||
return dom.window.document.documentElement.textContent;
|
||||
}
|
||||
return dom.serialize();
|
||||
},
|
||||
|
||||
createFieldDict: function(form_fields){
|
||||
var formFieldDict = {};
|
||||
form_fields.forEach(function(field){
|
||||
if(field.hasOwnProperty('fieldValue') && field.hasOwnProperty('_id')){
|
||||
formFieldDict[field._id] = String(field.fieldValue);
|
||||
}
|
||||
});
|
||||
return formFieldDict;
|
||||
}
|
||||
};
|
||||
@ -2,38 +2,36 @@
|
||||
|
||||
// Plugin
|
||||
module.exports = function timestamp (schema, options) {
|
||||
options || (options = {})
|
||||
options = options || (options === {});
|
||||
|
||||
// Options
|
||||
var fields = {}
|
||||
, createdPath = options.createdPath || 'created'
|
||||
, modifiedPath = options.modifiedPath || 'modified'
|
||||
, useVirtual = (options.useVirtual !== undefined)
|
||||
? options.useVirtual
|
||||
: true
|
||||
var fields = {},
|
||||
createdPath = options.createdPath || 'created',
|
||||
modifiedPath = options.modifiedPath || 'modified',
|
||||
useVirtual = (options.useVirtual !== undefined) ? options.useVirtual : true;
|
||||
|
||||
// Add paths to schema if not present
|
||||
if (!schema.paths[createdPath]) {
|
||||
fields[modifiedPath] = { type: Date }
|
||||
fields[modifiedPath] = { type: Date };
|
||||
}
|
||||
if (useVirtual) {
|
||||
// Use the ObjectID for extracting the created time
|
||||
schema.virtual(createdPath).get(function () {
|
||||
return new Date(this._id.generationTime * 1000)
|
||||
})
|
||||
return new Date(this._id.generationTime * 1000);
|
||||
});
|
||||
} else {
|
||||
if (!schema.paths[createdPath]) {
|
||||
fields[createdPath] = {
|
||||
type: Date
|
||||
, default: Date.now
|
||||
}
|
||||
type: Date,
|
||||
default: Date.now
|
||||
};
|
||||
}
|
||||
}
|
||||
schema.add(fields)
|
||||
schema.add(fields);
|
||||
|
||||
// Update the modified timestamp on save
|
||||
schema.pre('save', function (next) {
|
||||
this[modifiedPath] = new Date
|
||||
next()
|
||||
})
|
||||
}
|
||||
this[modifiedPath] = new Date();
|
||||
next();
|
||||
});
|
||||
};
|
||||
@ -8,19 +8,11 @@ var mongoose = require('mongoose'),
|
||||
_ = require('lodash'),
|
||||
timeStampPlugin = require('../libs/timestamp.server.plugin'),
|
||||
async = require('async'),
|
||||
Random = require('random-js'),
|
||||
mt = Random.engines.mt19937();
|
||||
|
||||
|
||||
mt.autoSeed();
|
||||
constants = require('../libs/constants');
|
||||
|
||||
//Mongoose Models
|
||||
var FieldSchema = require('./form_field.server.model.js');
|
||||
|
||||
var FormSubmissionSchema = require('./form_submission.server.model.js'),
|
||||
FormSubmission = mongoose.model('FormSubmission', FormSubmissionSchema);
|
||||
|
||||
|
||||
var ButtonSchema = new Schema({
|
||||
url: {
|
||||
type: String,
|
||||
@ -47,8 +39,8 @@ var VisitorDataSchema = new Schema({
|
||||
referrer: {
|
||||
type: String
|
||||
},
|
||||
lastActiveField: {
|
||||
type: Schema.Types.ObjectId
|
||||
filledOutFields: {
|
||||
type: [Schema.Types.ObjectId]
|
||||
},
|
||||
timeElapsed: {
|
||||
type: Number
|
||||
@ -57,11 +49,12 @@ var VisitorDataSchema = new Schema({
|
||||
type: Boolean
|
||||
},
|
||||
language: {
|
||||
type: String
|
||||
type: String,
|
||||
enum: constants.languageTypes,
|
||||
default: 'en',
|
||||
},
|
||||
ipAddr: {
|
||||
type: String,
|
||||
default: ''
|
||||
type: String
|
||||
},
|
||||
deviceType: {
|
||||
type: String,
|
||||
@ -100,13 +93,10 @@ var FormSchema = new Schema({
|
||||
},
|
||||
visitors: [VisitorDataSchema]
|
||||
},
|
||||
|
||||
form_fields: [FieldSchema],
|
||||
submissions: [{
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'FormSubmission'
|
||||
}],
|
||||
|
||||
form_fields: {
|
||||
type: [FieldSchema],
|
||||
default: []
|
||||
},
|
||||
admin: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
@ -149,17 +139,59 @@ var FormSchema = new Schema({
|
||||
buttons:[ButtonSchema]
|
||||
},
|
||||
|
||||
hideFooter: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
selfNotifications: {
|
||||
fromField: {
|
||||
type: String
|
||||
},
|
||||
toEmails: {
|
||||
type: String
|
||||
},
|
||||
subject: {
|
||||
type: String
|
||||
},
|
||||
htmlTemplate: {
|
||||
type: String
|
||||
},
|
||||
enabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
respondentNotifications: {
|
||||
toField: {
|
||||
type: String
|
||||
},
|
||||
fromEmails: {
|
||||
type: String,
|
||||
match: [/.+\@.+\..+/, 'Please fill a valid email address']
|
||||
},
|
||||
subject: {
|
||||
type: String,
|
||||
default: 'Tellform: Thank you for filling out this TellForm'
|
||||
},
|
||||
htmlTemplate: {
|
||||
type: String,
|
||||
default: 'Hello, <br><br> We’ve received your submission. <br><br> Thank you & have a nice day!',
|
||||
},
|
||||
enabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
showFooter: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
|
||||
isLive: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
|
||||
design: {
|
||||
colors:{
|
||||
colors: {
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
match: [/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/],
|
||||
@ -190,98 +222,6 @@ var FormSchema = new Schema({
|
||||
}
|
||||
}, formSchemaOptions);
|
||||
|
||||
/*
|
||||
** In-Form Analytics Virtual Attributes
|
||||
*/
|
||||
FormSchema.virtual('analytics.views').get(function () {
|
||||
if(this.analytics && this.analytics.visitors && this.analytics.visitors.length > 0){
|
||||
return this.analytics.visitors.length;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
|
||||
FormSchema.virtual('analytics.submissions').get(function () {
|
||||
return this.submissions.length;
|
||||
});
|
||||
|
||||
FormSchema.virtual('analytics.conversionRate').get(function () {
|
||||
if(this.analytics && this.analytics.visitors && this.analytics.visitors.length > 0){
|
||||
return this.submissions.length/this.analytics.visitors.length*100;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
|
||||
FormSchema.virtual('analytics.fields').get(function () {
|
||||
var fieldDropoffs = [];
|
||||
var visitors = this.analytics.visitors;
|
||||
var that = this;
|
||||
|
||||
if(!this.form_fields || this.form_fields.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for(var i=0; i<this.form_fields.length; i++){
|
||||
var field = this.form_fields[i];
|
||||
|
||||
if(field && !field.deletePreserved){
|
||||
|
||||
var dropoffViews = _.reduce(visitors, function(sum, visitorObj){
|
||||
|
||||
if(visitorObj.lastActiveField+'' === field._id+'' && !visitorObj.isSubmitted){
|
||||
return sum + 1;
|
||||
}
|
||||
return sum;
|
||||
}, 0);
|
||||
|
||||
var continueViews, nextIndex;
|
||||
|
||||
if(i !== this.form_fields.length-1){
|
||||
continueViews = _.reduce(visitors, function(sum, visitorObj){
|
||||
nextIndex = that.form_fields.indexOf(_.find(that.form_fields, function(o) {
|
||||
return o._id+'' === visitorObj.lastActiveField+'';
|
||||
}));
|
||||
|
||||
if(nextIndex > i){
|
||||
return sum + 1;
|
||||
}
|
||||
return sum;
|
||||
}, 0);
|
||||
} else {
|
||||
continueViews = _.reduce(visitors, function(sum, visitorObj){
|
||||
if(visitorObj.lastActiveField+'' === field._id+'' && visitorObj.isSubmitted){
|
||||
return sum + 1;
|
||||
}
|
||||
return sum;
|
||||
}, 0);
|
||||
|
||||
}
|
||||
|
||||
var totalViews = dropoffViews+continueViews;
|
||||
var continueRate = 0;
|
||||
var dropoffRate = 0;
|
||||
|
||||
if(totalViews > 0){
|
||||
continueRate = (continueViews/totalViews*100).toFixed(0);
|
||||
dropoffRate = (dropoffViews/totalViews*100).toFixed(0);
|
||||
}
|
||||
|
||||
fieldDropoffs[i] = {
|
||||
dropoffViews: dropoffViews,
|
||||
responses: continueViews,
|
||||
totalViews: totalViews,
|
||||
continueRate: continueRate,
|
||||
dropoffRate: dropoffRate,
|
||||
field: field
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return fieldDropoffs;
|
||||
});
|
||||
|
||||
FormSchema.plugin(timeStampPlugin, {
|
||||
createdPath: 'created',
|
||||
modifiedPath: 'lastModified',
|
||||
@ -289,159 +229,16 @@ FormSchema.plugin(timeStampPlugin, {
|
||||
});
|
||||
|
||||
FormSchema.pre('save', function (next) {
|
||||
switch(this.language){
|
||||
case 'spanish':
|
||||
this.language = 'es';
|
||||
break;
|
||||
case 'french':
|
||||
this.language = 'fr';
|
||||
break;
|
||||
case 'italian':
|
||||
this.language = 'it';
|
||||
break;
|
||||
case 'german':
|
||||
this.language = 'de';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
if(this.form_fields && this.form_fields.length){
|
||||
this.form_fields = this.form_fields.filter(function(field){
|
||||
return !field.deletePreserved;
|
||||
});
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
function getDeletedIndexes(needle, haystack){
|
||||
var deletedIndexes = [];
|
||||
|
||||
if(haystack.length > 0){
|
||||
for(var i = 0; i < needle.length; i++){
|
||||
if(haystack.indexOf(needle[i]) === -1){
|
||||
deletedIndexes.push(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
return deletedIndexes;
|
||||
}
|
||||
|
||||
function formFieldsAllHaveIds(form_fields){
|
||||
for(var i=0; i<form_fields.length; i++){
|
||||
if(!form_fields[i].hasOwnProperty('_id') && !form_fields[i].hasOwnProperty('globalId')){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
FormSchema.pre('save', function (next) {
|
||||
var that = this;
|
||||
var _original;
|
||||
|
||||
async.series([
|
||||
function(cb) {
|
||||
that.constructor
|
||||
.findOne({_id: that._id}).exec(function (err, original) {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
} else if (!original){
|
||||
return next();
|
||||
} else {
|
||||
_original = original;
|
||||
return cb(null);
|
||||
}
|
||||
});
|
||||
},
|
||||
function(cb) {
|
||||
if(that.form_fields && that.isModified('form_fields') && formFieldsAllHaveIds(that.toObject().form_fields)){
|
||||
|
||||
var current_form = that.toObject(),
|
||||
old_form_fields = _original.toObject().form_fields,
|
||||
new_ids = _.map(_.map(current_form.form_fields, 'globalId'), function(id){ return ''+id;}),
|
||||
old_ids = _.map(_.map(old_form_fields, 'globalId'), function(id){ return ''+id;}),
|
||||
deletedIds = getDeletedIndexes(old_ids, new_ids);
|
||||
|
||||
//Check if any form_fileds were deleted
|
||||
if( deletedIds.length > 0 ){
|
||||
|
||||
var modifiedSubmissions = [];
|
||||
|
||||
async.forEachOfSeries(deletedIds,
|
||||
function (deletedIdIndex, key, cb_id) {
|
||||
|
||||
var deleted_id = old_ids[deletedIdIndex];
|
||||
//Find FormSubmissions that contain field with _id equal to 'deleted_id'
|
||||
FormSubmission.
|
||||
find({ form: that, form_fields: {$elemMatch: {globalId: deleted_id} } }).
|
||||
exec(function(err, submissions){
|
||||
if(err) {
|
||||
return cb_id(err);
|
||||
}
|
||||
|
||||
//Preserve fields that have at least one submission
|
||||
if (submissions.length) {
|
||||
//Add submissions
|
||||
modifiedSubmissions.push.apply(modifiedSubmissions, submissions);
|
||||
}
|
||||
|
||||
return cb_id(null);
|
||||
});
|
||||
},
|
||||
function (err) {
|
||||
if(err){
|
||||
console.error(err.message);
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
//Iterate through all submissions with modified form_fields
|
||||
async.forEachOfSeries(modifiedSubmissions, function (submission, key, callback) {
|
||||
|
||||
var submission_form_fields = submission.toObject().form_fields;
|
||||
var currentform_form_fields = that.toObject().form_fields;
|
||||
|
||||
//Iterate through ids of deleted fields
|
||||
for (var i = 0; i < deletedIds.length; i++) {
|
||||
var index = _.findIndex(submission_form_fields, function (field) {
|
||||
var tmp_id = field.globalId + '';
|
||||
return tmp_id === old_ids[deletedIds[i]];
|
||||
});
|
||||
|
||||
var deletedField = submission_form_fields[index];
|
||||
|
||||
//Hide field if it exists
|
||||
if (deletedField) {
|
||||
|
||||
//Delete old form_field
|
||||
submission_form_fields.splice(index, 1);
|
||||
|
||||
deletedField.deletePreserved = true;
|
||||
|
||||
//Move deleted form_field to start
|
||||
submission_form_fields.unshift(deletedField);
|
||||
currentform_form_fields.unshift(deletedField);
|
||||
}
|
||||
}
|
||||
submission.form_fields = submission_form_fields;
|
||||
that.form_fields = currentform_form_fields;
|
||||
|
||||
return callback(null);
|
||||
}, function (err) {
|
||||
return cb(err);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
return cb(null);
|
||||
}
|
||||
} else {
|
||||
return cb(null);
|
||||
}
|
||||
}
|
||||
],
|
||||
function(err){
|
||||
if(err){
|
||||
return next(err);
|
||||
}
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
FormSchema.index({created: 1});
|
||||
|
||||
mongoose.model('Form', FormSchema);
|
||||
|
||||
module.exports = mongoose.model('Form');
|
||||
|
||||
@ -9,7 +9,8 @@ var mongoose = require('mongoose'),
|
||||
_ = require('lodash'),
|
||||
Schema = mongoose.Schema,
|
||||
LogicJumpSchema = require('./logic_jump.server.model'),
|
||||
tokgen = require('../libs/tokenGenerator');
|
||||
tokgen = require('../libs/tokenGenerator'),
|
||||
constants = require('../libs/constants');
|
||||
|
||||
var FieldOptionSchema = new Schema({
|
||||
option_id: {
|
||||
@ -34,21 +35,7 @@ var RatingFieldSchema = new Schema({
|
||||
},
|
||||
shape: {
|
||||
type: String,
|
||||
enum: [
|
||||
'Heart',
|
||||
'Star',
|
||||
'thumbs-up',
|
||||
'thumbs-down',
|
||||
'Circle',
|
||||
'Square',
|
||||
'Check Circle',
|
||||
'Smile Outlined',
|
||||
'Hourglass',
|
||||
'bell',
|
||||
'Paper Plane',
|
||||
'Comment',
|
||||
'Trash'
|
||||
]
|
||||
enum: constants.ratingShapeTypes
|
||||
},
|
||||
validShapes: {
|
||||
type: [String]
|
||||
@ -62,9 +49,6 @@ function BaseFieldSchema(){
|
||||
Schema.apply(this, arguments);
|
||||
|
||||
this.add({
|
||||
globalId: {
|
||||
type: String,
|
||||
},
|
||||
isSubmission: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
@ -85,6 +69,7 @@ function BaseFieldSchema(){
|
||||
|
||||
ratingOptions: RatingFieldSchema,
|
||||
fieldOptions: [FieldOptionSchema],
|
||||
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
@ -103,31 +88,12 @@ function BaseFieldSchema(){
|
||||
},
|
||||
fieldType: {
|
||||
type: String,
|
||||
enum: [
|
||||
'textfield',
|
||||
'date',
|
||||
'email',
|
||||
'link',
|
||||
'legal',
|
||||
'url',
|
||||
'textarea',
|
||||
'statement',
|
||||
'welcome',
|
||||
'thankyou',
|
||||
'file',
|
||||
'dropdown',
|
||||
'scale',
|
||||
'rating',
|
||||
'radio',
|
||||
'checkbox',
|
||||
'hidden',
|
||||
'yes_no',
|
||||
'natural',
|
||||
'stripe',
|
||||
'number'
|
||||
]
|
||||
enum: constants.fieldTypes
|
||||
},
|
||||
fieldValue: Schema.Types.Mixed
|
||||
fieldValue: {
|
||||
type: Schema.Types.Mixed,
|
||||
default: ''
|
||||
}
|
||||
});
|
||||
|
||||
this.plugin(timeStampPlugin, {
|
||||
@ -140,7 +106,7 @@ function BaseFieldSchema(){
|
||||
this.validFieldTypes = mongoose.model('Field').schema.path('fieldType').enumValues;
|
||||
|
||||
if(this.fieldType === 'rating' && this.ratingOptions.validShapes.length === 0){
|
||||
this.ratingOptions.validShapes = mongoose.model('RatingOptions').schema.path('shape').enumValues;
|
||||
this.ratingOptions.validShapes = constants.ratingShapeTypes;
|
||||
}
|
||||
|
||||
next();
|
||||
@ -162,19 +128,14 @@ FormFieldSchema.pre('validate', function(next) {
|
||||
return(next(error));
|
||||
}
|
||||
|
||||
}else{
|
||||
} else {
|
||||
//Setting default values for ratingOptions
|
||||
if(!this.ratingOptions.steps){
|
||||
if(!this.ratingOptions.steps) {
|
||||
this.ratingOptions.steps = 10;
|
||||
}
|
||||
if(!this.ratingOptions.shape){
|
||||
this.ratingOptions.shape = 'Star';
|
||||
}
|
||||
|
||||
//Checking that the fieldValue is between 0 and ratingOptions.steps
|
||||
if(this.fieldValue+0 > this.ratingOptions.steps || this.fieldValue+0 < 0){
|
||||
this.fieldValue = 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -183,27 +144,18 @@ FormFieldSchema.pre('validate', function(next) {
|
||||
if(this.fieldOptions && this.fieldOptions.length > 0){
|
||||
error.errors.ratingOptions = new mongoose.Error.ValidatorError({path:'fieldOptions', message: 'fieldOptions are only allowed for type dropdown, checkbox or radio fields.', type: 'notvalid', value: this.ratingOptions});
|
||||
console.error(error);
|
||||
return(next(error));
|
||||
return next(error);
|
||||
}
|
||||
}
|
||||
|
||||
return next();
|
||||
});
|
||||
|
||||
//LogicJump Save
|
||||
FormFieldSchema.pre('save', function(next) {
|
||||
if(!this.globalId){
|
||||
this.globalId = tokgen();
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
//Submission fieldValue correction
|
||||
FormFieldSchema.pre('save', function(next) {
|
||||
if(this.fieldType === 'dropdown' && this.isSubmission){
|
||||
this.fieldValue = this.fieldValue.option_value;
|
||||
}
|
||||
|
||||
return next();
|
||||
});
|
||||
|
||||
|
||||
@ -6,7 +6,9 @@
|
||||
var mongoose = require('mongoose'),
|
||||
Schema = mongoose.Schema,
|
||||
timeStampPlugin = require('../libs/timestamp.server.plugin'),
|
||||
FieldSchema = require('./form_field.server.model.js');
|
||||
FieldSchema = require('./form_field.server.model'),
|
||||
helpers = require('../controllers/helpers.server.controller'),
|
||||
constants = require('../libs/constants');
|
||||
|
||||
/**
|
||||
* Form Submission Schema
|
||||
@ -55,18 +57,7 @@ FormSubmissionSchema.pre('save', function (next) {
|
||||
this.form_fields[i].fieldValue = this.form_fields[i].fieldValue.option_value;
|
||||
}
|
||||
|
||||
delete form_fields[i].validFieldTypes;
|
||||
delete form_fields[i].disabled;
|
||||
delete form_fields[i].required;
|
||||
delete form_fields[i].isSubmission;
|
||||
delete form_fields[i].title;
|
||||
delete form_fields[i].fieldOptions;
|
||||
delete form_fields[i].ratingOptions;
|
||||
delete form_fields[i].logicJump;
|
||||
delete form_fields[i].description;
|
||||
delete form_fields[i].created;
|
||||
delete form_fields[i].lastModified;
|
||||
delete form_fields[i].deletePreserved;
|
||||
helpers.removeKeysFromDict(this.form_fields[i], constants.extraneousFormFieldProps);
|
||||
}
|
||||
next();
|
||||
});
|
||||
@ -77,19 +68,7 @@ FormSubmissionSchema.path('form_fields', {
|
||||
form_fields[i].isSubmission = true;
|
||||
form_fields[i]._id = new mongoose.mongo.ObjectID();
|
||||
|
||||
delete form_fields[i].validFieldTypes;
|
||||
delete form_fields[i].disabled;
|
||||
delete form_fields[i].required;
|
||||
delete form_fields[i].isSubmission;
|
||||
delete form_fields[i].title;
|
||||
delete form_fields[i].fieldOptions;
|
||||
delete form_fields[i].ratingOptions;
|
||||
delete form_fields[i].logicJump;
|
||||
delete form_fields[i].description;
|
||||
delete form_fields[i].created;
|
||||
delete form_fields[i].lastModified;
|
||||
delete form_fields[i].deletePreserved;
|
||||
|
||||
helpers.removeKeysFromDict(form_fields[i], constants.extraneousFormFieldProps);
|
||||
}
|
||||
return form_fields;
|
||||
}
|
||||
@ -101,4 +80,6 @@ FormSubmissionSchema.plugin(timeStampPlugin, {
|
||||
useVirtual: false
|
||||
});
|
||||
|
||||
module.exports = FormSubmissionSchema;
|
||||
mongoose.model('FormSubmission', FormSubmissionSchema);
|
||||
|
||||
module.exports = mongoose.model('FormSubmission');
|
||||
@ -1,26 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const constants = require('../../libs/constants'),
|
||||
config = require('../../../config/config');
|
||||
|
||||
module.exports = exports = function lastModifiedPlugin (schema, options) {
|
||||
schema.add({
|
||||
language: {
|
||||
type: String,
|
||||
enum: constants.languageTypes,
|
||||
default: config.defaultLanguage,
|
||||
required: options.required || 'Must be a valid language'
|
||||
}
|
||||
});
|
||||
|
||||
schema.pre('save', function (next) {
|
||||
var currWord = this.language;
|
||||
|
||||
//English is the default backup language
|
||||
this.language = 'en';
|
||||
if(constants.wordToLangCode.has(currWord)){
|
||||
this.language = constants.wordToLangCode[currWord];
|
||||
}
|
||||
next();
|
||||
});
|
||||
};
|
||||
@ -9,29 +9,8 @@ var mongoose = require('mongoose'),
|
||||
config = require('../../config/config'),
|
||||
timeStampPlugin = require('../libs/timestamp.server.plugin'),
|
||||
path = require('path'),
|
||||
querystring = require('querystring');
|
||||
|
||||
/**
|
||||
* A Validation function for local strategy properties
|
||||
*/
|
||||
var validateLocalStrategyProperty = function(property) {
|
||||
var propHasLength;
|
||||
if (property) {
|
||||
propHasLength = !!property.length;
|
||||
} else {
|
||||
propHasLength = false;
|
||||
}
|
||||
|
||||
return ((this.provider !== 'local' && !this.updated) || propHasLength);
|
||||
};
|
||||
|
||||
/**
|
||||
* A Validation function for username
|
||||
*/
|
||||
var validateUsername = function(username) {
|
||||
return (username.match(/^[a-zA-Z0-9.-_]+$/) !== null);
|
||||
};
|
||||
|
||||
querystring = require('querystring'),
|
||||
constants = require('../libs/constants');
|
||||
|
||||
/**
|
||||
* User Schema
|
||||
@ -52,14 +31,14 @@ var UserSchema = new Schema({
|
||||
trim: true,
|
||||
lowercase: true,
|
||||
unique: 'Account already exists with this email',
|
||||
match: [/.+\@.+\..+/, 'Please fill a valid email address'],
|
||||
match: [constants.regex.email, 'Please fill a valid email address'],
|
||||
required: [true, 'Email is required']
|
||||
},
|
||||
username: {
|
||||
type: String,
|
||||
unique: true,
|
||||
lowercase: true,
|
||||
match: [/^[a-zA-Z0-9\-]+$/, 'Username can only contain alphanumeric characters and \'-\''],
|
||||
match: [constants.regex.username, 'Username can only contain alphanumeric characters and \'-\''],
|
||||
required: [true, 'Username is required']
|
||||
},
|
||||
passwordHash: {
|
||||
@ -73,18 +52,16 @@ var UserSchema = new Schema({
|
||||
type: String,
|
||||
default: 'local'
|
||||
},
|
||||
providerData: {},
|
||||
additionalProvidersData: {},
|
||||
roles: {
|
||||
type: [{
|
||||
type: String,
|
||||
enum: ['user', 'admin', 'superuser']
|
||||
enum: constants.userRoleTypes
|
||||
}],
|
||||
default: ['user']
|
||||
},
|
||||
language: {
|
||||
type: String,
|
||||
enum: ['en', 'fr', 'es', 'it', 'de'],
|
||||
enum: constants.languageTypes,
|
||||
default: 'en',
|
||||
},
|
||||
lastModified: {
|
||||
@ -111,10 +88,6 @@ var UserSchema = new Schema({
|
||||
}
|
||||
});
|
||||
|
||||
UserSchema.virtual('displayName').get(function () {
|
||||
return this.firstName + ' ' + this.lastName;
|
||||
});
|
||||
|
||||
UserSchema.plugin(timeStampPlugin, {
|
||||
createdPath: 'created',
|
||||
modifiedPath: 'lastModified',
|
||||
@ -135,7 +108,7 @@ UserSchema.virtual('password').get(function () {
|
||||
/**
|
||||
* Create instance method for hashing a password
|
||||
*/
|
||||
UserSchema.methods.hashPassword = function(password) {
|
||||
UserSchema.statics.hashPassword = UserSchema.methods.hashPassword = function(password) {
|
||||
var encoding = 'base64';
|
||||
var iterations = 10000;
|
||||
var keylen = 128;
|
||||
@ -192,4 +165,6 @@ UserSchema.methods.isAdmin = function() {
|
||||
return false;
|
||||
};
|
||||
|
||||
module.exports = mongoose.model('User', UserSchema);
|
||||
mongoose.model('User', UserSchema);
|
||||
|
||||
module.exports = mongoose.model('User');
|
||||
@ -31,8 +31,8 @@ module.exports = function(app) {
|
||||
}
|
||||
|
||||
app.route('/forms/:formIdFast([a-zA-Z0-9]+)')
|
||||
.post(forms.createSubmission)
|
||||
|
||||
.post(forms.createSubmission);
|
||||
|
||||
app.route('/forms')
|
||||
.get(auth.isAuthenticatedOrApiKey, forms.list)
|
||||
.post(auth.isAuthenticatedOrApiKey, forms.create);
|
||||
@ -47,6 +47,9 @@ module.exports = function(app) {
|
||||
.get(auth.isAuthenticatedOrApiKey, forms.hasAuthorization, forms.listSubmissions)
|
||||
.delete(auth.isAuthenticatedOrApiKey, forms.hasAuthorization, forms.deleteSubmissions);
|
||||
|
||||
app.route('/forms/:formId([a-zA-Z0-9]+)/visitors')
|
||||
.get(auth.isAuthenticatedOrApiKey, forms.hasAuthorization, forms.getVisitorData);
|
||||
|
||||
// Slower formId middleware
|
||||
app.param('formId', forms.formByID);
|
||||
|
||||
|
||||
@ -12,6 +12,7 @@ module.exports = function(app) {
|
||||
var users = require('../../app/controllers/users.server.controller');
|
||||
|
||||
// Setting up the users profile api
|
||||
app.route('/users/password').post(users.requiresLogin, users.changePassword);
|
||||
app.route('/users/me').get(auth.isAuthenticatedOrApiKey, users.getUser);
|
||||
app.route('/users').put(auth.isAuthenticatedOrApiKey, users.update);
|
||||
|
||||
@ -19,8 +20,7 @@ module.exports = function(app) {
|
||||
app.route('/auth/verify/:token').get(users.validateVerificationToken);
|
||||
app.route('/auth/verify').post(users.resendVerificationEmail);
|
||||
|
||||
// Setting up the users password api
|
||||
app.route('/users/password').post(users.requiresLogin, users.changePassword);
|
||||
// Setting up the password reset api
|
||||
app.route('/auth/forgot').post(users.forgot);
|
||||
app.route('/auth/reset/:token').get(users.validateResetToken);
|
||||
app.route('/auth/reset/:token').post(users.reset);
|
||||
@ -33,7 +33,4 @@ module.exports = function(app) {
|
||||
app.route('/auth/signout').get(users.signout);
|
||||
|
||||
app.route('/auth/genkey').get(users.requiresLogin, users.generateAPIKey);
|
||||
|
||||
// Finish by binding the user middleware
|
||||
app.param('userId', users.userByID);
|
||||
};
|
||||
|
||||
@ -13,38 +13,30 @@ module.exports = function (io, socket) {
|
||||
var visitorsData = {};
|
||||
|
||||
var saveVisitorData = function (data, socket, cb){
|
||||
Form.findById(data.formId, function(err, form) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
throw new Error(errorHandler.getErrorMessage(err));
|
||||
}
|
||||
Form.findByIdAndUpdate(
|
||||
data.formId,
|
||||
{
|
||||
$push: {
|
||||
'analytics.visitors': {
|
||||
socketId: data.socketId,
|
||||
referrer: data.referrer,
|
||||
timeElapsed: data.timeElapsed,
|
||||
isSubmitted: data.isSubmitted,
|
||||
language: data.language,
|
||||
ipAddr: '',
|
||||
deviceType: data.deviceType
|
||||
}
|
||||
}
|
||||
},
|
||||
function(err, form) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
throw new Error(errorHandler.getErrorMessage(err));
|
||||
}
|
||||
|
||||
var newVisitor = {
|
||||
socketId: data.socketId,
|
||||
referrer: data.referrer,
|
||||
lastActiveField: data.lastActiveField,
|
||||
timeElapsed: data.timeElapsed,
|
||||
isSubmitted: data.isSubmitted,
|
||||
language: data.language,
|
||||
ipAddr: '',
|
||||
deviceType: data.deviceType
|
||||
};
|
||||
|
||||
form.analytics.visitors.push(newVisitor);
|
||||
|
||||
|
||||
form.form_fields = form.form_fields.map(v => Object.assign({}, v, { fieldValue: null }));
|
||||
|
||||
form.save(function (formSaveErr) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
throw new Error(errorHandler.getErrorMessage(formSaveErr));
|
||||
}
|
||||
|
||||
if(cb){
|
||||
return cb();
|
||||
}
|
||||
});
|
||||
if(cb){
|
||||
return cb();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@ -55,7 +47,6 @@ module.exports = function (io, socket) {
|
||||
visitorsData[current_socket.id].socketId = current_socket.id;
|
||||
visitorsData[current_socket.id].isSaved = false;
|
||||
|
||||
|
||||
if (data.isSubmitted && !data.isSaved) {
|
||||
visitorsData[current_socket.id].isSaved = true;
|
||||
saveVisitorData(data, function() {
|
||||
|
||||
@ -7,8 +7,8 @@ require('../../server.js');
|
||||
*/
|
||||
var should = require('should'),
|
||||
mongoose = require('mongoose'),
|
||||
User = mongoose.model('User'),
|
||||
Form = mongoose.model('Form');
|
||||
User = require('../models/user.server.model.js'),
|
||||
Form = require('../models/form.server.model.js');
|
||||
|
||||
/**
|
||||
* Globals
|
||||
@ -40,8 +40,8 @@ describe('Form Model Unit Tests:', function() {
|
||||
language: 'en',
|
||||
form_fields: [
|
||||
{'fieldType':'textfield', title:'First Name', 'fieldValue': ''},
|
||||
{'fieldType':'checkbox', title:'nascar', 'fieldValue': ''},
|
||||
{'fieldType':'checkbox', title:'hockey', 'fieldValue': ''}
|
||||
{'fieldType':'legal', title:'nascar', 'fieldValue': ''},
|
||||
{'fieldType':'legal', title:'hockey', 'fieldValue': ''}
|
||||
]
|
||||
});
|
||||
done();
|
||||
|
||||
@ -6,11 +6,26 @@ var should = require('should'),
|
||||
request = require('supertest'),
|
||||
Session = require('supertest-session'),
|
||||
mongoose = require('mongoose'),
|
||||
User = mongoose.model('User'),
|
||||
Form = mongoose.model('Form'),
|
||||
User = require('../models/user.server.model.js'),
|
||||
Form = require('../models/form.server.model.js'),
|
||||
FormSubmission = require('../models/form_submission.server.model.js'),
|
||||
Field = mongoose.model('Field'),
|
||||
FormSubmission = mongoose.model('FormSubmission'),
|
||||
async = require('async');
|
||||
async = require('async'),
|
||||
_ = require('lodash');
|
||||
|
||||
function omitDeep(collection, excludeKeys) {
|
||||
|
||||
function omitFn(value) {
|
||||
|
||||
if (value && typeof value === 'object') {
|
||||
excludeKeys.forEach((key) => {
|
||||
delete value[key];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return _.cloneDeepWith(collection, omitFn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Globals
|
||||
@ -24,6 +39,18 @@ var credentials = {
|
||||
password: 'password'
|
||||
};
|
||||
|
||||
var sampleVisitorData = [{
|
||||
socketId: 'ntneooe8989eotnoeeo',
|
||||
referrer: 'http://google.com',
|
||||
timeElapsed: 89898989,
|
||||
isSubmitted: true,
|
||||
language: 'en',
|
||||
ipAddr: '192.168.1.1',
|
||||
deviceType: 'desktop',
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36',
|
||||
filledOutFields: []
|
||||
}];
|
||||
|
||||
/**
|
||||
* Form routes tests
|
||||
*/
|
||||
@ -50,8 +77,8 @@ describe('Form Routes Unit tests', function() {
|
||||
admin: user.id,
|
||||
form_fields: [
|
||||
new Field({'fieldType':'textfield', 'title':'First Name', 'fieldValue': ''}),
|
||||
new Field({'fieldType':'checkbox', 'title':'nascar', 'fieldValue': ''}),
|
||||
new Field({'fieldType':'checkbox', 'title':'hockey', 'fieldValue': ''})
|
||||
new Field({'fieldType':'legal', 'title':'nascar', 'fieldValue': ''}),
|
||||
new Field({'fieldType':'legal', 'title':'hockey', 'fieldValue': ''})
|
||||
],
|
||||
isLive: true
|
||||
};
|
||||
@ -69,7 +96,6 @@ describe('Form Routes Unit tests', function() {
|
||||
.send({form: myForm})
|
||||
.expect(401)
|
||||
.end(function(FormSaveErr, FormSaveRes) {
|
||||
console.log(FormSaveRes.text);
|
||||
// Call the assertion callback
|
||||
done(FormSaveErr);
|
||||
});
|
||||
@ -92,7 +118,7 @@ describe('Form Routes Unit tests', function() {
|
||||
FormObj.save(function(err, form) {
|
||||
if(err) return done(err);
|
||||
|
||||
userSession.get('/subdomain/' + credentials.username + '/forms/' + form._id + '/render')
|
||||
userSession.get('/forms/' + form._id + '/render')
|
||||
.expect(200)
|
||||
.end(function(err, res) {
|
||||
if(err) return done(err)
|
||||
@ -115,7 +141,7 @@ describe('Form Routes Unit tests', function() {
|
||||
FormObj.save(function(err, form) {
|
||||
if(err) return done(err);
|
||||
|
||||
userSession.get('/subdomain/' + credentials.username + '/forms/' + form._id + '/render')
|
||||
userSession.get('/forms/' + form._id + '/render')
|
||||
.expect(401, {message: 'Form is Not Public'})
|
||||
.end(function(err, res) {
|
||||
done(err);
|
||||
@ -167,7 +193,7 @@ describe('Form Routes Unit tests', function() {
|
||||
it(' > should not be able to create a Form if body is empty', function(done) {
|
||||
loginSession.post('/forms')
|
||||
.send({form: null})
|
||||
.expect(400, {"message":"Invalid Input"})
|
||||
.expect(400, {'message':'Invalid Input'})
|
||||
.end(function(FormSaveErr, FormSaveRes) {
|
||||
// Call the assertion callback
|
||||
done(FormSaveErr);
|
||||
@ -316,8 +342,8 @@ describe('Form Routes Unit tests', function() {
|
||||
admin: user.id,
|
||||
form_fields: [
|
||||
new Field({'fieldType':'textfield', 'title':'First Name', 'fieldValue': ''}),
|
||||
new Field({'fieldType':'checkbox', 'title':'nascar', 'fieldValue': ''}),
|
||||
new Field({'fieldType':'checkbox', 'title':'hockey', 'fieldValue': ''})
|
||||
new Field({'fieldType':'legal', 'title':'nascar', 'fieldValue': ''}),
|
||||
new Field({'fieldType':'legal', 'title':'hockey', 'fieldValue': ''})
|
||||
],
|
||||
isLive: true
|
||||
};
|
||||
@ -328,8 +354,8 @@ describe('Form Routes Unit tests', function() {
|
||||
admin: user.id,
|
||||
form_fields: [
|
||||
new Field({'fieldType':'textfield', 'title':'Last Name', 'fieldValue': ''}),
|
||||
new Field({'fieldType':'checkbox', 'title':'formula one', 'fieldValue': ''}),
|
||||
new Field({'fieldType':'checkbox', 'title':'football', 'fieldValue': ''})
|
||||
new Field({'fieldType':'legal', 'title':'formula one', 'fieldValue': ''}),
|
||||
new Field({'fieldType':'legal', 'title':'football', 'fieldValue': ''})
|
||||
],
|
||||
isLive: true
|
||||
};
|
||||
@ -365,6 +391,123 @@ describe('Form Routes Unit tests', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it(' > should preserve visitor data when updating a Form', function(done) {
|
||||
// Create new Form model instance
|
||||
|
||||
var formObject = {
|
||||
title: 'First Form',
|
||||
language: 'en',
|
||||
admin: user.id,
|
||||
form_fields: [
|
||||
new Field({'fieldType':'textfield', 'title':'First Name', 'fieldValue': ''}),
|
||||
new Field({'fieldType':'legal', 'title':'nascar', 'fieldValue': ''}),
|
||||
new Field({'fieldType':'legal', 'title':'hockey', 'fieldValue': ''})
|
||||
],
|
||||
isLive: true,
|
||||
analytics: {
|
||||
gaCode: '',
|
||||
visitors: sampleVisitorData
|
||||
}
|
||||
};
|
||||
|
||||
var formUpdateObject = {
|
||||
title: 'Second Form',
|
||||
language: 'en',
|
||||
admin: user.id,
|
||||
form_fields: [
|
||||
new Field({'fieldType':'textfield', 'title':'Last Name', 'fieldValue': ''}),
|
||||
new Field({'fieldType':'legal', 'title':'formula one', 'fieldValue': ''}),
|
||||
new Field({'fieldType':'legal', 'title':'football', 'fieldValue': ''})
|
||||
],
|
||||
isLive: true
|
||||
};
|
||||
|
||||
var CurrentForm = new Form(formObject);
|
||||
|
||||
// Save the Form
|
||||
CurrentForm.save(function(err, form) {
|
||||
if(err) return done(err);
|
||||
|
||||
loginSession.put('/forms/' + form.id)
|
||||
.send({ form: formUpdateObject })
|
||||
.expect(200)
|
||||
.end(function(err, res) {
|
||||
|
||||
should.not.exist(err);
|
||||
|
||||
Form.findById(form.id, function (FormFindErr, UpdatedForm){
|
||||
should.not.exist(FormFindErr);
|
||||
should.exist(UpdatedForm);
|
||||
|
||||
var updatedFormObj = UpdatedForm.toJSON();
|
||||
var oldFormObj = CurrentForm.toJSON();
|
||||
|
||||
updatedFormObj.analytics.should.deepEqual(oldFormObj.analytics);
|
||||
|
||||
done(FormFindErr);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it(' > shouldn\'t allow a user to change the id when updating a form', function(done) {
|
||||
// Create new Form model instance
|
||||
|
||||
var formObject = {
|
||||
title: 'First Form',
|
||||
language: 'en',
|
||||
admin: user.id,
|
||||
form_fields: [
|
||||
new Field({'fieldType':'textfield', 'title':'First Name', 'fieldValue': ''}),
|
||||
new Field({'fieldType':'legal', 'title':'nascar', 'fieldValue': ''}),
|
||||
new Field({'fieldType':'legal', 'title':'hockey', 'fieldValue': ''})
|
||||
],
|
||||
isLive: true
|
||||
};
|
||||
|
||||
var formUpdateObject = {
|
||||
id: mongoose.Types.ObjectId(),
|
||||
title: 'First Form',
|
||||
language: 'en',
|
||||
admin: user.id,
|
||||
form_fields: [
|
||||
new Field({'fieldType':'textfield', 'title':'Last Name', 'fieldValue': ''}),
|
||||
new Field({'fieldType':'legal', 'title':'formula one', 'fieldValue': ''}),
|
||||
new Field({'fieldType':'legal', 'title':'football', 'fieldValue': ''})
|
||||
],
|
||||
isLive: true
|
||||
};
|
||||
|
||||
var CurrentForm = new Form(formObject);
|
||||
|
||||
// Save the Form
|
||||
CurrentForm.save(function(err, InitialForm) {
|
||||
if(err) return done(err);
|
||||
|
||||
loginSession.put('/forms/' + InitialForm.id)
|
||||
.send({ form: formUpdateObject })
|
||||
.expect(200)
|
||||
.end(function(err, OldForm) {
|
||||
should.not.exist(err);
|
||||
|
||||
Form.findById(InitialForm.id, function (FormFindErr, UpdatedForm){
|
||||
should.not.exist(FormFindErr);
|
||||
should.exist(UpdatedForm);
|
||||
|
||||
var updatedFormObj = UpdatedForm.toJSON();
|
||||
var oldFormObj = InitialForm.toJSON();
|
||||
|
||||
updatedFormObj = omitDeep('lastModified');
|
||||
oldFormObj = omitDeep('lastModified');
|
||||
|
||||
updatedFormObj.should.deepEqual(oldFormObj);
|
||||
|
||||
done(FormFindErr);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
afterEach('should be able to signout user', function(done){
|
||||
authenticatedSession.get('/auth/signout')
|
||||
.expect(200)
|
||||
|
||||
@ -11,13 +11,12 @@ var should = require('should'),
|
||||
_ = require('lodash'),
|
||||
async = require('async'),
|
||||
config = require('../../config/config'),
|
||||
FormSubmission = mongoose.model('FormSubmission');
|
||||
FormSubmission = require('../models/form_submission.server.model.js');
|
||||
|
||||
var exampleDemo = {
|
||||
address: '880-9650 Velit. St.',
|
||||
city: '',
|
||||
dateOfBirth: '10',
|
||||
displayName: 'Test User',
|
||||
email: 'polydaic@gmail.com',
|
||||
firstName: 'Test User',
|
||||
hin: '',
|
||||
@ -82,9 +81,8 @@ describe('FormSubmission Model Unit Tests:', function() {
|
||||
user = new User({
|
||||
firstName: 'Full',
|
||||
lastName: 'Name',
|
||||
displayName: 'Full Name',
|
||||
email: 'test1@test.com'+Date.now(),
|
||||
username: 'test1'+Date.now(),
|
||||
email: 'test1@test.com',
|
||||
username: 'test1',
|
||||
password: 'password',
|
||||
provider: 'local'
|
||||
});
|
||||
@ -168,7 +166,7 @@ describe('FormSubmission Model Unit Tests:', function() {
|
||||
});
|
||||
|
||||
it('should be able to find FormSubmission by $elemMatch on form_fields id', function(done){
|
||||
FormSubmission.findOne({ form: myForm._id, form_fields: {$elemMatch: {globalId: myForm.form_fields[0].globalId} } })
|
||||
FormSubmission.findOne({ form: myForm.id, form_fields: {$elemMatch: {_id: myForm.form_fields[0]._id} } })
|
||||
.exec(function(err, submission){
|
||||
should.not.exist(err);
|
||||
should.exist(submission);
|
||||
@ -178,76 +176,6 @@ describe('FormSubmission Model Unit Tests:', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test FormField and Submission Logic', function() {
|
||||
|
||||
beforeEach(function(done){
|
||||
|
||||
//Create Submission
|
||||
mySubmission = new FormSubmission({
|
||||
form_fields: _.merge(sampleSubmission, myForm.form_fields),
|
||||
admin: user,
|
||||
form: myForm,
|
||||
timeElapsed: 17.55
|
||||
});
|
||||
|
||||
mySubmission.save(function(err){
|
||||
should.not.exist(err);
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should preserve deleted form_fields that have submissions without any problems', function(done) {
|
||||
|
||||
var fieldPropertiesToOmit = ['deletePreserved', 'globalId', 'lastModified', 'created', '_id', 'submissionId', 'isSubmission', 'validFieldTypes', 'title'];
|
||||
var old_fields = myForm.toObject().form_fields;
|
||||
var new_form_fields = _.clone(myForm.toObject().form_fields);
|
||||
new_form_fields.splice(0, 1);
|
||||
|
||||
myForm.form_fields = new_form_fields;
|
||||
|
||||
myForm.save(function(err, _form) {
|
||||
|
||||
should.not.exist(err);
|
||||
should.exist(_form.form_fields);
|
||||
|
||||
var actual_fields = _.deepOmit(_form.toObject().form_fields, fieldPropertiesToOmit);
|
||||
old_fields = _.deepOmit(old_fields, fieldPropertiesToOmit);
|
||||
|
||||
should.deepEqual(actual_fields, old_fields, 'old form_fields not equal to newly saved form_fields');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should delete \'preserved\' form_fields whose submissions have been removed without any problems', function(done) {
|
||||
|
||||
var old_fields = myForm.toObject().form_fields;
|
||||
old_fields.splice(0,1);
|
||||
var new_form_fields = _.clone(myForm.toObject().form_fields);
|
||||
new_form_fields.splice(0, 1);
|
||||
|
||||
myForm.form_fields = new_form_fields;
|
||||
|
||||
myForm.save(function(err, _form){
|
||||
should.not.exist(err);
|
||||
should.exist(_form.form_fields);
|
||||
should.exist(old_fields);
|
||||
|
||||
var actual_fields = _.deepOmit(_form.toObject().form_fields, ['lastModified', 'created', '_id']);
|
||||
old_fields = _.deepOmit(old_fields, ['lastModified', 'created', '_id']);
|
||||
|
||||
should.deepEqual(JSON.stringify(actual_fields), JSON.stringify(old_fields)); //'old form_fields not equal to newly saved form_fields');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function(done){
|
||||
mySubmission.remove(function(){
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function(done) {
|
||||
Form.remove().exec(function() {
|
||||
User.remove().exec(function() {
|
||||
|
||||
@ -21,15 +21,14 @@ var credentials, user;
|
||||
* Form routes tests
|
||||
*/
|
||||
describe('Form Submission Routes Unit tests', function() {
|
||||
var FormObj, _Submission, submissionSession, _SubmissionBody
|
||||
|
||||
var FormObj, _Submission, submissionSession, _SubmissionBody;
|
||||
|
||||
beforeEach(function(done) {
|
||||
|
||||
// Create user credentials
|
||||
credentials = {
|
||||
email: 'test@test.com',
|
||||
username: 'test',
|
||||
email: 'test423@test.com',
|
||||
username: 'test534',
|
||||
password: 'password'
|
||||
};
|
||||
|
||||
@ -45,16 +44,34 @@ describe('Form Submission Routes Unit tests', function() {
|
||||
|
||||
// Save a user to the test db and create new Form
|
||||
user.save(function(err) {
|
||||
if(err) return done(err);
|
||||
if(err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
FormObj = new Form({
|
||||
title: 'Form Title',
|
||||
language: 'en',
|
||||
admin: user._id,
|
||||
form_fields: [
|
||||
new Field({'fieldType':'textfield', 'title':'First Name', 'fieldValue': ''}),
|
||||
new Field({'fieldType':'checkbox', 'title':'nascar', 'fieldValue': ''}),
|
||||
new Field({'fieldType':'checkbox', 'title':'hockey', 'fieldValue': ''})
|
||||
]
|
||||
new Field({'fieldType':'legal', 'title':'nascar', 'fieldValue': ''}),
|
||||
new Field({'fieldType':'legal', 'title':'hockey', 'fieldValue': ''})
|
||||
],
|
||||
selfNotifications: {
|
||||
fromField: mongoose.Types.ObjectId(),
|
||||
toEmails: 'john@smith.com',
|
||||
subject: 'Hello there',
|
||||
htmlTemplate: '<p> A form was submitted </p>',
|
||||
enabled: true
|
||||
},
|
||||
|
||||
respondentNotifications: {
|
||||
toField: mongoose.Types.ObjectId(),
|
||||
fromEmails: 'john@smith.com',
|
||||
subject: 'Tellform: Thank you for filling out this TellForm',
|
||||
htmlTemplate:'Hello, <br><br> We’ve received your submission. <br><br> Thank you & have a nice day!',
|
||||
enabled: true
|
||||
}
|
||||
});
|
||||
|
||||
FormObj.save(function(formSaveErr, form) {
|
||||
@ -64,8 +81,8 @@ describe('Form Submission Routes Unit tests', function() {
|
||||
form: form._id,
|
||||
form_fields: [
|
||||
{'fieldType':'textfield', 'title':'First Name', 'fieldValue': 'David', _id: '', isSubmission: false, deletePreserved: false},
|
||||
{'fieldType':'checkbox', 'title':'nascar', 'fieldValue': true, _id: '', isSubmission: false, deletePreserved: true},
|
||||
{'fieldType':'checkbox', 'title':'hockey', 'fieldValue': false, _id: '', isSubmission: false, deletePreserved: false}
|
||||
{'fieldType':'legal', 'title':'nascar', 'fieldValue': true, _id: '', isSubmission: false, deletePreserved: true},
|
||||
{'fieldType':'legal', 'title':'hockey', 'fieldValue': false, _id: '', isSubmission: false, deletePreserved: false}
|
||||
],
|
||||
percentageComplete: 100,
|
||||
timeElapsed: 11.55,
|
||||
@ -84,8 +101,8 @@ describe('Form Submission Routes Unit tests', function() {
|
||||
_id: form._id,
|
||||
form_fields: [
|
||||
{'fieldType':'textfield', 'title':'First Name', 'fieldValue': 'David', _id: '', isSubmission: false, deletePreserved: false},
|
||||
{'fieldType':'checkbox', 'title':'nascar', 'fieldValue': true, _id: '', isSubmission: false, deletePreserved: true},
|
||||
{'fieldType':'checkbox', 'title':'hockey', 'fieldValue': false, _id: '', isSubmission: false, deletePreserved: false}
|
||||
{'fieldType':'legal', 'title':'nascar', 'fieldValue': true, _id: '', isSubmission: false, deletePreserved: true},
|
||||
{'fieldType':'legal', 'title':'hockey', 'fieldValue': false, _id: '', isSubmission: false, deletePreserved: false}
|
||||
],
|
||||
percentageComplete: 100,
|
||||
timeElapsed: 11.55,
|
||||
@ -237,6 +254,4 @@ describe('Form Submission Routes Unit tests', function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
82
app/tests/libs/send-email-notifications.test.js
Normal file
82
app/tests/libs/send-email-notifications.test.js
Normal file
@ -0,0 +1,82 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
const should = require('should'),
|
||||
emailNotifications = require('../../libs/send-email-notifications'),
|
||||
mockTransport = require('nodemailer').createTransport({
|
||||
jsonTransport: true
|
||||
}),
|
||||
config = require('../../../config/config');
|
||||
|
||||
/**
|
||||
* Globals
|
||||
*/
|
||||
const validFormFields = [
|
||||
{fieldType:'textfield', title:'First Name', fieldValue: 'John Smith', deletePreserved: false, _id:'56340745f59a6fc9e22028e9'},
|
||||
{fieldType:'link', title:'Your Website', fieldValue: 'https://johnsmith.me', deletePreserved: false, _id:'5c9e22028e907634f45f59a6'},
|
||||
{fieldType:'number', title:'Your Age', fieldValue: 45, deletePreserved: false, _id:'56e90745f5934fc9e22028a6'}
|
||||
];
|
||||
|
||||
const validFieldDict = {
|
||||
'56340745f59a6fc9e22028e9': 'John Smith',
|
||||
'5c9e22028e907634f45f59a6': 'https://johnsmith.me',
|
||||
'56e90745f5934fc9e22028a6': '45'
|
||||
};
|
||||
|
||||
const invalidFormFields = [
|
||||
{fieldType:'textfield', title:'First Name', fieldValue: 'John Smith', deletePreserved: false},
|
||||
{fieldType:'link', title:'Your Website', deletePreserved: false, _id:'5c9e22028e907634f45f59a6'},
|
||||
{fieldType:'number', title:'Your Age'}
|
||||
];
|
||||
|
||||
const htmlTemplate = '<p><span class="placeholder-tag" data-id="56340745f59a6fc9e22028e9">First Name</span>'+
|
||||
'<br><span class="placeholder-tag" data-id="5c9e22028e907634f45f59a6">Your Website</span>'+
|
||||
'<br><span class="placeholder-tag" data-id="56e90745f5934fc9e22028a6">Your Age</span></p>';
|
||||
|
||||
const renderedTemplate = '<!DOCTYPE html><html><head></head><body><p>John Smith<br>https://johnsmith.me<br>45</p></body></html>';
|
||||
|
||||
/**
|
||||
* Unit tests
|
||||
*/
|
||||
describe('Send Email Notification Unit Tests', function() {
|
||||
|
||||
describe('Method createFieldDict', function() {
|
||||
it('should be return a fieldDict from valid form fields', function() {
|
||||
var actualFieldDict = emailNotifications.createFieldDict(validFormFields);
|
||||
actualFieldDict.should.deepEqual(validFieldDict);
|
||||
});
|
||||
|
||||
it('should return empty object if form fields are invalid or empty ', function() {
|
||||
var actualFieldDict = emailNotifications.createFieldDict(invalidFormFields);
|
||||
actualFieldDict.should.be.empty();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Method parseTemplate', function(){
|
||||
it('should properly render a template given a valid field dict', function() {
|
||||
var actualRenderedTemplate = emailNotifications.parseTemplate(htmlTemplate, validFieldDict, false).replace((/ |\r\n|\n|\r|\t/gm),'');
|
||||
actualRenderedTemplate.should.equal(renderedTemplate.replace((/ |\r\n|\n|\r|\t/gm),''));
|
||||
});
|
||||
});
|
||||
|
||||
describe('Method send', function() {
|
||||
this.timeout(10000);
|
||||
const emailSettings = {
|
||||
fromEmails: 'somewhere@somewhere.com',
|
||||
toEmails: 'there@there.com',
|
||||
subject: 'Hello <span class="placeholder-tag" data-id="56340745f59a6fc9e22028e9">First Name</span>!',
|
||||
htmlTemplate: htmlTemplate
|
||||
};
|
||||
|
||||
const emailTemplateVars = validFieldDict;
|
||||
|
||||
it('should properly replace a template var in a valid template', function(done) {
|
||||
emailNotifications.send(emailSettings, emailTemplateVars, mockTransport, function(err){
|
||||
should.not.exist(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -1,70 +1,72 @@
|
||||
'use strict';
|
||||
|
||||
// Dependencies
|
||||
var util = require('util')
|
||||
, assert = require('assert')
|
||||
, mongoose = require('mongoose')
|
||||
, timestamp = require('../../libs/timestamp.server.plugin')
|
||||
, Schema = mongoose.Schema
|
||||
, ObjectId = Schema.ObjectId
|
||||
var util = require('util'),
|
||||
assert = require('assert'),
|
||||
mongoose = require('mongoose'),
|
||||
timestamp = require('../../libs/timestamp.server.plugin'),
|
||||
Schema = mongoose.Schema,
|
||||
ObjectId = Schema.ObjectId;
|
||||
|
||||
// Run tests
|
||||
describe('Timestamp', function () {
|
||||
describe('#default()', function () {
|
||||
var FooSchema = new Schema()
|
||||
FooSchema.plugin(timestamp)
|
||||
var FooModel = mongoose.model('timeFoo', FooSchema)
|
||||
, bar = new FooModel()
|
||||
var FooSchema = new Schema();
|
||||
FooSchema.plugin(timestamp);
|
||||
var FooModel = mongoose.model('timeFoo', FooSchema),
|
||||
bar = new FooModel();
|
||||
|
||||
before(function () {
|
||||
FooModel.remove(function (err) {
|
||||
assert.strictEqual(err, null)
|
||||
})
|
||||
})
|
||||
assert.strictEqual(err, null);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have custom properties', function (done) {
|
||||
assert.strictEqual(typeof FooSchema.virtuals.created, 'object')
|
||||
assert.strictEqual(typeof FooSchema.paths.modified, 'object')
|
||||
done()
|
||||
})
|
||||
assert.strictEqual(typeof FooSchema.virtuals.created, 'object');
|
||||
assert.strictEqual(typeof FooSchema.paths.modified, 'object');
|
||||
done();
|
||||
});
|
||||
|
||||
it('should create the default attributes', function (done) {
|
||||
bar.save(function (err, doc) {
|
||||
assert.strictEqual(err, null)
|
||||
assert.strictEqual(util.isDate(doc.created), true)
|
||||
assert.strictEqual(util.isDate(doc.modified), true)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
assert.strictEqual(err, null);
|
||||
assert.strictEqual(util.isDate(doc.created), true);
|
||||
assert.strictEqual(util.isDate(doc.modified), true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#custom()', function () {
|
||||
var FooSchema = new Schema()
|
||||
var FooSchema = new Schema();
|
||||
FooSchema.plugin(timestamp, {
|
||||
createdPath: 'oh'
|
||||
, modifiedPath: 'hai'
|
||||
, useVirtual: false
|
||||
})
|
||||
var BarModel = mongoose.model('timeBar', FooSchema)
|
||||
, bar = new BarModel()
|
||||
createdPath: 'oh',
|
||||
modifiedPath: 'hai',
|
||||
useVirtual: false
|
||||
});
|
||||
var BarModel = mongoose.model('timeBar', FooSchema),
|
||||
bar = new BarModel();
|
||||
|
||||
before(function () {
|
||||
BarModel.remove(function (err) {
|
||||
assert.strictEqual(err, null)
|
||||
})
|
||||
})
|
||||
assert.strictEqual(err, null);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have custom properties', function (done) {
|
||||
assert.strictEqual(typeof FooSchema.paths.oh, 'object')
|
||||
assert.strictEqual(typeof FooSchema.paths.hai, 'object')
|
||||
done()
|
||||
})
|
||||
assert.strictEqual(typeof FooSchema.paths.oh, 'object');
|
||||
assert.strictEqual(typeof FooSchema.paths.hai, 'object');
|
||||
done();
|
||||
});
|
||||
|
||||
it('should create custom attributes', function (done) {
|
||||
bar.save(function (err, doc) {
|
||||
assert.strictEqual(err, null)
|
||||
assert.strictEqual(util.isDate(doc.oh), true)
|
||||
assert.strictEqual(util.isDate(doc.hai), true)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
assert.strictEqual(err, null);
|
||||
assert.strictEqual(util.isDate(doc.oh), true);
|
||||
assert.strictEqual(util.isDate(doc.hai), true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -5,8 +5,8 @@
|
||||
*/
|
||||
var should = require('should'),
|
||||
mongoose = require('mongoose'),
|
||||
User = mongoose.model('User');
|
||||
|
||||
User = require('../models/user.server.model.js');
|
||||
|
||||
/**
|
||||
* Globals
|
||||
*/
|
||||
|
||||
@ -4,26 +4,25 @@ var should = require('should'),
|
||||
app = require('../../server'),
|
||||
Session = require('supertest-session'),
|
||||
mongoose = require('mongoose'),
|
||||
User = mongoose.model('User'),
|
||||
User = require('../models/user.server.model.js'),
|
||||
config = require('../../config/config'),
|
||||
tmpUser = mongoose.model(config.tempUserCollection);
|
||||
tmpUser = mongoose.model(config.tempUserCollection),
|
||||
async = require('async');
|
||||
|
||||
/**
|
||||
* Globals
|
||||
*/
|
||||
var credentials, _User, activateToken, userSession;
|
||||
var credentials, _User, userSession;
|
||||
|
||||
/**
|
||||
* Form routes tests
|
||||
*/
|
||||
describe('User CRUD tests', function() {
|
||||
this.timeout(30000);
|
||||
|
||||
beforeEach(function() {
|
||||
before(function() {
|
||||
// Create user credentials
|
||||
credentials = {
|
||||
email: 'test732@test.com',
|
||||
username: 'test732',
|
||||
email: 'test099@test.com',
|
||||
username: 'test099',
|
||||
password: 'password3223'
|
||||
};
|
||||
|
||||
@ -31,77 +30,424 @@ describe('User CRUD tests', function() {
|
||||
_User = {
|
||||
email: credentials.email,
|
||||
username: credentials.username,
|
||||
password: credentials.password
|
||||
password: credentials.password,
|
||||
firstName: 'John',
|
||||
lastName: 'Smith'
|
||||
};
|
||||
|
||||
//Initialize Session
|
||||
userSession = Session(app);
|
||||
});
|
||||
|
||||
it(' > Create, Verify and Activate a User > ', function() {
|
||||
|
||||
it('should be able to create a temporary (non-activated) User', function(done) {
|
||||
userSession.post('/auth/signup')
|
||||
.send(_User)
|
||||
.expect(200)
|
||||
.end(function(FormSaveErr) {
|
||||
// Handle error
|
||||
should.not.exist(FormSaveErr);
|
||||
|
||||
tmpUser.findOne({username: _User.username}, function (err, user) {
|
||||
should.not.exist(err);
|
||||
describe(' > Create, Verify and Activate a User > ', function() {
|
||||
this.timeout(10000);
|
||||
it('should be able to create and activate a User', function(done) {
|
||||
async.waterfall([
|
||||
function(callback) {
|
||||
userSession.post('/auth/signup')
|
||||
.send(_User)
|
||||
.expect(200)
|
||||
.end(function(err) {
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
function(callback) {
|
||||
tmpUser.findOne({username: _User.username})
|
||||
.lean()
|
||||
.exec(function (err, user) {
|
||||
should.exist(user);
|
||||
|
||||
_User.username.should.equal(user.username);
|
||||
_User.firstName.should.equal(user.firstName);
|
||||
_User.lastName.should.equal(user.lastName);
|
||||
activateToken = user.GENERATED_VERIFYING_URL;
|
||||
|
||||
userSession.get('/auth/verify/'+activateToken)
|
||||
.expect(200)
|
||||
.end(function(VerifyErr, VerifyRes) {
|
||||
// Handle error
|
||||
if (VerifyErr) {
|
||||
return done(VerifyErr);
|
||||
}
|
||||
|
||||
(VerifyRes.text).should.equal('User successfully verified');
|
||||
|
||||
userSession.post('/auth/signin')
|
||||
.send(credentials)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.end(function(signinErr, signinRes) {
|
||||
// Handle signin error
|
||||
if (signinErr) {
|
||||
return done(signinErr);
|
||||
}
|
||||
|
||||
var user = signinRes.body;
|
||||
(user.username).should.equal(credentials.username);
|
||||
|
||||
userSession.get('/auth/signout')
|
||||
.expect(200)
|
||||
.end(function(signoutErr, signoutRes) {
|
||||
|
||||
// Handle signout error
|
||||
if (signoutErr) {
|
||||
return done(signoutErr);
|
||||
}
|
||||
|
||||
(signoutRes.text).should.equal('You have successfully logged out.');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
callback(err, user.GENERATED_VERIFYING_URL);
|
||||
});
|
||||
});
|
||||
},
|
||||
function(activateToken, callback) {
|
||||
userSession.get('/auth/verify/' + activateToken)
|
||||
.expect(200)
|
||||
.end(function(err, res) {
|
||||
(res.text).should.equal('User successfully verified');
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
function(callback) {
|
||||
userSession.post('/auth/signin')
|
||||
.send(credentials)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.end(function(err, res) {
|
||||
(res.body.username).should.equal(credentials.username);
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
function(callback) {
|
||||
userSession.get('/auth/signout')
|
||||
.expect(200)
|
||||
.end(function(err, res) {
|
||||
(res.text).should.equal('You have successfully logged out.');
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
function(callback) {
|
||||
User.findOne({ username: _User.username })
|
||||
.lean()
|
||||
.exec(function(err, user){
|
||||
should.exist(user);
|
||||
callback(err);
|
||||
});
|
||||
}
|
||||
], function (err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
after(function(done){
|
||||
User.remove().exec(done);
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function(done) {
|
||||
describe(' > Reset Password > ', function(){
|
||||
this.timeout(10000);
|
||||
beforeEach(function(done){
|
||||
var UserObj = new User(_User);
|
||||
UserObj.save(function(err){
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to reset password of a created User with a valid passwordResetToken', function(done) {
|
||||
var changedPassword = 'password1234';
|
||||
var resetPasswordToken;
|
||||
|
||||
async.waterfall([
|
||||
function(callback) {
|
||||
userSession.post('/auth/forgot')
|
||||
.send({ username: _User.username })
|
||||
.expect(200)
|
||||
.end(function(err) {
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
function(callback) {
|
||||
User.findOne({ username: _User.username })
|
||||
.lean()
|
||||
.exec(function(err, user){
|
||||
if(err){
|
||||
callback(err);
|
||||
}
|
||||
callback(null, user.resetPasswordToken);
|
||||
});
|
||||
},
|
||||
function(resetPasswordToken, callback) {
|
||||
userSession.get('/auth/reset/' + resetPasswordToken)
|
||||
.expect(302)
|
||||
.end(function(err) {
|
||||
callback(err, resetPasswordToken);
|
||||
});
|
||||
},
|
||||
function(resetPasswordToken, callback) {
|
||||
userSession.post('/auth/reset/' + resetPasswordToken)
|
||||
.send({
|
||||
newPassword: changedPassword,
|
||||
verifyPassword: changedPassword
|
||||
})
|
||||
.expect(200)
|
||||
.end(function(err, res) {
|
||||
callback(err, resetPasswordToken);
|
||||
});
|
||||
},
|
||||
function(resetPasswordToken, callback) {
|
||||
User.findOne({ username: _User.username })
|
||||
.exec(function(err, user){
|
||||
should.exist(user);
|
||||
user.authenticate(changedPassword).should.be.true();
|
||||
should.not.exist(user.resetPasswordToken);
|
||||
|
||||
callback(err);
|
||||
});
|
||||
}
|
||||
], function (err, result) {
|
||||
credentials.password = changedPassword;
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should be not able to reset password of a created User with a invalid passwordResetToken', function(done) {
|
||||
var changedPassword = 'password4321';
|
||||
var resetPasswordToken = 'thisIsNotAValidToken';
|
||||
|
||||
async.waterfall([
|
||||
function(callback) {
|
||||
userSession.post('/auth/forgot')
|
||||
.send({ username: credentials.username })
|
||||
.expect(200)
|
||||
.end(function(err, res) {
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
function(callback) {
|
||||
userSession.get('/auth/reset/' + resetPasswordToken)
|
||||
.expect(400)
|
||||
.end(function(err) {
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
function(callback) {
|
||||
userSession.post('/auth/reset/' + resetPasswordToken)
|
||||
.send({
|
||||
newPassword: changedPassword,
|
||||
verifyPassword: changedPassword
|
||||
})
|
||||
.expect(400)
|
||||
.end(function(err, res) {
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
function(callback) {
|
||||
User.findOne({ username: _User.username })
|
||||
.exec(function(err, user){
|
||||
should.exist(user);
|
||||
user.authenticate(changedPassword).should.be.false();
|
||||
callback(err);
|
||||
});
|
||||
}
|
||||
], function (err, result) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function(done){
|
||||
User.remove({ username: credentials.username }).exec(done);
|
||||
});
|
||||
});
|
||||
|
||||
describe(' > User Profile Changes > ', function(){
|
||||
var profileSession = new Session(app);
|
||||
|
||||
this.timeout(10000);
|
||||
beforeEach(function(done){
|
||||
var UserObj = new User(_User);
|
||||
UserObj.save(function(err, user){
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to change password when logged in', function(done) {
|
||||
var changedPassword = 'aVeryBadPassword';
|
||||
|
||||
async.waterfall([
|
||||
function(callback) {
|
||||
userSession.post('/auth/signin')
|
||||
.send({
|
||||
username: _User.username,
|
||||
password: _User.password
|
||||
})
|
||||
.expect(200)
|
||||
.end(function(err, res) {
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
function(callback) {
|
||||
userSession.post('/users/password')
|
||||
.send({
|
||||
currentPassword: _User.password,
|
||||
newPassword: changedPassword,
|
||||
verifyPassword: changedPassword
|
||||
})
|
||||
.expect(200)
|
||||
.end(function(err, res) {
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
function(callback) {
|
||||
User.findOne({ username: _User.username })
|
||||
.exec(function(err, user){
|
||||
user.authenticate(changedPassword).should.be.true();
|
||||
callback(err);
|
||||
});
|
||||
}
|
||||
], function (err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to update user when logged in', function(done) {
|
||||
var newUser = {};
|
||||
newUser.firstName = 'goodnight';
|
||||
newUser.lastName = 'everyone';
|
||||
|
||||
newUser.email = 'grcg@gcrc.com';
|
||||
newUser.username = 'grcg';
|
||||
|
||||
async.waterfall([
|
||||
function(callback) {
|
||||
userSession.post('/auth/signin')
|
||||
.send({
|
||||
username: _User.username,
|
||||
password: _User.password
|
||||
})
|
||||
.expect(200)
|
||||
.end(function(err, res) {
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
function(callback) {
|
||||
userSession.put('/users')
|
||||
.send(newUser)
|
||||
.expect(200)
|
||||
.end(function(err, res) {
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
function(callback) {
|
||||
User.findOne({ username: newUser.username })
|
||||
.exec(function(err, user){
|
||||
user.firstName.should.equal(newUser.firstName);
|
||||
user.lastName.should.equal(newUser.lastName);
|
||||
user.email.should.equal(newUser.email);
|
||||
user.username.should.equal(newUser.username);
|
||||
callback(err);
|
||||
});
|
||||
}
|
||||
], function (err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to fetch user when logged in', function(done) {
|
||||
async.waterfall([
|
||||
function(callback) {
|
||||
userSession.post('/auth/signin')
|
||||
.send({
|
||||
username: _User.username,
|
||||
password: _User.password
|
||||
})
|
||||
.expect(200)
|
||||
.end(function(err, res) {
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
function(callback) {
|
||||
userSession.get('/users/me')
|
||||
.expect(200)
|
||||
.end(function(err, res) {
|
||||
var user = res.body;
|
||||
user.firstName.should.equal(_User.firstName);
|
||||
user.lastName.should.equal(_User.lastName);
|
||||
user.email.should.equal(_User.email);
|
||||
user.username.should.equal(_User.username);
|
||||
callback(err);
|
||||
});
|
||||
}
|
||||
], function (err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function(done){
|
||||
userSession.get('/auth/signout')
|
||||
.end(function(err, res) {
|
||||
User.remove().exec(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe(' > User API > ', function(){
|
||||
var apiKey;
|
||||
|
||||
this.timeout(10000);
|
||||
before(function(done){
|
||||
var UserObj = new User(_User);
|
||||
UserObj.save(function(err, user){
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to request API Key', function(done) {
|
||||
async.waterfall([
|
||||
function(callback) {
|
||||
userSession.post('/auth/signin')
|
||||
.send({
|
||||
username: _User.username,
|
||||
password: _User.password
|
||||
})
|
||||
.expect(200)
|
||||
.end(function(err, res) {
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
function(callback) {
|
||||
userSession.get('/auth/genkey')
|
||||
.expect(200)
|
||||
.end(function(err, res) {
|
||||
apiKey = res.body.apiKey;
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
function(callback) {
|
||||
userSession.get('/auth/signout')
|
||||
.expect(200)
|
||||
.end(function(err, res) {
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
function(callback) {
|
||||
userSession.get('/users/me?apikey=' + apiKey)
|
||||
.expect(200)
|
||||
.end(function(err, res) {
|
||||
var user = res.body;
|
||||
|
||||
user.firstName.should.equal(_User.firstName);
|
||||
user.lastName.should.equal(_User.lastName);
|
||||
user.email.should.equal(_User.email);
|
||||
user.username.should.equal(_User.username);
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
], function (err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to update user with API key', function(done) {
|
||||
var newUser = {};
|
||||
newUser.firstName = 'goodnight';
|
||||
newUser.lastName = 'everyone';
|
||||
|
||||
newUser.email = 'grcg@gcrc.com';
|
||||
newUser.username = 'grcg';
|
||||
|
||||
async.waterfall([
|
||||
function(callback) {
|
||||
userSession.put('/users?apikey=' + apiKey)
|
||||
.send(newUser)
|
||||
.expect(200)
|
||||
.end(function(err, res) {
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
function(callback) {
|
||||
User.findOne({ username: newUser.username })
|
||||
.exec(function(err, user){
|
||||
user.firstName.should.equal(newUser.firstName);
|
||||
user.lastName.should.equal(newUser.lastName);
|
||||
user.email.should.equal(newUser.email);
|
||||
user.username.should.equal(newUser.username);
|
||||
callback(err);
|
||||
});
|
||||
}
|
||||
], function (err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
after(function(done){
|
||||
User.remove().exec(done);
|
||||
});
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
User.remove().exec(function () {
|
||||
tmpUser.remove().exec(function(){
|
||||
userSession.destroy();
|
||||
|
||||
@ -2,24 +2,24 @@ doctype html
|
||||
html(lang='en', xmlns='http://www.w3.org/1999/xhtml')
|
||||
head
|
||||
title=title
|
||||
// General META
|
||||
// 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
|
||||
// Semantic META
|
||||
meta(name='keywords', content='keywords')
|
||||
meta(name='description', content='description')
|
||||
// Facebook META
|
||||
// Facebook META
|
||||
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
|
||||
// Twitter META
|
||||
meta(name='twitter:title', content=title)
|
||||
meta(name='twitter:description', content='description')
|
||||
meta(name='twitter:url', content='url')
|
||||
@ -32,20 +32,20 @@ html(lang='en', xmlns='http://www.w3.org/1999/xhtml')
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 9999;
|
||||
background: url('/static/dist/page-loader.gif') 50% 35% no-repeat rgb(249,249,249);
|
||||
background: url('/static/modules/core/img/loaders/page-loader.gif') 50% 35% no-repeat rgb(249,249,249);
|
||||
background-size: 50px 50px;
|
||||
}
|
||||
// Fav Icon
|
||||
// Fav Icon
|
||||
link(href='/static/modules/core/img/brand/favicon.ico', rel='shortcut icon', type='image/x-icon')
|
||||
|
||||
body(ng-cloak='')
|
||||
.loader
|
||||
section.content
|
||||
section(ui-view='')
|
||||
//Embedding The User Object signupDisabled, socketPort and socketUrl Boolean
|
||||
//Embedding The User Object signupDisabled, socketPort and socketUrl Boolean
|
||||
script(type='text/javascript').
|
||||
var signupDisabled = !{signupDisabled};
|
||||
var socketPort = false;
|
||||
var socketPort = false;
|
||||
var socketUrl = false;
|
||||
var subdomainsDisabled = !{subdomainsDisabled};
|
||||
|
||||
@ -81,12 +81,11 @@ html(lang='en', xmlns='http://www.w3.org/1999/xhtml')
|
||||
|
||||
//Socket.io Client Dependency
|
||||
script(src='/static/lib/socket.io-client/dist/socket.io.min.js')
|
||||
script(src='/static/lib/jquery-ui/jquery-ui.js', type='text/javascript')
|
||||
|
||||
//Minified Bower Dependencies
|
||||
script(src='/static/lib/angular/angular.min.js')
|
||||
script(src='/static/dist/form-vendor.min.js')
|
||||
script(src='/static/lib/angular-ui-date/src/date.js', type='text/javascript')
|
||||
//Bower JS dependencies
|
||||
each bowerJSFile in bowerFormJSFiles
|
||||
script(type='text/javascript', src=bowerJSFile)
|
||||
// end Bower JS dependencies
|
||||
|
||||
//Application JavaScript Files
|
||||
each jsFile in formJSFiles
|
||||
@ -94,14 +93,14 @@ html(lang='en', xmlns='http://www.w3.org/1999/xhtml')
|
||||
// end Application Javascript dependencies
|
||||
|
||||
if process.env.NODE_ENV === 'development'
|
||||
//Livereload script rendered
|
||||
//Livereload script rendered
|
||||
script(async='', type='text/javascript', src='http://#{request.hostname}:35729/livereload.js')
|
||||
|
||||
script Raven.config('https://825fefd6b4ed4a4da199c1b832ca845c@sentry.tellform.com/2').install();
|
||||
|
||||
//script Raven.config('https://825fefd6b4ed4a4da199c1b832ca845c@sentry.tellform.com/2').install();
|
||||
|
||||
if google_analytics_id
|
||||
script window.ga=function(){ga.q.push(arguments)};ga.q=[];ga.l=+new Date;ga('create','{{google_analytics_id}}','auto');ga('send','pageview')
|
||||
|
||||
|
||||
script(src='https://www.google-analytics.com/analytics.js', async='')
|
||||
|
||||
script(type="text/javascript").
|
||||
|
||||
@ -3,7 +3,9 @@ extends layout.server.view.pug
|
||||
block content
|
||||
section.content(ui-view='', ng-cloak='')
|
||||
|
||||
script(src='/static/lib/file-saver.js/FileSaver.js', type='text/javascript')
|
||||
link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/quill/1.3.4/quill.snow.min.css')
|
||||
link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/quill/1.3.4/quill.bubble.min.css')
|
||||
link(rel='stylesheet', href='/static/lib/jquery-ui/themes/flick/jquery-ui.min.css')
|
||||
|
||||
//Embedding The User Object
|
||||
script(type='text/javascript').
|
||||
@ -12,7 +14,7 @@ block content
|
||||
//Embedding The signupDisabled Boolean
|
||||
script(type='text/javascript').
|
||||
var signupDisabled = !{signupDisabled};
|
||||
var socketPort = false;
|
||||
var socketPort = false;
|
||||
var socketUrl = false;
|
||||
var subdomainsDisabled = !{subdomainsDisabled};
|
||||
var locale = "en";
|
||||
@ -42,6 +44,10 @@ block content
|
||||
|
||||
script(type='text/javascript', src='https://cdnjs.cloudflare.com/ajax/libs/angular-strap/2.3.8/angular-strap.min.js')
|
||||
|
||||
script(src='https://cdnjs.cloudflare.com/ajax/libs/quill/1.3.4/quill.min.js')
|
||||
script(src='https://cdnjs.cloudflare.com/ajax/libs/ng-quill/3.5.2/ng-quill.js')
|
||||
script(src='https://unpkg.com/quill-placeholder-module@0.2.0/dist/placeholder-module.js')
|
||||
|
||||
//Application JavaScript Files
|
||||
each jsFile in jsFiles
|
||||
script(type='text/javascript', src=jsFile)
|
||||
@ -49,7 +55,7 @@ block content
|
||||
|
||||
if process.env.NODE_ENV === 'development'
|
||||
script(type='text/javascript', src='http://#{request.hostname}:35729/livereload.js')
|
||||
|
||||
|
||||
script(src='https://cdn.ravenjs.com/2.3.0/angular/raven.min.js')
|
||||
|
||||
script Raven.config('https://825fefd6b4ed4a4da199c1b832ca845c@sentry.tellform.com/2').install();
|
||||
|
||||
script Raven.config('https://825fefd6b4ed4a4da199c1b832ca845c@sentry.tellform.com/2').install();
|
||||
|
||||
@ -31,8 +31,8 @@ html(lang='en', xmlns='http://www.w3.org/1999/xhtml')
|
||||
|
||||
// Fav Icon
|
||||
link(href='/static/modules/core/img/brand/favicon.ico', rel='shortcut icon', type='image/x-icon')
|
||||
link(rel='stylesheet', href='https://maxcdn.bootstrapcdn.com/font-awesome/4.6.1/css/font-awesome.min.css')
|
||||
link(rel='stylesheet', href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css', integrity='sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u', crossorigin='anonymous')
|
||||
link(rel='stylesheet', href='/static/lib/font-awesome/css/font-awesome.min.css')
|
||||
link(rel='stylesheet', href='/static/lib/bootstrap/dist/css/bootstrap.min.css')
|
||||
link(rel='stylesheet', href='https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,900')
|
||||
|
||||
//Bower CSS dependencies
|
||||
@ -40,7 +40,6 @@ html(lang='en', xmlns='http://www.w3.org/1999/xhtml')
|
||||
link(rel='stylesheet', href=bowerCssFile)
|
||||
link(rel='stylesheet', href='/static/lib/angular-input-stars/angular-input-stars.css')
|
||||
link(rel='stylesheet', href='/static/lib/jquery-ui/themes/flick/jquery-ui.css')
|
||||
link(rel='stylesheet', href='/static/modules/core/css/github-fork-ribbon.css')
|
||||
// end Bower CSS dependencies
|
||||
|
||||
//Application CSS Files
|
||||
|
||||
34
bower.json
34
bower.json
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "tellform",
|
||||
"description": "Opensource alternative to TypeForm",
|
||||
"version": "2.0.0",
|
||||
"version": "2.2.0",
|
||||
"homepage": "https://github.com/tellform/tellform",
|
||||
"authors": [
|
||||
"David Baldwynn <polydaic@gmail.com> (http://baldwynn.me)"
|
||||
@ -11,30 +11,25 @@
|
||||
"appPath": "public/modules",
|
||||
"dependencies": {
|
||||
"bootstrap": "^3.3.7",
|
||||
"angular-resource": "~1.4.7",
|
||||
"angular-resource": "~1.7.8",
|
||||
"angular-cache-buster": "~0.4.3",
|
||||
"angular-mocks": "~1.4.7",
|
||||
"angular-bootstrap": "~0.14.3",
|
||||
"angular-ui-utils": "~3.0.0",
|
||||
"angular-ui-router": "~0.2.11",
|
||||
"ng-file-upload": "^12.0.4",
|
||||
"angular-raven": "~0.5.11",
|
||||
"angular-ui-date": "~0.0.11",
|
||||
"lodash": "~3.10.0",
|
||||
"angular-ui-sortable": "~0.13.4",
|
||||
"angular-permission": "~1.1.1",
|
||||
"file-saver.js": "~1.20150507.2",
|
||||
"angular-bootstrap-colorpicker": "~3.0.19",
|
||||
"angular-ui-router-tabs": "~1.7.0",
|
||||
"angular-scroll": "^1.0.0",
|
||||
"angular-sanitize": "1.4.14",
|
||||
"angular-sanitize": "^1.7.8",
|
||||
"v-button": "^1.1.1",
|
||||
"angular-input-stars": "https://github.com/tellform/angular-input-stars.git#master",
|
||||
"angular-input-stars": "^1.8.0",
|
||||
"raven-js": "^3.0.4",
|
||||
"tableExport.jquery.plugin": "^1.5.1",
|
||||
"js-yaml": "^3.6.1",
|
||||
"angular-ui-select": "https://github.com/tellform/ui-select.git#compiled",
|
||||
"angular-translate": "~2.11.0",
|
||||
"angular-translate": "^2.18.1",
|
||||
"ng-translate": "*",
|
||||
"deep-diff": "^0.3.4",
|
||||
"jsep": "0.3.1",
|
||||
@ -42,17 +37,14 @@
|
||||
"mobile-detect": "^1.3.3",
|
||||
"socket.io-client": "^1.7.2",
|
||||
"css-toggle-switch": "^4.0.2",
|
||||
"angular-strap": "^2.3.12"
|
||||
},
|
||||
"resolutions": {
|
||||
"angular-bootstrap": "^0.14.0",
|
||||
"angular": "1.4.14",
|
||||
"angular-ui-select": "compiled",
|
||||
"jspdf": "~1.0.178",
|
||||
"angular-sanitize": "1.4.14",
|
||||
"angular-ui-sortable": "^0.17.1",
|
||||
"angular-ui-date": "~0.0.11",
|
||||
"angular-input-stars-directive": "master"
|
||||
"angular-strap": "^2.3.12",
|
||||
"angular-ui-select": "^0.19.8",
|
||||
"angular-bootstrap-switch": "^0.5.2",
|
||||
"jquery": "^3.2.1",
|
||||
"ng-quill": "^4.5.0",
|
||||
"angular-ui-router": "^1.0.11",
|
||||
"angular-permission": "^5.3.2",
|
||||
"angular-mocks": "^1.7.8"
|
||||
},
|
||||
"overrides": {
|
||||
"BOWER-PACKAGE": {
|
||||
|
||||
@ -62,6 +62,12 @@ module.exports.removeRootDir = function(files, removeRoot, addRoot) {
|
||||
/**
|
||||
* Get the app's bower dependencies
|
||||
*/
|
||||
module.exports.getBowerFormJSAssets = function() {
|
||||
if(process.env.NODE_ENV === 'production'){
|
||||
return ['/static/lib/angular/angular.min.js', '/static/dist/vendor.min.js', '/static/lib/angular-ui-date/src/date.js'];
|
||||
}
|
||||
return this.removeRootDir(minBowerFiles('**/**.js'), 'public/', 'static/');
|
||||
};
|
||||
module.exports.getBowerJSAssets = function() {
|
||||
return this.removeRootDir(minBowerFiles('**/**.js'), 'public/', 'static/');
|
||||
};
|
||||
|
||||
20
config/env/all.js
vendored
20
config/env/all.js
vendored
@ -8,20 +8,15 @@ module.exports = {
|
||||
keywords: process.env.APP_KEYWORDS || 'typeform, pdfs, forms, opensource, formbuilder, google forms, nodejs'
|
||||
},
|
||||
db: {
|
||||
uri: process.env.MONGOLAB_URI || process.env.MONGODB_URI || 'mongodb://'+ (process.env.DB_PORT_27017_TCP_ADDR || '127.0.0.1') + '/mean',
|
||||
options: {
|
||||
user: '',
|
||||
pass: ''
|
||||
}
|
||||
uri: process.env.MONGOLAB_URI || process.env.MONGODB_URI || 'mongodb://'+ (process.env.DB_PORT_27017_TCP_ADDR || '127.0.0.1') + '/mean'
|
||||
},
|
||||
|
||||
|
||||
admin:{
|
||||
admin: {
|
||||
email: process.env.ADMIN_EMAIL || 'admin@admin.com',
|
||||
username: process.env.ADMIN_USERNAME || 'root',
|
||||
password: process.env.ADMIN_PASSWORD || 'root',
|
||||
roles: ['user', 'admin']
|
||||
},
|
||||
|
||||
|
||||
redisUrl: process.env.REDIS_URL || 'redis://127.0.0.1:6379',
|
||||
|
||||
port: process.env.PORT || 3000,
|
||||
@ -103,18 +98,18 @@ module.exports = {
|
||||
'public/config.js',
|
||||
'public/application.js',
|
||||
'public/dist/populate_template_cache.js',
|
||||
'public/dist/form_populate_template_cache.js',
|
||||
'public/modules/*/*.js',
|
||||
'public/modules/*/*/*.js',
|
||||
'public/modules/*/*/*/*.js',
|
||||
'public/modules/*/*/*/*/*.js',
|
||||
'!public/modules/*/tests/**/*.js',
|
||||
'public/form_modules/forms/*.js',
|
||||
'public/form_modules/forms/directives/*.js',
|
||||
'public/form_modules/forms/base/config/*.js',
|
||||
'public/form_modules/forms/base/config/*/*.js',
|
||||
'public/form_modules/forms/base/**/*.js',
|
||||
'public/form_modules/forms/base/*/*.js',
|
||||
'!public/modules/*/tests/**/*.js',
|
||||
'!public/modules/*/tests/*.js'
|
||||
],
|
||||
form_js: [
|
||||
'public/form-config.js',
|
||||
@ -123,8 +118,7 @@ module.exports = {
|
||||
'public/form_modules/forms/*.js',
|
||||
'public/form_modules/forms/*/*.js',
|
||||
'public/form_modules/forms/*/*/*.js',
|
||||
'public/form_modules/forms/*/*/*/*.js',
|
||||
'public/form_modules/forms/**.js',
|
||||
'public/form_modules/forms/**/*.js',
|
||||
'!public/form_modules/**/tests/**/*.js'
|
||||
],
|
||||
views: [
|
||||
|
||||
6
config/env/development.js
vendored
6
config/env/development.js
vendored
@ -4,11 +4,7 @@ module.exports = {
|
||||
baseUrl: process.env.BASE_URL || 'http://localhost:5000',
|
||||
port: process.env.PORT || 5000,
|
||||
db: {
|
||||
uri: process.env.MONGODB_URI || 'mongodb://'+( process.env.DB_PORT_27017_TCP_ADDR || '127.0.0.1') +'/mean',
|
||||
options: {
|
||||
user: '',
|
||||
pass: ''
|
||||
}
|
||||
uri: process.env.MONGODB_URI || 'mongodb://'+( process.env.DB_PORT_27017_TCP_ADDR || '127.0.0.1') +'/mean'
|
||||
},
|
||||
log: {
|
||||
// Can specify one of 'combined', 'common', 'dev', 'short', 'tiny'
|
||||
|
||||
4
config/env/production.js
vendored
4
config/env/production.js
vendored
@ -3,7 +3,7 @@
|
||||
module.exports = {
|
||||
baseUrl: process.env.BASE_URL || process.env.HEROKU_APP_NAME + '.herokuapp.com' || 'tellform.com',
|
||||
db: {
|
||||
uri: process.env.MONGODB_URI || process.env.MONGOHQ_URL || process.env.MONGOLAB_URI || 'mongodb://' + (process.env.DB_PORT_27017_TCP_ADDR || '127.0.0.1') + '/mean',
|
||||
uri: process.env.MONGODB_URI || process.env.MONGOHQ_URL || process.env.MONGOLAB_URI || 'mongodb://' + (process.env.DB_PORT_27017_TCP_ADDR || '127.0.0.1') + '/mean'
|
||||
},
|
||||
port: process.env.PORT || 5000,
|
||||
socketUrl: process.env.SOCKET_URL || 'ws.tellform.com',
|
||||
@ -30,6 +30,6 @@ module.exports = {
|
||||
assets: {
|
||||
css: ['public/dist/application.min.css'],
|
||||
js: ['public/dist/application.min.js', 'public/dist/populate_template_cache.js'],
|
||||
form_js: ['public/dist/form-application.min.js', 'public/dist/form_populate_template_cache.js', 'public/dist/form-vendor.min.js']
|
||||
form_js: ['public/dist/form-application.min.js', 'public/dist/form_populate_template_cache.js']
|
||||
}
|
||||
};
|
||||
|
||||
60
config/env/secure.js
vendored
60
config/env/secure.js
vendored
@ -1,60 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
baseUrl: 'https://forms.polydaic.com',
|
||||
port: 8443,
|
||||
db: {
|
||||
uri: process.env.MONGOHQ_URL || process.env.MONGOLAB_URI || process.env.MONGODB_URI || 'mongodb://127.0.0.1/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'
|
||||
}
|
||||
},
|
||||
|
||||
sessionCookie: {
|
||||
path: '/',
|
||||
httpOnly: false,
|
||||
// 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: true,
|
||||
// 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: 7200,
|
||||
// To set the cookie in a specific domain uncomment the following
|
||||
// setting:
|
||||
domain: process.env.BASE_URL || 'localhost:3000'
|
||||
},
|
||||
assets: {
|
||||
css: 'public/dist/application.min.css',
|
||||
js: 'public/dist/application.min.js'
|
||||
},
|
||||
mailer: {
|
||||
from: process.env.MAILER_FROM || '',
|
||||
options: process.env.MAILER_SMTP_HOST ? { //Uses custom SMTP if MAILER_SMTP_HOST is set
|
||||
host: process.env.MAILER_SMTP_HOST || '',
|
||||
port: process.env.MAILER_SMTP_PORT || 587,
|
||||
secure: (process.env.MAILER_SMTP_SECURE === 'TRUE'),
|
||||
auth: {
|
||||
user: process.env.MAILER_EMAIL_ID || '',
|
||||
pass: process.env.MAILER_PASSWORD || ''
|
||||
}
|
||||
} : {
|
||||
service: process.env.MAILER_SERVICE_PROVIDER || '',
|
||||
auth: {
|
||||
user: process.env.MAILER_EMAIL_ID || '',
|
||||
pass: process.env.MAILER_PASSWORD || ''
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
7
config/env/test.js
vendored
7
config/env/test.js
vendored
@ -3,11 +3,7 @@
|
||||
module.exports = {
|
||||
baseUrl: '127.0.0.1:3001',
|
||||
db: {
|
||||
uri: 'mongodb://localhost/mean-test',
|
||||
options: {
|
||||
user: '',
|
||||
pass: ''
|
||||
}
|
||||
uri: 'mongodb://localhost/mean-test'
|
||||
},
|
||||
port: 3001,
|
||||
log: {
|
||||
@ -19,6 +15,7 @@ module.exports = {
|
||||
//stream: 'access.log'
|
||||
}
|
||||
},
|
||||
subdomainsDisabled: true,
|
||||
app: {
|
||||
title: 'TellForm Test'
|
||||
},
|
||||
|
||||
@ -39,8 +39,9 @@ var configureSocketIO = function (app, db) {
|
||||
var supportedLanguages = ['en', 'de', 'fr', 'it', 'es'];
|
||||
|
||||
function containsAnySupportedLanguages(preferredLanguages){
|
||||
for (var i = 0; i < preferredLanguages.length; i++) {
|
||||
var currIndex = supportedLanguages.indexOf(preferredLanguages[i]);
|
||||
var i, currIndex;
|
||||
for (i = 0; i < preferredLanguages.length; i++) {
|
||||
currIndex = supportedLanguages.indexOf(preferredLanguages[i]);
|
||||
if (currIndex > -1) {
|
||||
return supportedLanguages[currIndex];
|
||||
}
|
||||
@ -75,8 +76,9 @@ module.exports = function(db) {
|
||||
|
||||
if(config.socketUrl){
|
||||
app.locals.socketUrl = config.socketUrl;
|
||||
}
|
||||
}
|
||||
|
||||
app.locals.bowerFormJSFiles = config.getBowerFormJSAssets();
|
||||
app.locals.bowerJSFiles = config.getBowerJSAssets();
|
||||
app.locals.bowerCssFiles = config.getBowerCSSAssets();
|
||||
app.locals.bowerOtherFiles = config.getBowerOtherAssets();
|
||||
@ -91,7 +93,7 @@ module.exports = function(db) {
|
||||
var User = mongoose.model('User');
|
||||
var subdomainPath = '/subdomain/';
|
||||
var subdomains = req.subdomains;
|
||||
|
||||
|
||||
if (subdomains.slice(0, 4).join('.') + '' === '1.0.0.127') {
|
||||
subdomains = subdomains.slice(4);
|
||||
}
|
||||
@ -100,7 +102,7 @@ module.exports = function(db) {
|
||||
if (!subdomains.length) {
|
||||
return next();
|
||||
}
|
||||
|
||||
|
||||
urlPath = url.parse(req.url).path.split('/');
|
||||
if (urlPath.indexOf('static') > -1) {
|
||||
urlPath.splice(1, 1);
|
||||
@ -244,7 +246,6 @@ module.exports = function(db) {
|
||||
|
||||
// Setting the app router and static folder
|
||||
app.use('/static', express.static(path.resolve('./public')));
|
||||
app.use('/uploads', express.static(path.resolve('./uploads')));
|
||||
|
||||
// CookieParser should be above session
|
||||
app.use(cookieParser());
|
||||
@ -255,7 +256,7 @@ module.exports = function(db) {
|
||||
resave: true,
|
||||
secret: config.sessionSecret,
|
||||
store: new MongoStore({
|
||||
mongooseConnection: db.connection,
|
||||
mongooseConnection: mongoose.connection,
|
||||
collection: config.sessionCollection
|
||||
}),
|
||||
cookie: config.sessionCookie,
|
||||
@ -270,6 +271,7 @@ module.exports = function(db) {
|
||||
//Visitor Language Detection
|
||||
app.use(function(req, res, next) {
|
||||
var acceptLanguage = req.headers['accept-language'];
|
||||
|
||||
var languages, supportedLanguage;
|
||||
|
||||
if(acceptLanguage){
|
||||
@ -279,13 +281,12 @@ module.exports = function(db) {
|
||||
|
||||
if(!req.user && supportedLanguage !== null){
|
||||
var currLanguage = res.cookie('userLang');
|
||||
|
||||
if(currLanguage && currLanguage !== supportedLanguage || !currLanguage){
|
||||
res.clearCookie('userLang');
|
||||
res.cookie('userLang', supportedLanguage, { maxAge: 90000, httpOnly: true });
|
||||
} else if(req.user && (!req.cookies.hasOwnProperty('userLang') || req.cookies.userLang !== req.user.language) ){
|
||||
res.cookie('userLang', req.user.language, { maxAge: 90000, httpOnly: true });
|
||||
}
|
||||
} else if(req.user && (!req.cookies.hasOwnProperty('userLang') || req.cookies['userLang'] !== req.user.language) ){
|
||||
res.cookie('userLang', req.user.language, { maxAge: 90000, httpOnly: true });
|
||||
}
|
||||
next();
|
||||
});
|
||||
@ -348,22 +349,6 @@ module.exports = function(db) {
|
||||
});
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
app = configureSocketIO(app, db);
|
||||
|
||||
// Return Express server instance
|
||||
|
||||
@ -4,18 +4,18 @@
|
||||
"404_BODY": "%s ist kein gültiger Pfad.",
|
||||
"500_BODY": "Ein unerwarteter Fehler scheint aufgetreten zu sein, warum nicht versuchen, Ihre Seite zu aktualisieren oder Sie können uns kontaktieren, wenn das Problem weiterhin besteht.",
|
||||
"EMAIL_GREETING": "Hallo da!",
|
||||
"VERIFICATION_EMAIL_PARAGRAPH_1": "Willkommen bei TellForm! Hier ist ein spezieller Link um deinen neuen Account zu aktivieren:",
|
||||
"VERIFICATION_EMAIL_PARAGRAPH_1": "Willkommen bei OhMyForm! Hier ist ein spezieller Link um deinen neuen Account zu aktivieren:",
|
||||
"VERIFICATION_EMAIL_LINK_TEXT": "Mein Konto aktivieren",
|
||||
"VERIFICATION_EMAIL_PARAGRAPH_2": "Vielen Dank für die Nutzung unserer Dienste! Wenn Sie Fragen oder Anregungen haben, senden Sie uns bitte eine E-Mail an",
|
||||
"VERIFICATION_EMAIL_SUBJECT": "Aktiviere dein neues TellForm-Konto!",
|
||||
"VERIFICATION_EMAIL_SUBJECT": "Aktiviere dein neues OhMyForm-Konto!",
|
||||
"VERIFICATION_EMAIL_TEXT": "Bitte bestätigen Sie Ihren Account, indem Sie auf den folgenden Link klicken oder ihn in Ihren Browser kopieren und einfügen: $ {URL}",
|
||||
"EMAIL_SIGNATURE": "- Das TellForm-Team",
|
||||
"EMAIL_SIGNATURE": "- Das OhMyForm-Team",
|
||||
"WELCOME_EMAIL_PARAGRAPH_1": "Wir möchten Sie als unser neustes Mitglied begrüßen!",
|
||||
"WELCOME_EMAIL_PARAGRAPH_2": "Wir wünschen Ihnen viel Spaß mit TellForm! Wenn Sie Probleme haben, senden Sie uns bitte eine E-Mail an",
|
||||
"WELCOME_EMAIL_PARAGRAPH_2": "Wir wünschen Ihnen viel Spaß mit OhMyForm! Wenn Sie Probleme haben, senden Sie uns bitte eine E-Mail an",
|
||||
"WELCOME_EMAIL_SUBJECT": "Willkommen bei %s!",
|
||||
"WELCOME_EMAIL_TEXT": "Ihr Konto wurde erfolgreich verifiziert.",
|
||||
"RESET_PASSWORD_CONFIRMATION_EMAIL_PARAGRAPH_1": "Dies ist eine Höflichkeitsnachricht, um zu bestätigen, dass Ihr Passwort gerade geändert wurde.",
|
||||
"RESET_PASSWORD_REQUEST_EMAIL_PARAGRAPH_1": "Hier ist ein spezieller Link, mit dem Sie Ihr Passwort zurücksetzen können: Bitte beachten Sie, dass es innerhalb einer Stunde zu Ihrem Schutz abläuft:",
|
||||
"RESET_PASSWORD_REQUEST_EMAIL_LINK_TEXT": "Passwort zurücksetzen",
|
||||
"RESET_PASSWORD_REQUEST_EMAIL_PARAGRAPH_2": "Falls Sie dies nicht gewünscht haben, ignorieren Sie bitte diese E-Mail und Ihr Passwort bleibt unverändert."
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,14 +4,14 @@
|
||||
"404_BODY": "%s is not a valid path",
|
||||
"500_BODY": "An unexpected error seems to have occured. Why not try refreshing your page? Or you can contact us if the problem persists.",
|
||||
"EMAIL_GREETING": "Hello there!",
|
||||
"VERIFICATION_EMAIL_PARAGRAPH_1": "Welcome to TellForm! Here is a special link to activate your new account:",
|
||||
"VERIFICATION_EMAIL_PARAGRAPH_1": "Welcome to OhMyForm! Here is a special link to activate your new account:",
|
||||
"VERIFICATION_EMAIL_LINK_TEXT": "Activate my account",
|
||||
"VERIFICATION_EMAIL_PARAGRAPH_2": "Thanks so much for using our services! If you have any questions, or suggestions, please feel free to email us here at",
|
||||
"VERIFICATION_EMAIL_SUBJECT": "Activate your new TellForm account!",
|
||||
"VERIFICATION_EMAIL_SUBJECT": "Activate your new OhMyForm account!",
|
||||
"VERIFICATION_EMAIL_TEXT": "Please verify your account by clicking the following link, or by copying and pasting it into your browser: ${URL}",
|
||||
"EMAIL_SIGNATURE": "- The TellForm team",
|
||||
"EMAIL_SIGNATURE": "- The OhMyForm team",
|
||||
"WELCOME_EMAIL_PARAGRAPH_1": "We would like to welcome you as our newest member!",
|
||||
"WELCOME_EMAIL_PARAGRAPH_2": "We hope you enjoy using TellForm! If you have any trouble please feel free to email us here at",
|
||||
"WELCOME_EMAIL_PARAGRAPH_2": "We hope you enjoy using OhMyForm! If you have any trouble please feel free to email us here at",
|
||||
"WELCOME_EMAIL_SUBJECT": "Welcome to %s!",
|
||||
"WELCOME_EMAIL_TEXT": "Your account has been successfully verified.",
|
||||
"RESET_PASSWORD_CONFIRMATION_EMAIL_PARAGRAPH_1": "This is a courtesy message to confirm that your password was just changed.",
|
||||
@ -19,4 +19,4 @@
|
||||
"RESET_PASSWORD_REQUEST_EMAIL_LINK_TEXT": "Reset Your Password",
|
||||
"RESET_PASSWORD_REQUEST_EMAIL_PARAGRAPH_2": "If you did not request this, please ignore this email and your password will remain unchanged.",
|
||||
"RESET_PASSWORD_CONFIRMATION_EMAIL_BODY_1": "RESET_PASSWORD_CONFIRMATION_EMAIL_BODY_1"
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,18 +4,18 @@
|
||||
"404_BODY": "%s no es una ruta válida",
|
||||
"500_BODY": "Parece que se produjo un error inesperado. ¿Por qué no intenta actualizar su página? O puede contactarnos si el problema persiste",
|
||||
"EMAIL_GREETING": "¡Hola!",
|
||||
"VERIFICATION_EMAIL_PARAGRAPH_1": "Bienvenido a TellForm. Aquí hay un enlace especial para activar su nueva cuenta:",
|
||||
"VERIFICATION_EMAIL_PARAGRAPH_1": "Bienvenido a OhMyForm. Aquí hay un enlace especial para activar su nueva cuenta:",
|
||||
"VERIFICATION_EMAIL_LINK_TEXT": "Activar mi cuenta",
|
||||
"VERIFICATION_EMAIL_PARAGRAPH_2": "¡Muchas gracias por utilizar nuestros servicios! Si tiene alguna pregunta o sugerencia, no dude en enviarnos un correo electrónico aquí",
|
||||
"VERIFICATION_EMAIL_SUBJECT": "¡Active su nueva cuenta TellForm!",
|
||||
"VERIFICATION_EMAIL_SUBJECT": "¡Active su nueva cuenta OhMyForm!",
|
||||
"VERIFICATION_EMAIL_TEXT": "Verifique su cuenta haciendo clic en el siguiente enlace, o copiándolo y pegándolo en su navegador: $ {URL}",
|
||||
"EMAIL_SIGNATURE": "- El equipo de TellForm",
|
||||
"EMAIL_SIGNATURE": "- El equipo de OhMyForm",
|
||||
"WELCOME_EMAIL_PARAGRAPH_1": "¡Nos gustaría darle la bienvenida como nuestro miembro más nuevo!",
|
||||
"WELCOME_EMAIL_PARAGRAPH_2": "Esperamos que disfrute utilizando TellForm. Si tiene algún problema, no dude en enviarnos un correo electrónico aquí",
|
||||
"WELCOME_EMAIL_PARAGRAPH_2": "Esperamos que disfrute utilizando OhMyForm. Si tiene algún problema, no dude en enviarnos un correo electrónico aquí",
|
||||
"WELCOME_EMAIL_SUBJECT": "¡Bienvenido a %s!",
|
||||
"WELCOME_EMAIL_TEXT": "Su cuenta ha sido verificada con éxito",
|
||||
"RESET_PASSWORD_CONFIRMATION_EMAIL_PARAGRAPH_1": "Este es un mensaje de cortesía para confirmar que su contraseña acaba de cambiarse",
|
||||
"RESET_PASSWORD_REQUEST_EMAIL_PARAGRAPH_1": "Aquí hay un enlace especial que le permitirá restablecer su contraseña. Tenga en cuenta que caducará en una hora para su protección:",
|
||||
"RESET_PASSWORD_REQUEST_EMAIL_LINK_TEXT": "Restablecer su contraseña",
|
||||
"RESET_PASSWORD_REQUEST_EMAIL_PARAGRAPH_2": "Si no lo solicitó, ignore este correo electrónico y su contraseña no cambiará".
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,14 +4,14 @@
|
||||
"404_BODY": "%s n'est pas un chemin valide.",
|
||||
"500_BODY": "Une erreur inattendue semble s'être produite, pourquoi ne pas essayer d'actualiser votre page ? Ou vous pouvez nous contacter si le problème persiste.",
|
||||
"EMAIL_GREETING": "Bonjour !",
|
||||
"VERIFICATION_EMAIL_PARAGRAPH_1": "Bienvenue sur TellForm ! Voici un lien spécial pour activer votre nouveau compte : ",
|
||||
"VERIFICATION_EMAIL_PARAGRAPH_1": "Bienvenue sur OhMyForm ! Voici un lien spécial pour activer votre nouveau compte : ",
|
||||
"VERIFICATION_EMAIL_LINK_TEXT": "Activer mon compte",
|
||||
"VERIFICATION_EMAIL_PARAGRAPH_2": "Merci infiniment d'utiliser nos services ! Si vous avez des questions ou des suggestions, n'hésitez pas à nous envoyer un courriel ici",
|
||||
"VERIFICATION_EMAIL_SUBJECT": "Activer votre nouveau compte TellForm !",
|
||||
"VERIFICATION_EMAIL_SUBJECT": "Activer votre nouveau compte OhMyForm !",
|
||||
"VERIFICATION_EMAIL_TEXT": "Merci de vérifier votre compte en cliquant sur le lien suivant, ou en le copiant dans votre navigateur web : ${URL}",
|
||||
"EMAIL_SIGNATURE": "- L'équipe TellForm",
|
||||
"EMAIL_SIGNATURE": "- L'équipe OhMyForm",
|
||||
"WELCOME_EMAIL_PARAGRAPH_1": "Nous aimerions vous accueillir en tant que nouveau membre !",
|
||||
"WELCOME_EMAIL_PARAGRAPH_2": "Nous espérons que vous apprécierez l'utilisation de TellForm ! Si vous avez des problèmes, n'hésitez pas à nous envoyer un e-mail ici",
|
||||
"WELCOME_EMAIL_PARAGRAPH_2": "Nous espérons que vous apprécierez l'utilisation de OhMyForm ! Si vous avez des problèmes, n'hésitez pas à nous envoyer un e-mail ici",
|
||||
"WELCOME_EMAIL_SUBJECT": "Bienvenue dans %s!",
|
||||
"WELCOME_EMAIL_TEXT": "Votre compte a été vérifié avec succès.",
|
||||
"RESET_PASSWORD_CONFIRMATION_EMAIL_PARAGRAPH_1": "Ceci est un message de courtoisie pour confirmer que votre mot de passe a été modifié.",
|
||||
|
||||
@ -4,18 +4,18 @@
|
||||
"404_BODY": "%s non è un percorso valido",
|
||||
"500_BODY": "Si è verificato un errore imprevisto: perché non provare a rinfrescare la tua pagina oppure puoi contattarci se il problema persiste",
|
||||
"EMAIL_GREETING": "Ciao!",
|
||||
"VERIFICATION_EMAIL_PARAGRAPH_1": "Benvenuti a TellForm! Ecco un collegamento speciale per attivare il tuo nuovo account:",
|
||||
"VERIFICATION_EMAIL_PARAGRAPH_1": "Benvenuti a OhMyForm! Ecco un collegamento speciale per attivare il tuo nuovo account:",
|
||||
"VERIFICATION_EMAIL_LINK_TEXT": "Attiva il mio account",
|
||||
"VERIFICATION_EMAIL_PARAGRAPH_2": "Grazie mille per l'utilizzo dei nostri servizi! Se hai domande o suggerimenti, non esitate a contattarci via",
|
||||
"VERIFICATION_EMAIL_SUBJECT": "Attiva il tuo nuovo account TellForm",
|
||||
"VERIFICATION_EMAIL_SUBJECT": "Attiva il tuo nuovo account OhMyForm",
|
||||
"VERIFICATION_EMAIL_TEXT": "Verifica il tuo account facendo clic sul seguente collegamento o copiandolo e incollandolo nel tuo browser: $ {URL}",
|
||||
"EMAIL_SIGNATURE": "- Il team TellForm",
|
||||
"EMAIL_SIGNATURE": "- Il team OhMyForm",
|
||||
"WELCOME_EMAIL_PARAGRAPH_1": "Vorremmo darVi il benvenuto come il nostro nuovo membro!",
|
||||
"WELCOME_EMAIL_PARAGRAPH_2": "Speriamo che ti piace usare TellForm! Se hai problemi, non esitate a contattarci via",
|
||||
"WELCOME_EMAIL_PARAGRAPH_2": "Speriamo che ti piace usare OhMyForm! Se hai problemi, non esitate a contattarci via",
|
||||
"WELCOME_EMAIL_SUBJECT": "Benvenuto a %s!",
|
||||
"WELCOME_EMAIL_TEXT": "Il tuo account è stato verificato correttamente.",
|
||||
"RESET_PASSWORD_CONFIRMATION_EMAIL_PARAGRAPH_1": "Si tratta di un messaggio di cortesia per confermare che la password è stata appena modificata".
|
||||
"RESET_PASSWORD_REQUEST_EMAIL_PARAGRAPH_1": "Ecco un collegamento speciale che ti permetterà di reimpostare la tua password. Si prega di notare che scadrà in un'ora per la protezione:",
|
||||
"RESET_PASSWORD_REQUEST_EMAIL_LINK_TEXT": "Ripristina la tua password",
|
||||
"RESET_PASSWORD_REQUEST_EMAIL_PARAGRAPH_2": "Se non l'hai richiesta, ignora questa email e la tua password rimane invariata."
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,14 +4,14 @@
|
||||
"404_BODY": "%s är inte en giltig sökväg",
|
||||
"500_BODY": "Ett oväntat fel verkar ha inträffat. Kan du prova med att uppdatera sidan? Eller kan du kontakta oss om problemet återuppstår igen?",
|
||||
"EMAIL_GREETING": "Hej där!",
|
||||
"VERIFICATION_EMAIL_PARAGRAPH_1": "Välkommen till TellForm! Här är en speciell länk till dig för att aktivera ditt nya konto:",
|
||||
"VERIFICATION_EMAIL_PARAGRAPH_1": "Välkommen till OhMyForm! Här är en speciell länk till dig för att aktivera ditt nya konto:",
|
||||
"VERIFICATION_EMAIL_LINK_TEXT": "Aktivera mitt konto",
|
||||
"VERIFICATION_EMAIL_PARAGRAPH_2": "Tack så mycket för att du använder våra tjänster! Om du har några frågor eller förslag är du varmt välkommen att e-posta oss här på",
|
||||
"VERIFICATION_EMAIL_SUBJECT": "Aktivera ditt nya TellForm-konto!",
|
||||
"VERIFICATION_EMAIL_SUBJECT": "Aktivera ditt nya OhMyForm-konto!",
|
||||
"VERIFICATION_EMAIL_TEXT": "Vänligen verifiera ditt konto genom att klicka på den följande länken, eller genom att kopiera och klistra in den i din webbläsare: ${URL}",
|
||||
"EMAIL_SIGNATURE": "- TellForm-gruppen",
|
||||
"EMAIL_SIGNATURE": "- OhMyForm-gruppen",
|
||||
"WELCOME_EMAIL_PARAGRAPH_1": "Vi skulle vilja välkomna dig som vår nyaste medlem!",
|
||||
"WELCOME_EMAIL_PARAGRAPH_2": "Vi hoppas att du gillar att använda TellForm! Om du stöter på några problem är du varmt välkommen att e-posta oss här på",
|
||||
"WELCOME_EMAIL_PARAGRAPH_2": "Vi hoppas att du gillar att använda OhMyForm! Om du stöter på några problem är du varmt välkommen att e-posta oss här på",
|
||||
"WELCOME_EMAIL_SUBJECT": "Välkommen till %s!",
|
||||
"WELCOME_EMAIL_TEXT": "Ditt konto har framgångsrikt blivit verifierat.",
|
||||
"RESET_PASSWORD_CONFIRMATION_EMAIL_PARAGRAPH_1": "Detta är ett artigt meddelande för att bekräfta att ditt lösenord just har ändrats.",
|
||||
|
||||
@ -63,7 +63,6 @@ logger.setupFileLogger = function setupFileLogger() {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
@ -76,7 +75,7 @@ logger.getLogOptions = function getLogOptions() {
|
||||
var _config = _.clone(config, true);
|
||||
var configFileLogger = _config.log.fileLogger;
|
||||
|
||||
if (!_.has(_config, 'log.fileLogger.directoryPath') || !_.has(_config, 'log.fileLogger.fileName')) {
|
||||
if (process.env.NODE_ENV !== 'test' && !_.has(_config, 'log.fileLogger.directoryPath') || !_.has(_config, 'log.fileLogger.fileName')) {
|
||||
console.log('unable to find logging file configuration');
|
||||
return false;
|
||||
}
|
||||
@ -97,7 +96,6 @@ logger.getLogOptions = function getLogOptions() {
|
||||
handleExceptions: true,
|
||||
humanReadableUnhandledException: true
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -6,14 +6,23 @@ module.exports.isAuthenticatedOrApiKey = function isAuthenticated(req, res, next
|
||||
if (req.isAuthenticated()) {
|
||||
return next();
|
||||
}
|
||||
|
||||
// Try authenticate with API KEY
|
||||
if (req.headers.apikey || req.query.apikey || req.body.apikey) {
|
||||
passport.authenticate('localapikey', function (err, user, info) {
|
||||
if (err)
|
||||
return res.sendStatus(500);
|
||||
if(!req.body.apikey && req.headers.apikey){
|
||||
req.body.apikey = req.headers.apikey;
|
||||
} else if(!req.query.apikey && req.headers.apikey){
|
||||
req.query.apikey = req.headers.apikey;
|
||||
}
|
||||
|
||||
if (!user)
|
||||
passport.authenticate('localapikey', function (err, user, info) {
|
||||
if (err) {
|
||||
return res.status(500).send('Internal Server Error with API. Sorry about that!');
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return res.status(401).send(info.message || '');
|
||||
}
|
||||
|
||||
req.login(user, function(loginErr) {
|
||||
if (loginErr) return res.sendStatus(500);
|
||||
@ -28,23 +37,3 @@ module.exports.isAuthenticatedOrApiKey = function isAuthenticated(req, res, next
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
module.exports.hasRole = function hasRole(roleRequired) {
|
||||
if (!roleRequired) {
|
||||
throw new Error('Required role needs to be set');
|
||||
}
|
||||
|
||||
return function(req, res, next) {
|
||||
return module.exports.isAuthenticated(req, res, function() {
|
||||
if (req.user && req.user.roles && req.user.roles.indexOf(roleRequired) !== -1){
|
||||
return next();
|
||||
}
|
||||
return res.sendStatus(403);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.hasAdminRole = function hasAdminRole() {
|
||||
return module.exports.hasRole('admin');
|
||||
};
|
||||
|
||||
|
||||
@ -11,13 +11,15 @@ module.exports = function() {
|
||||
return User.findOne({
|
||||
'apiKey': apiKey
|
||||
}, function(err, user) {
|
||||
if (err)
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
if (!user)
|
||||
if (!user){
|
||||
return done(null, false, {
|
||||
message: 'Unknown API Key'
|
||||
});
|
||||
}
|
||||
|
||||
return done(null, user);
|
||||
});
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 156 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 157 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 179 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 72 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 27 KiB |
@ -1,10 +0,0 @@
|
||||
#!/bin/bash
|
||||
## TODO: Reconsider this as I think that it's no longer relevant.
|
||||
line=$(head -n 1 /etc/hosts)
|
||||
echo "$line tellform.dev $(hostname)" >> /etc/hosts
|
||||
|
||||
# Restart sendmail
|
||||
service sendmail restart
|
||||
|
||||
# Run Server
|
||||
npm start
|
||||
@ -1,41 +0,0 @@
|
||||
<!-- TODO: Consider modernizing this and moving it to https://ohmyform.com's documentation -->
|
||||
# wildcard DNS in localhost development
|
||||
- install [dnsmasq](http://www.thekelleys.org.uk/dnsmasq/doc.html)
|
||||
```
|
||||
$ brew install dnsmasq
|
||||
...
|
||||
$ cp /usr/local/opt/dnsmasq/dnsmasq.conf.example /usr/local/etc/dnsmasq.conf
|
||||
```
|
||||
- edit `/usr/local/etc/dnsmasq.conf`
|
||||
```
|
||||
address=/dev/127.0.0.1
|
||||
```
|
||||
- start **dnsmasq**
|
||||
```
|
||||
$ sudo brew services start dnsmasq
|
||||
```
|
||||
- any time we change `dnsmasq.conf` we have to re-start **dnsmasq**:
|
||||
```
|
||||
$ sudo launchctl stop homebrew.mxcl.dnsmasq
|
||||
$ sudo launchctl start homebrew.mxcl.dnsmasq
|
||||
```
|
||||
- For OS X to _resolve_ requests from `*.dev` to **localhost** we need to add a _resolver_:
|
||||
```
|
||||
$ sudo mkdir /etc/resolver
|
||||
$ sudo touch /etc/resolver/dev
|
||||
```
|
||||
- edit `/etc/resolver/dev`
|
||||
```
|
||||
nameserver 127.0.0.1
|
||||
```
|
||||
- re-start the computer to enable the _resolver_
|
||||
|
||||
===
|
||||
**REFERENCES**
|
||||
|
||||
- [Using Dnsmasq for local development on OS X - Passing Curiosity](https://passingcuriosity.com/2013/dnsmasq-dev-osx/)
|
||||
- [Using Dnsmasq Configure Wildcard DNS Record on Mac | Ri Xu Online](https://xuri.me/2014/12/13/using-dnsmasq-configure-wildcard-dns-record-on-mac.html)
|
||||
- [unix - In my /etc/hosts/ file on Linux/OSX, how do I do a wildcard subdomain? - Server Fault](http://serverfault.com/questions/118378/in-my-etc-hosts-file-on-linux-osx-how-do-i-do-a-wildcard-subdomain)
|
||||
- [hostname - Wildcard in /etc/hosts file - Unix & Linux Stack Exchange](http://unix.stackexchange.com/questions/3352/wildcard-in-etc-hosts-file)
|
||||
- [Mac OS Lion - Wildcard subdomain virtual host - Stack Overflow](http://stackoverflow.com/questions/9562059/mac-os-lion-wildcard-subdomain-virtual-host)
|
||||
- [How to put wildcard entry into /etc/hosts? - Stack Overflow](http://stackoverflow.com/questions/20446930/how-to-put-wildcard-entry-into-etc-hosts)
|
||||
@ -9,14 +9,19 @@ services:
|
||||
- "./data/mongo:/data"
|
||||
tellform:
|
||||
image: ohmyform/ohmyform
|
||||
#build: .
|
||||
#volumes:
|
||||
# - ".:/opt/app"
|
||||
environment:
|
||||
CREATE_ADMIN: "TRUE"
|
||||
SOCKET_URL: 'localhost:5000'
|
||||
SOCKET_PORT: "5000"
|
||||
SOCKET_PORT_EXTERN_VISIBLE: "TRUE"
|
||||
MONGODB_URI: mongodb://mongo/tellform
|
||||
REDIS_URL: redis://redis
|
||||
MAILER_SMTP_HOST: mail
|
||||
MAILER_SMTP_PORT: 1025
|
||||
# command: grunt dev # override command to have livereloading on file change
|
||||
links:
|
||||
- mongo
|
||||
- redis
|
||||
@ -30,3 +35,13 @@ services:
|
||||
image: mailhog/mailhog
|
||||
ports:
|
||||
- "5050:8025"
|
||||
mongoexpress:
|
||||
image: mongo-express
|
||||
environment:
|
||||
ME_CONFIG_MONGODB_SERVER: mongo
|
||||
ports:
|
||||
- "5051:8081"
|
||||
links:
|
||||
- mongo
|
||||
depends_on:
|
||||
- mongo
|
||||
@ -1,35 +0,0 @@
|
||||
{
|
||||
"apps" : [{
|
||||
"name" : "tellform",
|
||||
"script" : "server.js",
|
||||
"instances": "max",
|
||||
"exec_mode": "cluster",
|
||||
"max_memory_restart" : "200M"
|
||||
}],
|
||||
"deploy" : {
|
||||
"stage": {
|
||||
"user": "polydaic",
|
||||
"host": "159.203.42.158",
|
||||
"ref": "origin/stage",
|
||||
"repo": "git@github.com:tellform/tellform.git",
|
||||
"path": "/opt/deploy",
|
||||
"post-deploy": "npm install && pm2 startOrGracefulReload ecosystem.json --env production",
|
||||
"env": {
|
||||
"NODE_ENV": "production",
|
||||
"BASE_URL": "stage.tellform.com"
|
||||
}
|
||||
},
|
||||
"prod": {
|
||||
"user": "polydaic",
|
||||
"host": "159.203.33.182",
|
||||
"ref": "origin/master",
|
||||
"repo": "git@github.com:tellform/tellform.git",
|
||||
"path": "/opt/deploy",
|
||||
"post-deploy": "npm install && pm2 startOrGracefulReload ecosystem.json --env production",
|
||||
"env": {
|
||||
"NODE_ENV": "production",
|
||||
"BASE_URL": "admin.tellform.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
85
gruntfile.js
85
gruntfile.js
@ -18,16 +18,21 @@ var bowerArray = ['public/lib/angular/angular.min.js',
|
||||
'public/lib/js-yaml/dist/js-yaml.js',
|
||||
'public/lib/angular-sanitize/angular-sanitize.min.js'];
|
||||
|
||||
const bowerFiles = require('main-bower-files');
|
||||
const bowerDep = bowerFiles('**/**.js');
|
||||
|
||||
module.exports = function(grunt) {
|
||||
require('jit-grunt')(grunt);
|
||||
|
||||
var angularTestDeps = ['public/lib/angular/angular.js', 'public/lib/angular-mocks/angular-mocks.js'];
|
||||
|
||||
// Unified Watch Object
|
||||
var watchFiles = {
|
||||
serverViews: ['app/views/**/*.pug'],
|
||||
serverJS: ['gruntfile.js', 'server.js', 'config/**/*.js', 'app/**/*.js', '!app/tests/'],
|
||||
|
||||
clientViews: ['public/modules/**/*.html', 'public/form_modules/forms/base/**/*.html', '!public/modules/forms/base/**/*.html',],
|
||||
clientJS: ['public/form_modules/**/*.js', 'public/modules/**/*.js'],
|
||||
clientJS: ['public/config.js', 'public/form-config.js', 'public/application.js', 'public/form-application.js', 'public/form_modules/**[!tests]/*.js', 'public/modules/**[!tests]/*.js'],
|
||||
clientCSS: ['public/modules/**/*.css'],
|
||||
|
||||
serverTests: ['app/tests/**/*.js'],
|
||||
@ -123,7 +128,7 @@ module.exports = function(grunt) {
|
||||
compress: true
|
||||
},
|
||||
files: {
|
||||
'public/dist/form-vendor.min.js': bowerArray
|
||||
'public/dist/vendor.min.js': bowerArray
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -138,7 +143,7 @@ module.exports = function(grunt) {
|
||||
dev: {
|
||||
script: 'server.js',
|
||||
options: {
|
||||
nodeArgs: ['--debug'],
|
||||
nodeArgs: ['--inspect'],
|
||||
ext: 'js,html',
|
||||
watch: watchFiles.serverViews.concat(watchFiles.serverJS)
|
||||
}
|
||||
@ -201,66 +206,26 @@ module.exports = function(grunt) {
|
||||
level: 'log',
|
||||
terminal: true
|
||||
},
|
||||
singleRun: true
|
||||
singleRun: false
|
||||
}
|
||||
},
|
||||
protractor: {
|
||||
options: {
|
||||
configFile: 'protractor.conf.js',
|
||||
keepAlive: true,
|
||||
noColor: false
|
||||
},
|
||||
e2e: {
|
||||
options: {
|
||||
args: {} // Target-specific arguments
|
||||
}
|
||||
}
|
||||
},
|
||||
mocha_istanbul: {
|
||||
coverage: {
|
||||
src: watchFiles.allTests, // a folder works nicely
|
||||
options: {
|
||||
mask: '*.test.js',
|
||||
require: ['server.js']
|
||||
}
|
||||
},
|
||||
coverageClient: {
|
||||
src: watchFiles.clientTests, // specifying file patterns works as well
|
||||
options: {
|
||||
coverageFolder: 'coverageClient',
|
||||
mask: '*.test.js',
|
||||
require: ['server.js']
|
||||
}
|
||||
},
|
||||
coverageServer: {
|
||||
src: watchFiles.serverTests,
|
||||
options: {
|
||||
coverageFolder: 'coverageServer',
|
||||
mask: '*.test.js',
|
||||
require: ['server.js']
|
||||
}
|
||||
},
|
||||
coveralls: {
|
||||
src: watchFiles.allTests, // multiple folders also works
|
||||
options: {
|
||||
require: ['server.js'],
|
||||
coverage: true, // this will make the grunt.event.on('coverage') event listener to be triggered
|
||||
root: './lib', // define where the cover task should consider the root of libraries that are covered by tests
|
||||
reportFormats: ['cobertura','lcovonly']
|
||||
require: ['server.js'],
|
||||
reportFormats: ['html','lcovonly']
|
||||
}
|
||||
}
|
||||
},
|
||||
istanbul_check_coverage: {
|
||||
default: {
|
||||
options: {
|
||||
coverageFolder: 'coverage*', // will check both coverage folders and merge the coverage results
|
||||
check: {
|
||||
lines: 80,
|
||||
statements: 80
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
lcovMerge: {
|
||||
options: {
|
||||
emitters: ['event'],
|
||||
},
|
||||
src: ['./coverageServer/*.info', './coverageClient/**/*.info']
|
||||
},
|
||||
html2js: {
|
||||
options: {
|
||||
base: 'public',
|
||||
@ -285,9 +250,9 @@ module.exports = function(grunt) {
|
||||
},
|
||||
main: {
|
||||
options: {
|
||||
module: 'TellForm.templates'
|
||||
module: 'app.templates'
|
||||
},
|
||||
src: ['public/modules/**/views/**.html', 'public/modules/**/views/**/*.html', 'public/form_modules/forms/base/**/*.html', '!public/modules/forms/base/**/*.html'],
|
||||
src: ['public/modules/**/views/**.html', 'public/modules/**/views/**/*.html', 'public/form_modules/forms/base/**/*.html'],
|
||||
dest: 'public/dist/populate_template_cache.js'
|
||||
}
|
||||
},
|
||||
@ -323,9 +288,7 @@ module.exports = function(grunt) {
|
||||
});
|
||||
|
||||
// Code coverage tasks.
|
||||
grunt.registerTask('coveralls', ['env:test','mocha_istanbul:coveralls']);
|
||||
grunt.registerTask('coverage', ['env:test', 'mocha_istanbul:coverage']);
|
||||
grunt.registerTask('coverage:client', ['env:test', 'mocha_istanbul:coverageClient']);
|
||||
grunt.registerTask('coveralls', ['test:client', 'karma:unit', 'mocha_istanbul:coverageServer', 'lcovMerge']);
|
||||
grunt.registerTask('coverage:server', ['env:test', 'mocha_istanbul:coverageServer']);
|
||||
|
||||
// Default task(s).
|
||||
@ -334,9 +297,9 @@ module.exports = function(grunt) {
|
||||
|
||||
// Debug task.
|
||||
grunt.registerTask('debug', ['lint', 'html2js:main', 'html2js:forms', 'concurrent:debug']);
|
||||
|
||||
|
||||
// Lint task(s).
|
||||
grunt.registerTask('lint', ['jshint', 'csslint', 'i18nlint:client', 'i18nlint:server']);
|
||||
grunt.registerTask('lint', ['jshint', 'csslint']);
|
||||
grunt.registerTask('lint:tests', ['jshint:allTests']);
|
||||
|
||||
// Build task(s).
|
||||
@ -346,9 +309,11 @@ module.exports = function(grunt) {
|
||||
grunt.registerTask('setup', ['execute']);
|
||||
|
||||
// Test task(s).
|
||||
grunt.registerTask('test', ['lint:tests', 'test:server', 'test:client']);
|
||||
grunt.registerTask('test', ['test:server', 'test:client']);
|
||||
grunt.registerTask('test:server', ['lint:tests', 'env:test', 'mochaTest']);
|
||||
grunt.registerTask('test:client', ['lint:tests', 'html2js:main', 'html2js:forms', 'env:test', 'karma:unit']);
|
||||
grunt.registerTask('test:travis', ['coverage:server', 'test:client', 'lcovMerge']);
|
||||
|
||||
|
||||
grunt.registerTask('testdebug', ['env:test', 'karma:debug']);
|
||||
};
|
||||
|
||||
@ -18,7 +18,7 @@ module.exports = function(config) {
|
||||
frameworks: ['jasmine'],
|
||||
|
||||
// List of files / patterns to load in the browser
|
||||
files: bowerDep.concat(['public/lib/socket.io-client/dist/socket.io.js', 'public/lib/mobile-detect/mobile-detect.js'], applicationConfiguration.assets.js, applicationConfiguration.assets.views, applicationConfiguration.assets.unit_tests),
|
||||
files: bowerDep.concat(['public/lib/socket.io-client/dist/socket.io.js', 'public/lib/mobile-detect/mobile-detect.js', 'public/lib/quill/quill.js', 'public/lib/ng-quill/src/ng-quill.js'], applicationConfiguration.assets.js, applicationConfiguration.assets.views, applicationConfiguration.assets.unit_tests),
|
||||
|
||||
// Test results reporter to use
|
||||
// Possible values: 'dots', 'progress', 'junit', 'growl', 'coverage'
|
||||
@ -29,11 +29,22 @@ module.exports = function(config) {
|
||||
'public/modules/**/views/**/*.html': ['ng-html2js'],
|
||||
'public/modules/**/views/*.html': ['ng-html2js'],
|
||||
'public/form_modules/forms/base/views/**/*.html': ['ng-html2js'],
|
||||
'public/form_modules/forms/base/views/*.html': ['ng-html2js']
|
||||
//'public/modules/*/*.js': ['coverage'],
|
||||
//'public/modules/*/*[!tests]*/*.js': ['coverage'],
|
||||
'public/form_modules/forms/base/views/*.html': ['ng-html2js'],
|
||||
'public/modules/*/*.js': ['coverage'],
|
||||
'public/modules/*/*[!tests]*/*.js': ['coverage'],
|
||||
'public/form_modules/*/*.js': ['coverage'],
|
||||
'public/form_modules/*/*[!tests]*/*.js': ['coverage']
|
||||
},
|
||||
|
||||
// configure coverage reporter
|
||||
coverageReporter: {
|
||||
reporters: [
|
||||
//{ type: 'html', subdir: 'report-html' },
|
||||
{ type: 'lcov' },
|
||||
],
|
||||
dir : 'coverageClient/'
|
||||
},
|
||||
|
||||
ngHtml2JsPreprocessor: {
|
||||
stripPrefix: 'public/',
|
||||
prependPrefix: 'static/',
|
||||
|
||||
731
package-lock.json
generated
731
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
15
package.json
15
package.json
@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "TellForm",
|
||||
"name": "ohmyform",
|
||||
"description": "Opensource alternative to TypeForm",
|
||||
"version": "2.1.0",
|
||||
"homepage": "https://github.com/tellform/tellform",
|
||||
"homepage": "https://github.com/ohmyform/ohmyform",
|
||||
"authors": [
|
||||
"David Baldwynn <polydaic@gmail.com> (http://baldwynn.me)"
|
||||
],
|
||||
@ -10,11 +10,7 @@
|
||||
"private": true,
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/tellform/tellform.git"
|
||||
},
|
||||
"engines": {
|
||||
"node": "6.x.x",
|
||||
"npm": "3.x.x"
|
||||
"url": "https://github.com/ohmyform/ohmyform.git"
|
||||
},
|
||||
"scripts": {
|
||||
"addcontrib": "all-contributors add",
|
||||
@ -53,11 +49,12 @@
|
||||
"helmet": "^3.16.0",
|
||||
"i18n": "^0.8.3",
|
||||
"jit-grunt": "^0.9.1",
|
||||
"lodash": "^4.17.11",
|
||||
"jsdom": "^15.1.1",
|
||||
"lodash": "^4.17.13",
|
||||
"main-bower-files": "^2.13.1",
|
||||
"method-override": "~2.3.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"mongoose": "~4.4.19",
|
||||
"mongoose": "^5.6.4",
|
||||
"morgan": "^1.9.1",
|
||||
"nodemailer": "~4.0.0",
|
||||
"passport": "~0.3.0",
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
apps:
|
||||
- script : 'server.js'
|
||||
name : 'TellForm'
|
||||
exec_mode: 'cluster'
|
||||
instances: 4
|
||||
@ -25,6 +25,9 @@ angular.module(ApplicationConfiguration.applicationModuleName).constant('USER_RO
|
||||
superuser: 'superuser'
|
||||
});
|
||||
|
||||
//users url
|
||||
angular.module(ApplicationConfiguration.applicationModuleName).constant('USERS_URL', '/users');
|
||||
|
||||
//form url
|
||||
angular.module(ApplicationConfiguration.applicationModuleName).constant('FORM_URL', '/forms/:formId');
|
||||
|
||||
|
||||
@ -2,22 +2,29 @@
|
||||
|
||||
// Init the application configuration module for AngularJS application
|
||||
var ApplicationConfiguration = (function() {
|
||||
// Init module configuration options
|
||||
var applicationModuleName = 'TellForm';
|
||||
var applicationModuleVendorDependencies = ['duScroll', 'ui.select', 'ngSanitize', 'vButton', 'ngResource', 'TellForm.templates', 'ui.router', 'ui.bootstrap', 'ui.utils', 'pascalprecht.translate', 'view-form'];
|
||||
|
||||
// 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);
|
||||
angular.module('app').requires.push(moduleName);
|
||||
};
|
||||
|
||||
return {
|
||||
applicationModuleName: applicationModuleName,
|
||||
applicationModuleVendorDependencies: applicationModuleVendorDependencies,
|
||||
applicationModuleName: 'app',
|
||||
applicationModuleVendorDependencies: [
|
||||
'duScroll',
|
||||
'ui.select',
|
||||
'ngSanitize',
|
||||
'vButton',
|
||||
'ngResource',
|
||||
'app.templates',
|
||||
'ui.router',
|
||||
'ui.bootstrap',
|
||||
'ui.utils',
|
||||
'pascalprecht.translate'
|
||||
],
|
||||
registerModule: registerModule
|
||||
};
|
||||
})();
|
||||
|
||||
@ -28,9 +28,8 @@ angular.module('view-form')
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
});
|
||||
|
||||
angular.module('view-form').value('supportedFields', [
|
||||
})
|
||||
.value('supportedFields', [
|
||||
'textfield',
|
||||
'textarea',
|
||||
'date',
|
||||
@ -44,7 +43,18 @@ angular.module('view-form').value('supportedFields', [
|
||||
'yes_no',
|
||||
'number',
|
||||
'natural'
|
||||
]);
|
||||
])
|
||||
.constant('VIEW_FORM_URL', '/forms/:formId/render')
|
||||
.filter('indexToAlphabet', function(){
|
||||
return function(index){
|
||||
var char = String.fromCharCode(index + 65);
|
||||
return char;
|
||||
};
|
||||
})
|
||||
|
||||
angular.module('view-form').constant('VIEW_FORM_URL', '/forms/:formId/render');
|
||||
//Angular-Scroll Settings
|
||||
angular.module('view-form').value('duScrollActiveClass', 'activeField')
|
||||
.value('duScrollGreedy', true)
|
||||
.value('duScrollOffset', 100)
|
||||
.value('duScrollSpyWait', 0);
|
||||
|
||||
|
||||
@ -34,6 +34,9 @@ angular.module('view-form').config(['$translateProvider', function ($translatePr
|
||||
ADD_NEW_LINE_INSTR: 'Press SHIFT+ENTER to add a newline',
|
||||
ERROR: 'Error',
|
||||
|
||||
LOADING_LABEL: 'Loading',
|
||||
WAIT_LABEL: 'Please wait',
|
||||
|
||||
FORM_404_HEADER: '404 - Form Does Not Exist',
|
||||
FORM_404_BODY: 'The form you are trying to access does not exist. Sorry about that!',
|
||||
|
||||
|
||||
@ -34,11 +34,14 @@ angular.module('view-form').config(['$translateProvider', function ($translatePr
|
||||
ADD_NEW_LINE_INSTR: 'Appuyez sur MAJ + ENTRÉE pour ajouter une nouvelle ligne',
|
||||
ERROR: 'Erreur',
|
||||
|
||||
LOADING_LABEL: 'Chargement',
|
||||
WAIT_LABEL: "Veuillez patienter",
|
||||
|
||||
FORM_404_HEADER: '404 - Le formulaire n\'existe pas',
|
||||
FORM_404_BODY: 'Le formulaire auquel vous essayez d\'accéder n\'existe pas. Désolé pour ça !',
|
||||
|
||||
FORM_UNAUTHORIZED_HEADER: 'Non autorisé à accéder au formulaire',
|
||||
FORM_UNAUTHORIZED_BODY1: 'Le formulaire auquel vous essayez d\'accéder est actuellement privé et inaccessible publiquement.',
|
||||
FORM_UNAUTHORIZED_BODY1: 'Le formulaire auquel vous essayez d\'accéder est actuellement privé et inaccessible publiquement.',
|
||||
FORM_UNAUTHORIZED_BODY2: 'Si vous êtes le propriétaire du formulaire, vous pouvez le définir en "Public" dans le panneau "Configuration" du formulaire admin.',
|
||||
});
|
||||
|
||||
|
||||
@ -33,13 +33,16 @@ angular.module('view-form').config(['$translateProvider', function ($translatePr
|
||||
OPTION_PLACEHOLDER: 'Geben oder wählen Sie eine Option aus',
|
||||
ADD_NEW_LINE_INSTR: 'Drücken Sie UMSCHALT + EINGABETASTE, um eine neue Zeile hinzuzufügen',
|
||||
ERROR: 'Fehler',
|
||||
|
||||
LOADING_LABEL: 'Laden',
|
||||
WAIT_LABEL: 'Bitte warten',
|
||||
|
||||
FORM_404_HEADER: '404 - Formular existiert nicht',
|
||||
FORM_404_BODY: 'Das Formular, auf das Sie zugreifen möchten, existiert nicht. Das tut mir leid!',
|
||||
|
||||
FORM_UNAUTHORIZED_HEADER: 'Nicht zum Zugriffsformular berechtigt\' ',
|
||||
FORM_UNAUTHORIZED_BODY1: 'Das Formular, auf das Sie zugreifen möchten, ist derzeit privat und nicht öffentlich zugänglich.',
|
||||
FORM_UNAUTHORIZED_BODY2: 'Wenn Sie der Eigentümer des Formulars sind, können Sie es im Fenster "Konfiguration" im Formular admin auf "Öffentlich" setzen.',
|
||||
FORM_UNAUTHORIZED_BODY1: 'Das Formular, auf das Sie zugreifen möchten, ist derzeit privat und nicht öffentlich zugänglich.',
|
||||
FORM_UNAUTHORIZED_BODY2: 'Wenn Sie der Eigentümer des Formulars sind, können Sie es im Fenster "Konfiguration" im Formular admin auf "Öffentlich" setzen.',
|
||||
});
|
||||
|
||||
}]);
|
||||
|
||||
@ -33,6 +33,9 @@ angular.module('view-form').config(['$translateProvider', function ($translatePr
|
||||
OPTION_PLACEHOLDER: 'Digitare o selezionare un\'opzione',
|
||||
ADD_NEW_LINE_INSTR: 'Premere SHIFT + INVIO per aggiungere una nuova riga',
|
||||
ERROR: 'Errore',
|
||||
|
||||
LOADING_LABEL: 'Caricamento',
|
||||
WAIT_LABEL: "Attendere prego",
|
||||
|
||||
FORM_404_HEADER: '404 - Il modulo non esiste',
|
||||
FORM_404_BODY: 'La forma che stai cercando di accedere non esiste. Ci dispiace!',
|
||||
|
||||
@ -34,6 +34,9 @@ angular.module('view-form').config(['$translateProvider', function ($translatePr
|
||||
ADD_NEW_LINE_INSTR: 'Presione MAYÚS + ENTRAR para agregar una nueva línea',
|
||||
ERROR: 'Error',
|
||||
|
||||
LOADING_LABEL: 'Cargando',
|
||||
WAIT_LABEL: 'Espera',
|
||||
|
||||
FORM_404_HEADER: '404 - La forma no existe',
|
||||
FORM_404_BODY: 'El formulario al que intenta acceder no existe. ¡Lo siento por eso!',
|
||||
|
||||
|
||||
@ -1,555 +0,0 @@
|
||||
.panel-default.startPage {
|
||||
border-style: dashed;
|
||||
border-color: #a9a9a9;
|
||||
border-width:3px;
|
||||
}
|
||||
|
||||
.busy-updating-wrapper {
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
right: 55px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.busy-submitting-wrapper {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.dropzone h4.panel-title {
|
||||
height: 17px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.container.admin-form {
|
||||
margin-top: 70px;
|
||||
}
|
||||
|
||||
.public-form input, .public-form textarea {
|
||||
background-color: #000000;
|
||||
background-color: rgba(0,0,0,0);
|
||||
border: 2px dashed #ddd!important;
|
||||
}
|
||||
|
||||
.public-form input:focus, .public-form textarea:focus {
|
||||
border: 2px dashed #ddd!important;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
/*.public-form input.no-border.ng-invalid, .public-form textarea.no-border {
|
||||
border-color: none;
|
||||
}*/
|
||||
.public-form input.ng-valid, .public-form textarea.ng-valid {
|
||||
border-color: #20FF20!important;
|
||||
border-style: solid!important;
|
||||
border-width: 3px!important;
|
||||
}
|
||||
|
||||
.public-form input.ng-invalid.ng-dirty, .public-form textarea.ng-invalid.ng-dirty {
|
||||
border-color: #FA787E!important;
|
||||
border-style: solid!important;
|
||||
border-width: 3px!important;
|
||||
}
|
||||
|
||||
section.content p.breakwords {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.btn {
|
||||
border: 1px solid #c6c6c6;
|
||||
}
|
||||
|
||||
.btn[type='submit'] {
|
||||
font-size: 1.5em;
|
||||
padding: 0.35em 1.2em 0.35em 1.2em;
|
||||
}
|
||||
|
||||
section.content > section > section.container {
|
||||
margin-top: 70px;
|
||||
}
|
||||
|
||||
/*
|
||||
** Modal CSS Styles
|
||||
*/
|
||||
.modal-header {
|
||||
padding: 15px;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
font-size: 18px;
|
||||
font-weight: normal;
|
||||
}
|
||||
.input-block {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
.modal-footer input[type='text'] {
|
||||
min-height: 34px;
|
||||
padding: 7px 8px;
|
||||
font-size: 13px;
|
||||
color: #333;
|
||||
vertical-align: middle;
|
||||
background-color: #fff;
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 8px center;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 3px;
|
||||
box-shadow: inset 0 1px 2px rgba(0,0,0,0.075);
|
||||
}
|
||||
.modal-body > .modal-body-alert {
|
||||
color: #796620;
|
||||
background-color: #f8eec7;
|
||||
border-color: #f2e09a;
|
||||
margin: -16px -15px 15px;
|
||||
padding: 10px 15px;
|
||||
border-style: solid;
|
||||
border-width: 1px 0;
|
||||
}
|
||||
|
||||
div.form-fields {
|
||||
position: relative;
|
||||
padding-top: 35vh;
|
||||
}
|
||||
.letter {
|
||||
position: relative;
|
||||
display: -moz-inline-stack;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
zoom: 1;
|
||||
width: 16px;
|
||||
margin-top: 1px;
|
||||
padding: 0;
|
||||
height: 17px;
|
||||
font-size: 12px;
|
||||
line-height: 19px;
|
||||
border: 1px solid #000;
|
||||
border: 1px solid rgba(0,0,0,.2);
|
||||
margin-right: 7px;
|
||||
-webkit-border-radius: 3px;
|
||||
-moz-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
div.form-submitted > .field.row {
|
||||
padding-bottom: 2%;
|
||||
margin-top: 35vh;
|
||||
}
|
||||
div.form-submitted > .field.row > div {
|
||||
font-size: 1.7em;
|
||||
}
|
||||
|
||||
/* Styles for accordion */
|
||||
form .accordion-edit {
|
||||
width: inherit;
|
||||
}
|
||||
|
||||
/*Styles for ui-datepicker*/
|
||||
.ui-datepicker.ui-widget {
|
||||
z-index: 99!important;
|
||||
}
|
||||
|
||||
form .row.field .field-number {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
/* Styles for form submission view (/forms/:formID) */
|
||||
form .row.field {
|
||||
padding: 1em 0 0 0;
|
||||
width: inherit;
|
||||
}
|
||||
form .row.field > .field-title {
|
||||
margin-top:0.5em;
|
||||
font-size:1.2em;
|
||||
padding-bottom: 1.8em;
|
||||
width: inherit;
|
||||
}
|
||||
form .row.field > .field-input {
|
||||
font-size: 1.4em;
|
||||
color: #777;
|
||||
}
|
||||
form.submission-form .row.field.statement > .field-title {
|
||||
font-size:1.7em;
|
||||
}
|
||||
form.submission-form .row.field.statement > .field-input {
|
||||
font-size:1em;
|
||||
color:#ddd;
|
||||
}
|
||||
|
||||
form.submission-form .select.radio > .field-input input, form.submission-form .select > .field-input input {
|
||||
width:20%;
|
||||
}
|
||||
|
||||
form.submission-form .field.row.radio .btn.activeBtn {
|
||||
background-color: rgb(0,0,0)!important;
|
||||
background-color: rgba(0,0,0,0.7)!important;
|
||||
color: white;
|
||||
}
|
||||
form.submission-form .field.row.radio .btn {
|
||||
margin-right:1.2em;
|
||||
}
|
||||
|
||||
form.submission-form .select > .field-input .btn {
|
||||
text-align: left;
|
||||
margin-bottom:0.7em;
|
||||
}
|
||||
form.submission-form .select > .field-input .btn > span {
|
||||
font-size: 1.10em;
|
||||
}
|
||||
|
||||
/*form.submission-form .field-input > input:focus {
|
||||
font-size:1em;
|
||||
}*/
|
||||
|
||||
form .field-input > textarea{
|
||||
padding: 0.45em 0.9em;
|
||||
width: 100%;
|
||||
line-height: 160%;
|
||||
}
|
||||
|
||||
form .field-input > input.hasDatepicker{
|
||||
padding: 0.45em 0.9em;
|
||||
width: 50%;
|
||||
line-height: 160%;
|
||||
}
|
||||
form .field-input > input.text-field-input{
|
||||
padding: 0.45em 0.9em;
|
||||
width: 100%;
|
||||
line-height: 160%;
|
||||
}
|
||||
form .required-error{
|
||||
color: #ddd;
|
||||
font-size:0.8em;
|
||||
}
|
||||
|
||||
form .row.field.dropdown > .field-input input {
|
||||
min-height: 34px;
|
||||
border-width: 0 0 2px 0;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
form .row.field.dropdown > .field-input input:focus {
|
||||
border: none;
|
||||
}
|
||||
|
||||
form .dropdown > .field-input .ui-select-choices-row-inner {
|
||||
border-radius: 3px;
|
||||
margin: 5px;
|
||||
padding: 10px;
|
||||
background-color: #000000;
|
||||
background-color: rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
form .dropdown > .field-input .ui-select-choices-row-inner.active, form .dropdown > .field-input .ui-select-choices-row-inner.active:focus {
|
||||
background-color: #000000;
|
||||
background-color: rgba(0,0,0,0.1);
|
||||
}
|
||||
.config-form {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.config-form > .row {
|
||||
padding: 19px;
|
||||
margin-bottom: 20px;
|
||||
background-color: #f5f5f5;
|
||||
border: 1px solid #e3e3e3;
|
||||
border-radius: 4px;
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
|
||||
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
|
||||
}
|
||||
|
||||
div.config-form .row.field {
|
||||
padding-top:1.5em;
|
||||
}
|
||||
|
||||
div.config-form > .row > .container:nth-of-type(odd){
|
||||
border-right: 1px #ddd solid;
|
||||
}
|
||||
div.config-form.design > .row > .container:nth-of-type(odd){
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
div.config-form .row > .field-input {
|
||||
padding-left:0.1em;
|
||||
}
|
||||
div.config-form .row > .field-input label {
|
||||
padding-left:1.3em;
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
/* Styles for form admin view (/forms/:formID/admin) */
|
||||
.admin-form > .page-header {
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
.admin-form > .page-header h1 {
|
||||
margin-bottom: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
.admin-form > .page-header > .col-xs-3 {
|
||||
padding-top: 1.4em;
|
||||
}
|
||||
.admin-form .form-controls .row {
|
||||
padding: 5px;
|
||||
}
|
||||
.admin-form .page-header {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/*Styles for admin view tabs */
|
||||
.admin-form .tab-content {
|
||||
padding-top: 3em;
|
||||
}
|
||||
|
||||
.admin-form .panel-heading {
|
||||
background-color: #f1f1f1;
|
||||
position: relative!important;
|
||||
}
|
||||
.admin-form .panel-heading:hover {
|
||||
background-color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
.admin-form .panel-heading a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.current-fields .panel-body .row.question input[type='text'], .current-fields .panel-body .row.description textarea{
|
||||
width: 100%;
|
||||
}
|
||||
.current-fields .panel-body .row.options input[type='text'] {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
/*Override Select2 UI*/
|
||||
.ui-select-choices.ui-select-dropdown {
|
||||
top:2.5em!important;
|
||||
}
|
||||
.ui-select-toggle {
|
||||
box-shadow:none!important;
|
||||
border:none!important;
|
||||
}
|
||||
|
||||
.current-fields .tool-panel > .panel-default:hover {
|
||||
border-color: #9d9d9d;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.current-fields .tool-panel > .panel-default .panel-heading {
|
||||
background-color: #fff;
|
||||
color: #9d9d9d!important;
|
||||
}
|
||||
.current-fields .tool-panel > .panel-default .panel-heading:hover {
|
||||
background-color: #eee;
|
||||
color: #000!important;
|
||||
cursor: pointer;
|
||||
}
|
||||
.current-fields .tool-panel > .panel-default .panel-heading a {
|
||||
color: inherit;
|
||||
}
|
||||
.current-fields .tool-panel > .panel-default .panel-heading a:hover{
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/*Styles for submission table*/
|
||||
.submissions-table .table-outer.row {
|
||||
margin: 1.5em 0 2em 0!important;
|
||||
}
|
||||
.submissions-table .table-outer .col-xs-12 {
|
||||
padding-left: 0!important;
|
||||
border:1px solid #ddd;
|
||||
overflow-x: scroll;
|
||||
border-radius:3px;
|
||||
}
|
||||
.submissions-table .table > thead > tr > th {
|
||||
min-width:8em;
|
||||
}
|
||||
.submissions-table .table > tbody > tr.selected {
|
||||
background-color:#efefef;
|
||||
}
|
||||
|
||||
|
||||
/*Styles for add fields tab*/
|
||||
.admin-form .add-field {
|
||||
background-color: #ddd;
|
||||
padding: 0 2% 0 2%;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.admin-form .add-field .col-xs-6 {
|
||||
padding: 0.25em 0.4em;
|
||||
}
|
||||
.admin-form .add-field .col-xs-6 .panel-heading {
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-color: #bbb;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.admin-form .oscar-field-select {
|
||||
margin: 10px 0 10px;
|
||||
}
|
||||
|
||||
.view-form-btn.span {
|
||||
padding-right:0.6em;
|
||||
}
|
||||
.status-light.status-light-off {
|
||||
color: #BE0000;
|
||||
}
|
||||
.status-light.status-light-on {
|
||||
color: #33CC00;
|
||||
}
|
||||
|
||||
/* Styles for form list view (/forms) */
|
||||
section.public-form {
|
||||
padding: 0 10% 0 10%;
|
||||
}
|
||||
section.public-form .form-submitted {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
section.public-form .btn {
|
||||
border: 1px solid;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
text-align: center;
|
||||
border-bottom: 6px inset #ccc;
|
||||
background-color: #eee;
|
||||
width: 180px;
|
||||
/*width:100%;*/
|
||||
position: relative;
|
||||
height: 215px;
|
||||
/*padding-bottom: 25%;*/
|
||||
margin-bottom: 45px;
|
||||
}
|
||||
.form-item.create-new input[type='text']{
|
||||
width: inherit;
|
||||
color:black;
|
||||
border:none;
|
||||
}
|
||||
|
||||
.form-item.create-new {
|
||||
background-color: rgb(131,131,131);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/*CREATE-NEW FORM MODAL*/
|
||||
.form-item.new-form {
|
||||
background-color: rgb(300,131,131);
|
||||
z-index: 11;
|
||||
}
|
||||
.form-item.new-form:hover {
|
||||
background-color: rgb(300,100,100);
|
||||
}
|
||||
.form-item.new-form input[type='text'] {
|
||||
margin-top:0.2em;
|
||||
width: inherit;
|
||||
color:black;
|
||||
border:none;
|
||||
padding: 0.3em 0.6em 0.3em 0.6em;
|
||||
}
|
||||
.form-item.new-form .custom-select {
|
||||
margin-top: 0.2em
|
||||
}
|
||||
.form-item.new-form .custom-select select {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
|
||||
.form-item.new-form .details-row {
|
||||
margin-top: 1em;
|
||||
}
|
||||
.form-item.new-form .details-row.submit {
|
||||
margin-top: 1.7em;
|
||||
}
|
||||
.form-item.new-form .details-row.submit .btn {
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
.form-item.new-form .title-row {
|
||||
margin-top: 1em;
|
||||
top:0;
|
||||
}
|
||||
|
||||
/*Modal overlay (for lightbox effect)*/
|
||||
.overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: rgb(0,0,0);
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
z-index: 10;
|
||||
}
|
||||
.overlay.submitform {
|
||||
background-color: rgb(256,256,256);
|
||||
background-color: rgba(256,256,256,0.8);
|
||||
}
|
||||
.field-directive {
|
||||
z-index: 9;
|
||||
padding: 10% 10% 10% 0;
|
||||
border: 25px transparent solid;
|
||||
position: relative;
|
||||
}
|
||||
.activeField {
|
||||
z-index: 11;
|
||||
position: relative;
|
||||
background-color: transparent;
|
||||
}
|
||||
.activeField.field-directive {
|
||||
display: inline-block;
|
||||
border-radius: 7px;
|
||||
width: 100%;
|
||||
border: 25px transparent solid;
|
||||
}
|
||||
.activeField input {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.form-item:hover, .form-item.create-new:hover {
|
||||
border-bottom: 8px inset #ccc;
|
||||
background-color: #d9d9d9;
|
||||
}
|
||||
|
||||
.form-item.create-new:hover {
|
||||
background-color: rgb(81,81,81);
|
||||
}
|
||||
|
||||
.form-item > .row.footer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 30%;
|
||||
}
|
||||
|
||||
.form-item .title-row{
|
||||
position: relative;
|
||||
top: 15px;
|
||||
padding-top:3em;
|
||||
padding-bottom:3.65em;
|
||||
}
|
||||
.form-item .title-row h4 {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
.form-item.create-new .title-row{
|
||||
padding: 0;
|
||||
}
|
||||
.form-item.create-new .title-row h4 {
|
||||
font-size: 7em;
|
||||
}
|
||||
|
||||
.form-item .details-row{
|
||||
margin-top: 3.2em;
|
||||
}
|
||||
.form-item .details-row small {
|
||||
font-size: 0.6em;
|
||||
}
|
||||
.form-item.create-new .details-row small {
|
||||
font-size: 0.95em;
|
||||
}
|
||||
@ -65,8 +65,6 @@ angular.module('view-form').directive('onEnterKey', ['$rootScope', function($roo
|
||||
var keyCode = event.which || event.keyCode;
|
||||
|
||||
if(keyCode === 9 && event.shiftKey) {
|
||||
|
||||
console.log('onTabAndShiftKey');
|
||||
event.preventDefault();
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.$eval($attrs.onTabAndShiftKey);
|
||||
|
||||
@ -13,31 +13,37 @@ angular.module('view-form').directive('submitFormDirective', ['$http', 'TimeCoun
|
||||
function ($http, TimeCounter, $filter, $rootScope, SendVisitorData, $translate, $timeout) {
|
||||
return {
|
||||
templateUrl: 'form_modules/forms/base/views/directiveViews/form/submit-form.client.view.html',
|
||||
restrict: 'E',
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
myform:'=',
|
||||
ispreview: '='
|
||||
myform: '='
|
||||
},
|
||||
controller: function($document, $window, $scope){
|
||||
var NOSCROLL = false;
|
||||
var FORM_ACTION_ID = 'submit_field';
|
||||
var FORM_ACTION_ID = 'submit_field';
|
||||
$scope.forms = {};
|
||||
|
||||
//Don't start timer if we are looking at a design preview
|
||||
if($scope.ispreview){
|
||||
TimeCounter.restartClock();
|
||||
|
||||
var form_fields_count = $scope.myform.visible_form_fields.filter(function(field){
|
||||
return field.fieldType !== 'statement';
|
||||
}).length;
|
||||
|
||||
$scope.$watch('myform', function(oldVal, newVal){
|
||||
$scope.myform.visible_form_fields = $scope.myform.form_fields.filter(function(field){
|
||||
return !field.deletePreserved;
|
||||
});
|
||||
});
|
||||
|
||||
$scope.updateFormValidity = function(){
|
||||
$timeout(function(){
|
||||
var nb_valid = $scope.myform.form_fields.filter(function(field){
|
||||
return (field.fieldType === 'statement' || field.fieldValue !== '' || !field.required);
|
||||
}).length;
|
||||
$scope.translateAdvancementData = {
|
||||
done: nb_valid,
|
||||
total: $scope.myform.visible_form_fields.length
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
var form_fields_count = $scope.myform.visible_form_fields.filter(function(field){
|
||||
return field.fieldType !== 'statement';
|
||||
}).length;
|
||||
|
||||
var nb_valid = $filter('formValidity')($scope.myform);
|
||||
$scope.translateAdvancementData = {
|
||||
done: nb_valid,
|
||||
total: form_fields_count,
|
||||
answers_not_completed: form_fields_count - nb_valid
|
||||
};
|
||||
$scope.updateFormValidity();
|
||||
|
||||
$scope.reloadForm = function(){
|
||||
//Reset Form
|
||||
@ -47,7 +53,7 @@ angular.module('view-form').directive('submitFormDirective', ['$http', 'TimeCoun
|
||||
return field;
|
||||
}).value();
|
||||
|
||||
$scope.loading = false;
|
||||
$scope.loading = false;
|
||||
$scope.error = '';
|
||||
|
||||
$scope.selected = {
|
||||
@ -63,240 +69,179 @@ angular.module('view-form').directive('submitFormDirective', ['$http', 'TimeCoun
|
||||
/*
|
||||
** Field Controls
|
||||
*/
|
||||
var evaluateLogicJump = function(field){
|
||||
var logicJump = field.logicJump;
|
||||
var evaluateLogicJump = function(field){
|
||||
var logicJump = field.logicJump;
|
||||
|
||||
if(logicJump.enabled){
|
||||
if (logicJump.expressionString && logicJump.valueB && field.fieldValue) {
|
||||
var parse_tree = jsep(logicJump.expressionString);
|
||||
var left, right;
|
||||
if(logicJump.enabled){
|
||||
if (logicJump.expressionString && logicJump.valueB && field.fieldValue) {
|
||||
var parse_tree = jsep(logicJump.expressionString);
|
||||
var left, right;
|
||||
|
||||
if(parse_tree.left.name === 'field'){
|
||||
left = field.fieldValue;
|
||||
right = logicJump.valueB;
|
||||
} else {
|
||||
left = logicJump.valueB;
|
||||
right = field.fieldValue;
|
||||
}
|
||||
if(parse_tree.left.name === 'field'){
|
||||
left = field.fieldValue;
|
||||
right = logicJump.valueB;
|
||||
} else {
|
||||
left = logicJump.valueB;
|
||||
right = field.fieldValue;
|
||||
}
|
||||
|
||||
if(field.fieldType === 'number' || field.fieldType === 'scale' || field.fieldType === 'rating'){
|
||||
switch(parse_tree.operator) {
|
||||
case '==':
|
||||
return (parseInt(left) === parseInt(right));
|
||||
case '!==':
|
||||
return (parseInt(left) !== parseInt(right));
|
||||
case '>':
|
||||
return (parseInt(left) > parseInt(right));
|
||||
case '>=':
|
||||
return (parseInt(left) > parseInt(right));
|
||||
case '<':
|
||||
return (parseInt(left) < parseInt(right));
|
||||
case '<=':
|
||||
return (parseInt(left) <= parseInt(right));
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
switch(parse_tree.operator) {
|
||||
case '==':
|
||||
return (left === right);
|
||||
case '!==':
|
||||
return (left !== right);
|
||||
case 'contains':
|
||||
return (left.indexOf(right) > -1);
|
||||
case '!contains':
|
||||
/* jshint -W018 */
|
||||
return !(left.indexOf(right) > -1);
|
||||
case 'begins':
|
||||
return left.startsWith(right);
|
||||
case '!begins':
|
||||
return !left.startsWith(right);
|
||||
case 'ends':
|
||||
return left.endsWith(right);
|
||||
case '!ends':
|
||||
return left.endsWith(right);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
if(field.fieldType === 'number' || field.fieldType === 'scale' || field.fieldType === 'rating'){
|
||||
switch(parse_tree.operator) {
|
||||
case '==':
|
||||
return (parseInt(left) === parseInt(right));
|
||||
case '!==':
|
||||
return (parseInt(left) !== parseInt(right));
|
||||
case '>':
|
||||
return (parseInt(left) > parseInt(right));
|
||||
case '>=':
|
||||
return (parseInt(left) > parseInt(right));
|
||||
case '<':
|
||||
return (parseInt(left) < parseInt(right));
|
||||
case '<=':
|
||||
return (parseInt(left) <= parseInt(right));
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
switch(parse_tree.operator) {
|
||||
case '==':
|
||||
return (left === right);
|
||||
case '!==':
|
||||
return (left !== right);
|
||||
case 'contains':
|
||||
return (left.indexOf(right) > -1);
|
||||
case '!contains':
|
||||
/* jshint -W018 */
|
||||
return !(left.indexOf(right) > -1);
|
||||
case 'begins':
|
||||
return left.startsWith(right);
|
||||
case '!begins':
|
||||
return !left.startsWith(right);
|
||||
case 'ends':
|
||||
return left.endsWith(right);
|
||||
case '!ends':
|
||||
return left.endsWith(right);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var getActiveField = function(){
|
||||
if($scope.selected === null){
|
||||
console.error('current active field is null');
|
||||
throw new Error('current active field is null');
|
||||
}
|
||||
$rootScope.getActiveField = function(){
|
||||
if($scope.selected === null){
|
||||
console.error('current active field is null');
|
||||
throw new Error('current active field is null');
|
||||
}
|
||||
|
||||
if($scope.selected._id === FORM_ACTION_ID) {
|
||||
return $scope.myform.form_fields.length - 1;
|
||||
}
|
||||
return $scope.selected.index;
|
||||
};
|
||||
|
||||
$scope.isActiveField = function(field){
|
||||
if($scope.selected._id === field._id) {
|
||||
return true
|
||||
}
|
||||
return false;
|
||||
};
|
||||
if($scope.selected._id === FORM_ACTION_ID) {
|
||||
return $scope.myform.visible_form_fields[$scope.selected.index - 1]._id;
|
||||
}
|
||||
return $scope.selected._id;
|
||||
};
|
||||
|
||||
$scope.setActiveField = $rootScope.setActiveField = function(field_id, field_index, animateScroll) {
|
||||
if($scope.selected === null || (!field_id && field_index === null) ) {
|
||||
return;
|
||||
if(!field_id && field_index === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!field_id){
|
||||
field_id = $scope.myform.visible_form_fields[field_index]._id;
|
||||
} else if(field_index === null){
|
||||
field_index = $scope.myform.visible_form_fields.length
|
||||
|
||||
for(var i=0; i < $scope.myform.visible_form_fields.length; i++){
|
||||
var currField = $scope.myform.visible_form_fields[i];
|
||||
if(currField['_id'] == field_id){
|
||||
field_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(field_id === FORM_ACTION_ID){
|
||||
field_index = $scope.myform.visible_form_fields.length;
|
||||
} else if(!field_id) {
|
||||
field_id = $scope.myform.visible_form_fields[field_index]._id;
|
||||
} else if(field_index === null){
|
||||
field_index = $scope.myform.visible_form_fields.length
|
||||
|
||||
if($scope.selected._id === field_id){
|
||||
return;
|
||||
}
|
||||
for(var i=0; i < $scope.myform.visible_form_fields.length; i++){
|
||||
var currField = $scope.myform.visible_form_fields[i];
|
||||
if(currField['_id'] == field_id){
|
||||
field_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!$scope.selected){
|
||||
$scope.selected = {
|
||||
_id: '',
|
||||
index: 0
|
||||
}
|
||||
}
|
||||
if($scope.selected._id === field_id){
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.selected._id = field_id;
|
||||
$scope.selected.index = field_index;
|
||||
|
||||
|
||||
var nb_valid = $filter('formValidity')($scope.myform);
|
||||
$scope.translateAdvancementData = {
|
||||
done: nb_valid,
|
||||
total: form_fields_count,
|
||||
answers_not_completed: form_fields_count - nb_valid
|
||||
};
|
||||
|
||||
if(animateScroll){
|
||||
NOSCROLL=true;
|
||||
setTimeout(function() {
|
||||
$document.scrollToElement(angular.element('.activeField'), -10, 200).then(function() {
|
||||
NOSCROLL = false;
|
||||
setTimeout(function() {
|
||||
if (document.querySelectorAll('.activeField .focusOn').length) {
|
||||
//Handle default case
|
||||
document.querySelectorAll('.activeField .focusOn')[0].focus();
|
||||
} else if(document.querySelectorAll('.activeField input').length) {
|
||||
//Handle case for rating input
|
||||
document.querySelectorAll('.activeField input')[0].focus();
|
||||
} else {
|
||||
//Handle case for dropdown input
|
||||
document.querySelectorAll('.activeField .selectize-input')[0].focus();
|
||||
}
|
||||
});
|
||||
});
|
||||
$document.scrollToElement(angular.element('#'+field_id), -10, 300).then(function() {
|
||||
if (angular.element('#'+field_id+' .focusOn').length) {
|
||||
//Handle default case
|
||||
angular.element('#'+field_id+' .focusOn')[0].focus();
|
||||
} else if(angular.element('#'+field_id+' input').length) {
|
||||
//Handle case for rating input
|
||||
angular.element('#'+field_id+' input')[0].focus();
|
||||
} else {
|
||||
//Handle case for dropdown input
|
||||
angular.element('#'+field_id+'.selectize-input')[0].focus();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (angular.element('#'+field_id+' .focusOn').length) {
|
||||
//Handle default case
|
||||
angular.element('#'+field_id+' .focusOn')[0].focus();
|
||||
} else if(angular.element('#'+field_id+' input').length) {
|
||||
//Handle case for rating input
|
||||
angular.element('#'+field_id+' input')[0].focus();
|
||||
} else if(angular.element('#'+field_id+'.selectize-input').length) {
|
||||
//Handle case for dropdown input
|
||||
angular.element('#'+field_id+'.selectize-input')[0].focus();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$scope.$watch('selected.index', function(oldValue, newValue){
|
||||
if(oldValue !== newValue && newValue < $scope.myform.form_fields.length){
|
||||
//Only send analytics data if form has not been submitted
|
||||
if(!$scope.myform.submitted){
|
||||
console.log('SendVisitorData.send()');
|
||||
SendVisitorData.send($scope.myform, newValue, TimeCounter.getTimeElapsed());
|
||||
}
|
||||
}
|
||||
$rootScope.$on('duScrollspy:becameActive', function($event, $element, $target){
|
||||
$scope.setActiveField($element.prop('id'), null, false);
|
||||
$scope.updateFormValidity();
|
||||
$scope.$apply()
|
||||
if(!$scope.myform.submitted){
|
||||
SendVisitorData.send($scope.myform, $rootScope.getActiveField(), TimeCounter.getTimeElapsed());
|
||||
}
|
||||
});
|
||||
|
||||
//Fire event when window is scrolled
|
||||
$window.onscroll = function(){
|
||||
if(!NOSCROLL){
|
||||
$rootScope.nextField = $scope.nextField = function(){
|
||||
if($scope.selected && $scope.selected.index > -1){
|
||||
if($scope.selected._id !== FORM_ACTION_ID){
|
||||
var currField = $scope.myform.visible_form_fields[$scope.selected.index];
|
||||
|
||||
var scrollTop = $(window).scrollTop();
|
||||
var elemBox = document.getElementsByClassName('activeField')[0].getBoundingClientRect();
|
||||
var fieldTop = elemBox.top;
|
||||
var fieldBottom = elemBox.bottom;
|
||||
|
||||
var field_id, field_index;
|
||||
var elemHeight = $('.activeField').height();
|
||||
|
||||
var submitSectionHeight = $('.form-actions').height();
|
||||
var maxScrollTop = $(document).height() - $(window).height();
|
||||
var fieldWrapperHeight = $('form_fields').height();
|
||||
|
||||
var selector = 'form > .field-directive:nth-of-type(' + String($scope.myform.visible_form_fields.length - 1)+ ')'
|
||||
var fieldDirectiveHeight = $(selector).height()
|
||||
var scrollPosition = maxScrollTop - submitSectionHeight - fieldDirectiveHeight*1.2;
|
||||
|
||||
var fractionToJump = 0.9;
|
||||
|
||||
//Focus on field above submit form button
|
||||
if($scope.selected.index === $scope.myform.visible_form_fields.length){
|
||||
if(scrollTop < scrollPosition){
|
||||
field_index = $scope.selected.index-1;
|
||||
$scope.setActiveField(null, field_index, false);
|
||||
//Jump to logicJump's destination if it is true
|
||||
if(currField.logicJump && currField.logicJump.jumpTo && evaluateLogicJump(currField)){
|
||||
$scope.setActiveField(currField.logicJump.jumpTo, null, true);
|
||||
} else if($scope.selected.index < $scope.myform.visible_form_fields.length-1){
|
||||
$scope.setActiveField(null, $scope.selected.index+1, true);
|
||||
} else {
|
||||
$scope.setActiveField(FORM_ACTION_ID, null, true);
|
||||
}
|
||||
}
|
||||
|
||||
//Focus on submit form button
|
||||
else if($scope.selected.index === $scope.myform.visible_form_fields.length-1 && scrollTop > scrollPosition){
|
||||
field_index = $scope.selected.index+1;
|
||||
$scope.setActiveField(FORM_ACTION_ID, field_index, false);
|
||||
}
|
||||
|
||||
//If we scrolled bellow the current field, move to next field
|
||||
else if(fieldBottom < elemHeight * fractionToJump && $scope.selected.index < $scope.myform.visible_form_fields.length-1 ){
|
||||
field_index = $scope.selected.index+1;
|
||||
$scope.setActiveField(null, field_index, false);
|
||||
}
|
||||
//If we scrolled above the current field, move to prev field
|
||||
else if ( $scope.selected.index !== 0 && fieldTop > elemHeight * fractionToJump) {
|
||||
field_index = $scope.selected.index-1;
|
||||
$scope.setActiveField(null, field_index, false);
|
||||
}
|
||||
} else {
|
||||
//If selected is not defined go to the first field
|
||||
$scope.setActiveField(null, 0, true);
|
||||
}
|
||||
|
||||
$scope.$apply();
|
||||
};
|
||||
|
||||
$rootScope.nextField = $scope.nextField = function(){
|
||||
if($scope.selected && $scope.selected.index > -1){
|
||||
|
||||
if($scope.selected._id !== FORM_ACTION_ID){
|
||||
var currField = $scope.myform.visible_form_fields[$scope.selected.index];
|
||||
|
||||
//Jump to logicJump's destination if it is true
|
||||
if(currField.logicJump && currField.logicJump.jumpTo && evaluateLogicJump(currField)){
|
||||
$scope.setActiveField(currField.logicJump.jumpTo, null, true);
|
||||
} else if($scope.selected.index < $scope.myform.visible_form_fields.length-1){
|
||||
$scope.setActiveField(null, $scope.selected.index+1, true);
|
||||
} else {
|
||||
$scope.setActiveField(FORM_ACTION_ID, null, true);
|
||||
}
|
||||
} else {
|
||||
//If we are at the submit actions page, go to the first field
|
||||
$rootScope.setActiveField(null, 0, true);
|
||||
}
|
||||
} else {
|
||||
//If selected is not defined go to the first field
|
||||
$rootScope.setActiveField(null, 0, true);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
$rootScope.prevField = $scope.prevField = function(){
|
||||
console.log('prevField');
|
||||
console.log($scope.selected);
|
||||
var selected_index = $scope.selected.index - 1;
|
||||
var selected_index = $scope.selected.index - 1;
|
||||
if($scope.selected.index > 0){
|
||||
$scope.setActiveField(null, selected_index, true);
|
||||
}
|
||||
};
|
||||
|
||||
$rootScope.goToInvalid = $scope.goToInvalid = function() {
|
||||
var field_id = $('.row.field-directive .ng-invalid.focusOn, .row.field-directive .ng-untouched.focusOn:not(.ng-valid)').first().parents('.row.field-directive').first().attr('data-id');
|
||||
$scope.setActiveField(field_id, null, true);
|
||||
};
|
||||
var field_id = $('.ng-invalid, .ng-untouched').first().parents('.row.field-directive').first().attr('id');
|
||||
$scope.setActiveField(field_id, null, true);
|
||||
};
|
||||
|
||||
/*
|
||||
** Form Display Functions
|
||||
@ -308,109 +253,98 @@ angular.module('view-form').directive('submitFormDirective', ['$http', 'TimeCoun
|
||||
}
|
||||
};
|
||||
|
||||
var getDeviceData = function(){
|
||||
var md = new MobileDetect(window.navigator.userAgent);
|
||||
var deviceType = 'other';
|
||||
var getDeviceData = function(){
|
||||
var md = new MobileDetect(window.navigator.userAgent);
|
||||
var deviceType = 'other';
|
||||
|
||||
if (md.tablet()){
|
||||
deviceType = 'tablet';
|
||||
} else if (md.mobile()) {
|
||||
deviceType = 'mobile';
|
||||
} else if (!md.is('bot')) {
|
||||
deviceType = 'desktop';
|
||||
}
|
||||
if (md.tablet()){
|
||||
deviceType = 'tablet';
|
||||
} else if (md.mobile()) {
|
||||
deviceType = 'mobile';
|
||||
} else if (!md.is('bot')) {
|
||||
deviceType = 'desktop';
|
||||
}
|
||||
|
||||
return {
|
||||
type: deviceType,
|
||||
name: window.navigator.platform
|
||||
};
|
||||
};
|
||||
return {
|
||||
type: deviceType,
|
||||
name: window.navigator.platform
|
||||
};
|
||||
};
|
||||
|
||||
var getIpAndGeo = function(){
|
||||
//Get Ip Address and GeoLocation Data
|
||||
$.ajaxSetup( { 'async': false } );
|
||||
var geoData = $.getJSON('https://freegeoip.net/json/').responseJSON;
|
||||
$.ajaxSetup( { 'async': true } );
|
||||
var getIpAndGeo = function(){
|
||||
//Get Ip Address and GeoLocation Data
|
||||
$.ajaxSetup( { 'async': false } );
|
||||
var geoData = $.getJSON('https://freegeoip.net/json/').responseJSON;
|
||||
$.ajaxSetup( { 'async': true } );
|
||||
|
||||
if(!geoData || !geoData.ip){
|
||||
geoData = {
|
||||
ip: 'Adblocker'
|
||||
};
|
||||
}
|
||||
if(!geoData || !geoData.ip){
|
||||
geoData = {
|
||||
ip: 'Adblocker'
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
ipAddr: geoData.ip,
|
||||
geoLocation: {
|
||||
City: geoData.city,
|
||||
Country: geoData.country_name
|
||||
}
|
||||
};
|
||||
};
|
||||
return {
|
||||
ipAddr: geoData.ip,
|
||||
geoLocation: {
|
||||
City: geoData.city,
|
||||
Country: geoData.country_name
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
$rootScope.submitForm = $scope.submitForm = function() {
|
||||
if($scope.forms.myForm.$invalid){
|
||||
$scope.goToInvalid();
|
||||
return;
|
||||
}
|
||||
$rootScope.submitForm = $scope.submitForm = function() {
|
||||
if($scope.forms.myForm.$invalid){
|
||||
$scope.goToInvalid();
|
||||
return;
|
||||
}
|
||||
|
||||
var _timeElapsed = TimeCounter.stopClock();
|
||||
$scope.loading = true;
|
||||
var _timeElapsed = TimeCounter.stopClock();
|
||||
$scope.loading = true;
|
||||
|
||||
var form = _.cloneDeep($scope.myform);
|
||||
var form = _.cloneDeep($scope.myform);
|
||||
|
||||
var deviceData = getDeviceData();
|
||||
form.device = deviceData;
|
||||
var deviceData = getDeviceData();
|
||||
form.device = deviceData;
|
||||
|
||||
var geoData = getIpAndGeo();
|
||||
form.ipAddr = geoData.ipAddr;
|
||||
form.geoLocation = geoData.geoLocation;
|
||||
var geoData = getIpAndGeo();
|
||||
form.ipAddr = geoData.ipAddr;
|
||||
form.geoLocation = geoData.geoLocation;
|
||||
|
||||
form.timeElapsed = _timeElapsed;
|
||||
form.percentageComplete = $filter('formValidity')($scope.myform) / $scope.myform.visible_form_fields.length * 100;
|
||||
delete form.endPage
|
||||
delete form.isLive
|
||||
delete form.provider
|
||||
delete form.startPage
|
||||
delete form.visible_form_fields;
|
||||
delete form.analytics;
|
||||
delete form.design;
|
||||
delete form.submissions;
|
||||
delete form.submitted;
|
||||
for(var i=0; i < $scope.myform.form_fields.length; i++){
|
||||
if($scope.myform.form_fields[i].fieldType === 'dropdown' && !$scope.myform.form_fields[i].deletePreserved){
|
||||
$scope.myform.form_fields[i].fieldValue = $scope.myform.form_fields[i].fieldValue.option_value;
|
||||
}
|
||||
|
||||
//Get rid of unnessecary attributes for each form field
|
||||
delete form.form_fields[i].submissionId;
|
||||
delete form.form_fields[i].disabled;
|
||||
delete form.form_fields[i].ratingOptions;
|
||||
delete form.form_fields[i].fieldOptions;
|
||||
delete form.form_fields[i].logicJump;
|
||||
delete form.form_fields[i].description;
|
||||
delete form.form_fields[i].validFieldTypes;
|
||||
delete form.form_fields[i].fieldType;
|
||||
|
||||
}
|
||||
form.timeElapsed = _timeElapsed;
|
||||
form.percentageComplete = $filter('formValidity')($scope.myform) / $scope.myform.visible_form_fields.length * 100;
|
||||
delete form.endPage;
|
||||
delete form.isLive;
|
||||
delete form.provider;
|
||||
delete form.startPage;
|
||||
delete form.visible_form_fields;
|
||||
delete form.analytics;
|
||||
delete form.design;
|
||||
delete form.submissions;
|
||||
delete form.submitted;
|
||||
for(var i=0; i < $scope.myform.form_fields.length; i++){
|
||||
if($scope.myform.form_fields[i].fieldType === 'dropdown' && !$scope.myform.form_fields[i].deletePreserved){
|
||||
$scope.myform.form_fields[i].fieldValue = $scope.myform.form_fields[i].fieldValue.option_value;
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(function () {
|
||||
$scope.submitPromise = $http.post('/forms/' + $scope.myform._id, form)
|
||||
.success(function (data, status) {
|
||||
$scope.myform.submitted = true;
|
||||
$scope.loading = false;
|
||||
SendVisitorData.send(form, getActiveField(), _timeElapsed);
|
||||
})
|
||||
.error(function (error) {
|
||||
$scope.loading = false;
|
||||
console.error(error);
|
||||
$scope.error = error.message;
|
||||
});
|
||||
}, 500);
|
||||
setTimeout(function () {
|
||||
$scope.submitPromise = $http.post('/forms/' + $scope.myform._id, form)
|
||||
.then(function (data, status) {
|
||||
$scope.myform.submitted = true;
|
||||
$scope.loading = false;
|
||||
SendVisitorData.send($scope.myform, $rootScope.getActiveField(), _timeElapsed);
|
||||
}, function (error) {
|
||||
$scope.loading = false;
|
||||
console.error(error);
|
||||
$scope.error = error.message;
|
||||
});
|
||||
}, 500);
|
||||
};
|
||||
|
||||
//Reload our form
|
||||
$scope.reloadForm();
|
||||
$scope.reloadForm();
|
||||
}
|
||||
};
|
||||
}
|
||||
]);
|
||||
|
||||
|
||||
@ -20,6 +20,7 @@
|
||||
} else {
|
||||
url = window.location.protocol + '//' + window.location.hostname;
|
||||
}
|
||||
url = url.replace(/^http/, 'ws');
|
||||
service.socket = io(url, {'transports': ['websocket', 'polling']});
|
||||
}
|
||||
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
<div class="col-xs-12 field-input">
|
||||
<div class="control-group input-append">
|
||||
<input class="focusOn"
|
||||
ng-focus="setActiveField(field._id, null, false)"
|
||||
name="{{field.fieldType}}{{index}}"
|
||||
ng-style="{'color': design.colors.answerColor, 'border-color': design.colors.answerColor}"
|
||||
ng-class="{ 'no-border': !!field.fieldValue }"
|
||||
ui-date="dateOptions"
|
||||
|
||||
@ -15,11 +15,10 @@
|
||||
</div>
|
||||
<div class="col-xs-12 field-input">
|
||||
<ui-select ng-model="field.fieldValue"
|
||||
ng-focus="setActiveField(field._id, null, false)"
|
||||
name="{{field.fieldType}}{{index}}"
|
||||
class="dropdown"
|
||||
theme="selectize"
|
||||
search-enabled="true"
|
||||
search-by="option_value"
|
||||
set-search-to-answer="true"
|
||||
ng-required="field.required"
|
||||
on-tab-and-shift-key="prevField()"
|
||||
on-tab-key="nextField()"
|
||||
|
||||
@ -22,12 +22,13 @@
|
||||
<label class="btn col-md-5 col-xs-12"
|
||||
ng-class="{activeBtn: field.fieldValue == 'true'}">
|
||||
<input class="focusOn"
|
||||
name="{{field.fieldType}}{{index}}"
|
||||
ng-style="{'color': design.colors.answerColor, 'border-color': design.colors.answerColor}"
|
||||
type="radio" value="true"
|
||||
ng-model="field.fieldValue"
|
||||
ng-model-options="{ debounce: 250 }"
|
||||
ng-required="field.required"
|
||||
ng-change="nextField()"/>
|
||||
ng-click="nextField()"/>
|
||||
<div class="letter" style="float:left">
|
||||
{{ 'Y' | translate }}
|
||||
</div>
|
||||
@ -36,12 +37,13 @@
|
||||
<label class="btn col-md-5 col-md-offset-1 col-xs-12"
|
||||
ng-class="{activeBtn: field.fieldValue == 'false'}">
|
||||
<input class="focusOn"
|
||||
name="{{field.fieldType}}{{index}}"
|
||||
ng-style="{'color': design.colors.answerColor, 'border-color': design.colors.answerColor}"
|
||||
type="radio" value="false"
|
||||
ng-model="field.fieldValue"
|
||||
ng-model-options="{ debounce: 250 }"
|
||||
ng-required="field.required"
|
||||
ng-change="nextField()"/>
|
||||
ng-click="nextField()"/>
|
||||
<div class="letter" style="float:left">
|
||||
{{ 'N' | translate }}
|
||||
</div>
|
||||
|
||||
@ -22,17 +22,16 @@
|
||||
<label class="btn col-md-4 col-xs-12 col-sm-12"
|
||||
style="margin: 0.5em; padding-left:30px"
|
||||
ng-class="{activeBtn: field.fieldValue == field.fieldOptions[$index].option_value}">
|
||||
<div class="letter" style="float:left">
|
||||
{{$index+1}}
|
||||
<div ng-if="field.fieldOptions.length <= 26" class="letter" style="float:left">
|
||||
{{$index | indexToAlphabet}}
|
||||
</div>
|
||||
<input ng-style="{'color': design.colors.answerColor, 'border-color': design.colors.answerColor}"
|
||||
ng-focus="setActiveField(field._id, null, false)"
|
||||
name="{{field.fieldType}}{{index}}"
|
||||
type="radio" class="focusOn"
|
||||
value="{{option.option_value}}"
|
||||
ng-model="field.fieldValue"
|
||||
ng-model-options="{ debounce: 250 }"
|
||||
ng-required="field.required"
|
||||
ng-change="$root.nextField()"/>
|
||||
ng-click="$root.nextField()"/>
|
||||
|
||||
<span ng-bind="option.option_value"></span>
|
||||
</label>
|
||||
|
||||
@ -15,14 +15,13 @@
|
||||
<div class="col-xs-12 field-input">
|
||||
|
||||
<input-stars max="{{field.ratingOptions.steps}}"
|
||||
ng-init="field.fieldValue = 1"
|
||||
name="{{field.fieldType}}{{index}}"
|
||||
on-shape-click="true"
|
||||
on-star-click="nextField()"
|
||||
on-star-click="$root.nextField()"
|
||||
icon-full="{{field.ratingOptions.shape}}"
|
||||
icon-base="fa fa-3x"
|
||||
icon-empty="{{field.ratingOptions.shape}}"
|
||||
ng-model="field.fieldValue"
|
||||
ng-model-options="{ debounce: 250 }"
|
||||
ng-required="field.required"
|
||||
on-enter-or-tab-key="nextField()"
|
||||
on-tab-and-shift-key="prevField()"
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
{{ 'ADD_NEW_LINE_INSTR' | translate }}
|
||||
</small>
|
||||
<textarea class="textarea focusOn" type="text"
|
||||
ng-focus="setActiveField(field._id, null, false)"
|
||||
name="{{field.fieldType}}{{index}}"
|
||||
ng-model="field.fieldValue"
|
||||
ng-model-options="{ debounce: 250 }"
|
||||
ng-class="{ 'no-border': !!field.fieldValue }"
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
<div class="textfield field row"
|
||||
ng-click="setActiveField(field._id, index, true)">
|
||||
<div class="textfield field row">
|
||||
<div class="col-xs-12 field-title row-fluid" ng-style="{'color': design.colors.questionColor}">
|
||||
<h3 class="col-xs-12">
|
||||
<small class="field-number">
|
||||
@ -26,7 +25,6 @@
|
||||
placeholder="{{placeholder}}"
|
||||
ng-class="{ 'no-border': !!field.fieldValue }"
|
||||
class="focusOn text-field-input"
|
||||
ng-focus="setActiveField(field._id, null, false)"
|
||||
ng-model="field.fieldValue"
|
||||
ng-model-options="{ debounce: 250 }"
|
||||
value="field.fieldValue"
|
||||
|
||||
@ -26,9 +26,9 @@
|
||||
<label class="btn btn-default col-md-2 col-sm-3 col-xs-7"
|
||||
style="background: rgba(0,0,0,0.1); text-align:left;">
|
||||
<input type="radio" value="true"
|
||||
name="{{field.fieldType}}{{index}}"
|
||||
class="focusOn"
|
||||
style="opacity: 0; margin-left: 0px;"
|
||||
ng-focus="setActiveField(field._id, null, false)"
|
||||
ng-model="field.fieldValue"
|
||||
ng-model-options="{ debounce: 250 }"
|
||||
ng-required="field.required"
|
||||
@ -46,8 +46,8 @@
|
||||
style="background: rgba(0,0,0,0.1); text-align:left;">
|
||||
|
||||
<input type="radio" value="false"
|
||||
name="{{field.fieldType}}{{index}}"
|
||||
style="opacity:0; margin-left:0px;"
|
||||
ng-focus="setActiveField(field._id, null, false)"
|
||||
ng-model="field.fieldValue"
|
||||
ng-model-options="{ debounce: 250 }"
|
||||
ng-required="field.required"
|
||||
|
||||
@ -41,13 +41,13 @@
|
||||
<div class="row form-field-wrapper">
|
||||
<form name="forms.myForm" novalidate class="submission-form">
|
||||
|
||||
<div ng-repeat="field in myform.form_fields" ng-if="!field.deletePreserved" data-index="{{$index}}" data-id="{{field._id}}" ng-class="{activeField: selected._id == field._id }" class="row field-directive">
|
||||
|
||||
<div ng-repeat="field in myform.form_fields" ng-if="!field.deletePreserved" id="{{field._id}}" class="row field-directive" du-scrollspy="{{field._id}}">
|
||||
<field-directive field="field" design="myform.design" index="$index" forms="forms">
|
||||
</field-directive>
|
||||
</div>
|
||||
<div class="row form-actions" id="submit_field" ng-class="{activeField: selected._id == 'submit_field' }"
|
||||
ng-style="{ 'background-color':myform.design.colors.buttonColor}" style="border-top: 1px solid #ddd; margin-top: 30vh; height: 100vh; margin-left: 1%; margin-right: 1%;"
|
||||
<div class="row form-actions" id="submit_field" du-scrollspy="submit_field"
|
||||
ng-style="{ 'background-color':myform.design.colors.buttonColor}"
|
||||
style="border-top: 1px solid #ddd; margin-top: 30vh; height: 100vh; margin-left: 1%; margin-right: 1%;"
|
||||
on-tab-and-shift-key="prevField()"
|
||||
on-tab-key="nextField()"
|
||||
on-enter-key="submitForm()">
|
||||
@ -56,8 +56,10 @@
|
||||
{{ 'COMPLETING_NEEDED' | translate:translateAdvancementData }}
|
||||
</div>
|
||||
|
||||
<button ng-if="!forms.myForm.$invalid" class="Button btn col-sm-2 col-xs-8 focusOn" v-busy="loading" v-busy-label="Please wait" v-pressable ng-disabled="loading || forms.myForm.$invalid" ng-click="submitForm()" on-enter-key-disabled="loading || forms.myForm.$invalid" ng-style="{'background-color':myform.design.colors.buttonColor, 'color':myform.design.colors.buttonTextColor}" style="font-size: 1.6em; margin-left: 1em; margin-top: 1em;">
|
||||
|
||||
<button ng-if="!forms.myForm.$invalid" class="Button btn col-sm-2 col-xs-8 focusOn" v-busy="loading" v-busy-label="{{ 'WAIT_LABEL' | translate }}" v-pressable
|
||||
ng-disabled="loading || forms.myForm.$invalid" ng-click="submitForm()" on-enter-key-disabled="loading || forms.myForm.$invalid"
|
||||
ng-style="{'background-color':myform.design.colors.buttonColor, 'color':myform.design.colors.buttonTextColor}"
|
||||
style="font-size: 1.6em; margin-left: 1em; margin-top: 1em;">
|
||||
{{ 'SUBMIT' | translate }}
|
||||
</button>
|
||||
|
||||
@ -74,7 +76,7 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<section ng-if="!myform.hideFooter" class="navbar navbar-fixed-bottom" ng-style="{ 'background-color':myform.design.colors.buttonColor, 'padding-top': '15px', 'border-top': '2px '+ myform.design.colors.buttonTextColor +' solid', 'color':myform.design.colors.buttonTextColor}">
|
||||
<section ng-if="myform.showFooter" class="navbar navbar-fixed-bottom" ng-style="{ 'background-color':myform.design.colors.buttonColor, 'padding-top': '15px', 'border-top': '2px '+ myform.design.colors.buttonTextColor +' solid', 'color':myform.design.colors.buttonTextColor}">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-sm-5 col-md-6 col-xs-5" ng-show="!myform.submitted">
|
||||
|
||||
@ -11,8 +11,7 @@
|
||||
function SendVisitorData(Socket, $state) {
|
||||
|
||||
// Create a controller method for sending visitor data
|
||||
function send(form, lastActiveIndex, timeElapsed) {
|
||||
|
||||
function send(form, lastActiveId, timeElapsed) {
|
||||
var lang = window.navigator.userLanguage || window.navigator.language;
|
||||
lang = lang.slice(0,2);
|
||||
|
||||
@ -33,7 +32,7 @@
|
||||
referrer: document.referrer,
|
||||
isSubmitted: form.submitted,
|
||||
formId: form._id,
|
||||
lastActiveField: form.form_fields[lastActiveIndex]._id,
|
||||
lastActiveField: lastActiveId,
|
||||
timeElapsed: timeElapsed,
|
||||
language: lang,
|
||||
deviceType: deviceType,
|
||||
@ -49,7 +48,7 @@
|
||||
if (!Socket.socket) {
|
||||
Socket.connect();
|
||||
}
|
||||
|
||||
|
||||
Socket.on('disconnect', function(){
|
||||
Socket.connect();
|
||||
});
|
||||
|
||||
@ -1,25 +1,29 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('view-form').directive('keyToOption', function(){
|
||||
angular.module('view-form').directive('keyToOption', ['$rootScope', function($rootScope){
|
||||
return {
|
||||
restrict: 'A',
|
||||
scope: {
|
||||
field: '='
|
||||
},
|
||||
link: function($scope, $element, $attrs, $select) {
|
||||
$element.bind('keydown keypress', function(event) {
|
||||
|
||||
link: function($scope, $element, $attrs) {
|
||||
$('body').on('keypress', function(event) {
|
||||
var keyCode = event.which || event.keyCode;
|
||||
var index = parseInt(String.fromCharCode(keyCode))-1;
|
||||
|
||||
if (index < $scope.field.fieldOptions.length) {
|
||||
var index = -1;
|
||||
if(keyCode <= 122 && keyCode >= 97){
|
||||
index = keyCode - 97;
|
||||
} else if (keyCode <= 90 && keyCode >= 65){
|
||||
index = keyCode - 65;
|
||||
}
|
||||
|
||||
if ($scope.field.fieldOptions.length <= 26 && $scope.field._id === $rootScope.getActiveField() && index !== -1 && index < $scope.field.fieldOptions.length) {
|
||||
event.preventDefault();
|
||||
$scope.$apply(function () {
|
||||
$scope.field.fieldValue = $scope.field.fieldOptions[index].option_value;
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
}]);
|
||||
|
||||
@ -2,5 +2,5 @@
|
||||
|
||||
// Use Application configuration module to register a new module
|
||||
ApplicationConfiguration.registerModule('view-form', [
|
||||
'ngFileUpload', 'ui.date', 'angular-input-stars'
|
||||
'ui.date', 'angular-input-stars', 'duScroll'
|
||||
]);
|
||||
|
||||
@ -8,7 +8,9 @@ angular.module('core').config(['$stateProvider', '$urlRouterProvider',
|
||||
}
|
||||
]);
|
||||
|
||||
angular.module(ApplicationConfiguration.applicationModuleName).run(['$rootScope', 'Auth', '$state', '$stateParams',
|
||||
var statesWithoutAuth = ['access_denied', 'signin', 'resendVerifyEmail', 'verify', 'signup', 'signup-success', 'forgot', 'reset-invalid', 'reset', 'reset-success'];
|
||||
|
||||
angular.module(ApplicationConfiguration.applicationModuleName).run(['$rootScope', '$state', '$stateParams',
|
||||
function($rootScope, Auth, $state, $stateParams) {
|
||||
|
||||
$rootScope.$state = $state;
|
||||
@ -20,46 +22,35 @@ angular.module(ApplicationConfiguration.applicationModuleName).run(['$rootScope'
|
||||
state: fromState,
|
||||
params: fromParams
|
||||
}
|
||||
|
||||
var statesToIgnore = ['', 'home', 'signin', 'resendVerifyEmail', 'verify', 'signup', 'signup-success', 'forgot', 'reset-invalid', 'reset', 'reset-success'];
|
||||
|
||||
//Redirect to listForms if user is authenticated
|
||||
if(statesToIgnore.indexOf(toState.name) > 0){
|
||||
if(Auth.isAuthenticated()){
|
||||
event.preventDefault(); // stop current execution
|
||||
$state.go('listForms'); // go to listForms page
|
||||
}
|
||||
}
|
||||
//Redirect to 'signup' route if user is not authenticated
|
||||
else if(toState.name !== 'access_denied' && !Auth.isAuthenticated() && toState.name !== 'submitForm'){
|
||||
event.preventDefault(); // stop current execution
|
||||
$state.go('listForms'); // go to listForms page
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
]);
|
||||
|
||||
//Page access/authorization logic
|
||||
angular.module(ApplicationConfiguration.applicationModuleName).run(['$rootScope', 'Auth', 'User', 'Authorizer', '$state', '$stateParams',
|
||||
function($rootScope, Auth, User, Authorizer, $state, $stateParams) {
|
||||
$rootScope.$on('$stateChangeStart', function(event, next) {
|
||||
var authenticator, permissions, user;
|
||||
permissions = next && next.data && next.data.permissions ? next.data.permissions : null;
|
||||
angular.module(ApplicationConfiguration.applicationModuleName).run(['$rootScope', 'Auth', 'Authorizer', '$state', '$stateParams',
|
||||
function($rootScope, Auth, Authorizer, $state, $stateParams) {
|
||||
$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {
|
||||
|
||||
Auth.ensureHasCurrentUser();
|
||||
user = Auth.currentUser;
|
||||
//Only run permissions check if it is an authenticated state
|
||||
if(statesWithoutAuth.indexOf(toState.name) > -1){
|
||||
Auth.ensureHasCurrentUser().then(
|
||||
function onSuccess(currentUser){
|
||||
if(currentUser){
|
||||
var authenticator = new Authorizer(user);
|
||||
var permissions = toState && toState.data && toState.data.permissions ? toState.data.permissions : null;
|
||||
|
||||
if(user){
|
||||
authenticator = new Authorizer(user);
|
||||
|
||||
if( (permissions !== null) ){
|
||||
if( !authenticator.canAccess(permissions) ){
|
||||
if( permissions !== null && !authenticator.canAccess(permissions) ){
|
||||
event.preventDefault();
|
||||
$state.go('access_denied');
|
||||
}
|
||||
}
|
||||
},
|
||||
function onError(error){
|
||||
event.preventDefault();
|
||||
$state.go('access_denied');
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}]);
|
||||
|
||||
@ -6,10 +6,10 @@ angular.module('core').config(['$translateProvider', function ($translateProvide
|
||||
MENU: 'MENU',
|
||||
SIGNUP_TAB: 'Créer un compte',
|
||||
SIGNIN_TAB: 'Connexion',
|
||||
SIGNOUT_TAB: 'Créer un compte',
|
||||
EDIT_PROFILE: 'Modifier mon profil',
|
||||
MY_SETTINGS: 'Mes paramètres',
|
||||
CHANGE_PASSWORD: 'Changer mon mot de passe',
|
||||
SIGNOUT_TAB: 'Déconnexion',
|
||||
EDIT_PROFILE: 'Modifier Mon Profil',
|
||||
MY_SETTINGS: 'Mes Paramètres',
|
||||
CHANGE_PASSWORD: 'Changer mon Mot de Pass',
|
||||
TOGGLE_NAVIGATION: 'Basculer la navigation',
|
||||
});
|
||||
}]);
|
||||
|
||||
@ -5,51 +5,67 @@ angular.module('core').controller('HeaderController', ['$rootScope', '$scope', '
|
||||
|
||||
$rootScope.signupDisabled = $window.signupDisabled;
|
||||
|
||||
$scope.user = $rootScope.user = Auth.ensureHasCurrentUser();
|
||||
Auth.ensureHasCurrentUser().then(function(currUser){
|
||||
$scope.user = $rootScope.user = currUser;
|
||||
$scope.authentication = $rootScope.authentication = Auth;
|
||||
|
||||
$scope.authentication = $rootScope.authentication = Auth;
|
||||
//Set global app language
|
||||
$rootScope.language = $scope.user.language;
|
||||
$translate.use($scope.user.language);
|
||||
|
||||
$rootScope.languages = $scope.languages = ['en', 'fr', 'es', 'it', 'de'];
|
||||
|
||||
//Set global app language
|
||||
$rootScope.language = $scope.user.language;
|
||||
$translate.use($scope.user.language);
|
||||
|
||||
$scope.isCollapsed = false;
|
||||
$rootScope.hideNav = false;
|
||||
$scope.menu = Menus.getMenu('topbar');
|
||||
|
||||
$scope.signout = function() {
|
||||
var promise = User.logout();
|
||||
promise.then(function() {
|
||||
Auth.logout();
|
||||
Auth.ensureHasCurrentUser();
|
||||
$scope.user = $rootScope.user = null;
|
||||
$state.go('listForms');
|
||||
|
||||
//Refresh view
|
||||
$state.reload();
|
||||
},
|
||||
function(reason) {
|
||||
console.error('Logout Failed: ' + reason);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.toggleCollapsibleMenu = function() {
|
||||
$scope.isCollapsed = !$scope.isCollapsed;
|
||||
};
|
||||
|
||||
// Collapsing the menu after navigation
|
||||
$scope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams) {
|
||||
$scope.isCollapsed = false;
|
||||
$rootScope.hideNav = false;
|
||||
if ( angular.isDefined( toState.data ) ) {
|
||||
$scope.menu = Menus.getMenu('topbar');
|
||||
|
||||
$rootScope.languages = ['en', 'fr', 'es', 'it', 'de'];
|
||||
|
||||
$rootScope.langCodeToWord = {
|
||||
'en': 'English',
|
||||
'fr': 'Français',
|
||||
'es': 'Español',
|
||||
'it': 'Italiàno',
|
||||
'de': 'Deutsch'
|
||||
};
|
||||
|
||||
$rootScope.wordToLangCode = {
|
||||
'English': 'en',
|
||||
'Français': 'fr',
|
||||
'Español': 'es',
|
||||
'Italiàno': 'it',
|
||||
'Deutsch': 'de'
|
||||
};
|
||||
|
||||
$scope.signout = function() {
|
||||
var promise = User.logout();
|
||||
promise.then(function() {
|
||||
Auth.logout();
|
||||
$scope.user = $rootScope.user = null;
|
||||
$state.go('signin', { reload: true });
|
||||
},
|
||||
function(reason) {
|
||||
console.error('Logout Failed: ' + reason);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.toggleCollapsibleMenu = function() {
|
||||
$scope.isCollapsed = !$scope.isCollapsed;
|
||||
};
|
||||
|
||||
// Collapsing the menu after navigation
|
||||
$scope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams) {
|
||||
$scope.isCollapsed = false;
|
||||
$rootScope.hideNav = false;
|
||||
if ( angular.isDefined( toState.data ) ) {
|
||||
|
||||
if ( angular.isDefined( toState.data.hideNav ) ) {
|
||||
$rootScope.hideNav = toState.data.hideNav;
|
||||
}
|
||||
}
|
||||
});
|
||||
}, function(){
|
||||
$state.go('signup');
|
||||
})
|
||||
|
||||
if ( angular.isDefined( toState.data.hideNav ) ) {
|
||||
$rootScope.hideNav = toState.data.hideNav;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
]);
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
|
||||
.form-item .title-row > .list-group-item-heading {
|
||||
color: #34628a;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.form-item:hover .title-row > .list-group-item-heading {
|
||||
|
||||
@ -20,19 +20,21 @@
|
||||
|
||||
// Load the main application module
|
||||
beforeEach(module(ApplicationConfiguration.applicationModuleName));
|
||||
beforeEach(module('module-templates'));
|
||||
|
||||
//Mock Authentication Service
|
||||
beforeEach(module(function($provide) {
|
||||
$provide.service('Auth', function() {
|
||||
return {
|
||||
ensureHasCurrentUser: function() {
|
||||
return sampleUser;
|
||||
return {
|
||||
then: function(onFulfilled, onRejected, progressBack) {
|
||||
return onFulfilled(sampleUser);
|
||||
}
|
||||
};
|
||||
},
|
||||
isAuthenticated: function() {
|
||||
return true;
|
||||
},
|
||||
getUserState: function() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
});
|
||||
@ -45,9 +47,5 @@
|
||||
$scope: scope
|
||||
});
|
||||
}));
|
||||
|
||||
it('should expose the authentication service', function() {
|
||||
expect(scope.authentication).toBeTruthy();
|
||||
});
|
||||
});
|
||||
})();
|
||||
@ -1,20 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
(function() {
|
||||
describe('HomeController', function() {
|
||||
//Initialize global variables
|
||||
var scope;
|
||||
|
||||
// Load the main application module
|
||||
beforeEach(module(ApplicationConfiguration.applicationModuleName));
|
||||
|
||||
beforeEach(inject(function($controller, $rootScope) {
|
||||
scope = $rootScope.$new();
|
||||
|
||||
$controller('HomeController', {
|
||||
$scope: scope
|
||||
});
|
||||
}));
|
||||
|
||||
});
|
||||
})();
|
||||
@ -6,16 +6,30 @@ angular.module('forms').config(['$translateProvider', function ($translateProvid
|
||||
|
||||
//Configure Form Tab View
|
||||
ADVANCED_SETTINGS: 'Advanced Settings',
|
||||
FORM_NAME: 'Form Name',
|
||||
FORM_STATUS: 'Form Status',
|
||||
FORM_NAME: 'Your tellform is called',
|
||||
FORM_STATUS: 'Status',
|
||||
PUBLIC: 'Public',
|
||||
PRIVATE: 'Private',
|
||||
GA_TRACKING_CODE: 'Google Analytics Tracking Code',
|
||||
DISPLAY_FOOTER: 'Display Form Footer?',
|
||||
DISPLAY_FOOTER: 'Form Footer',
|
||||
SAVE_CHANGES: 'Save Changes',
|
||||
CANCEL: 'Cancel',
|
||||
DISPLAY_START_PAGE: 'Display Start Page?',
|
||||
DISPLAY_END_PAGE: 'Display Custom End Page?',
|
||||
DISPLAY_START_PAGE: 'Start Page',
|
||||
DISPLAY_END_PAGE: 'Custom End Page',
|
||||
GENERAL_TAB: 'General',
|
||||
SELF_NOTIFICATIONS_TAB: 'Self notifications',
|
||||
RESPONDENT_NOTIFICATIONS_TAB: 'Respondent notifications',
|
||||
|
||||
SEND_NOTIFICATION_TO: 'Send to',
|
||||
NO_EMAIL_FIELD_WARNING: 'Error: You need an email field in your form to send the email to your form respondent',
|
||||
REPLY_TO: 'Reply to',
|
||||
EMAIL_SUBJECT: 'Subject',
|
||||
EMAIL_MESSAGE: 'Message',
|
||||
ENABLE_RESPONDENT_NOTIFICATIONS: 'Respondent Notifications are currently',
|
||||
ENABLE_SELF_NOTIFICATIONS: 'Self Notifications are currently',
|
||||
TOGGLE_ENABLED: 'Enabled',
|
||||
TOGGLE_DISABLED: 'Disabled',
|
||||
ADD_VARIABLE_BUTTON: 'Add variable',
|
||||
|
||||
//List Forms View
|
||||
CREATE_A_NEW_FORM: 'Create a new form',
|
||||
@ -35,6 +49,7 @@ angular.module('forms').config(['$translateProvider', function ($translateProvid
|
||||
LOGIC_JUMP: 'Logic Jump',
|
||||
SHOW_BUTTONS: 'Additional Buttons',
|
||||
SAVE_START_PAGE: 'Save',
|
||||
ADD_OPTIONS_PLACEHOLDER: 'Add one choice per line. Minumum of one choice is required',
|
||||
|
||||
//Admin Form View
|
||||
ARE_YOU_SURE: 'Are you ABSOLUTELY sure?',
|
||||
@ -47,9 +62,12 @@ angular.module('forms').config(['$translateProvider', function ($translateProvid
|
||||
DELETE_FORM_MD: 'Delete Form',
|
||||
DELETE: 'Delete',
|
||||
FORM: 'Form',
|
||||
VIEW: 'View',
|
||||
VIEW_MY_TELLFORM: 'View my tellform',
|
||||
LIVE: 'Live',
|
||||
PREVIEW: 'Preview',
|
||||
|
||||
//Share Tab
|
||||
COPIED_LABEL: 'Copied',
|
||||
COPY: 'Copy',
|
||||
COPY_AND_PASTE: 'Copy and Paste this to add your TellForm to your website',
|
||||
CHANGE_WIDTH_AND_HEIGHT: 'Change the width and height values to suit you best',
|
||||
@ -143,9 +161,9 @@ angular.module('forms').config(['$translateProvider', function ($translateProvid
|
||||
//Admin Tabs
|
||||
CREATE_TAB: 'Create',
|
||||
DESIGN_TAB: 'Design',
|
||||
CONFIGURE_TAB: 'Configure',
|
||||
CONFIGURE_TAB: 'Form Settings',
|
||||
ANALYZE_TAB: 'Analyze',
|
||||
SHARE_TAB: 'Share',
|
||||
SHARE_TAB: 'Share',
|
||||
|
||||
//Field Types
|
||||
SHORT_TEXT: 'Short Text',
|
||||
@ -153,7 +171,7 @@ angular.module('forms').config(['$translateProvider', function ($translateProvid
|
||||
MULTIPLE_CHOICE: 'Multiple Choice',
|
||||
DROPDOWN: 'Dropdown',
|
||||
DATE: 'Date',
|
||||
PARAGRAPH_T: 'Paragraph',
|
||||
PARAGRAPH_FIELD: 'Paragraph',
|
||||
YES_NO: 'Yes/No',
|
||||
LEGAL: 'Legal',
|
||||
RATING: 'Rating',
|
||||
|
||||
@ -5,17 +5,32 @@ angular.module('forms').config(['$translateProvider', function ($translateProvid
|
||||
$translateProvider.translations('fr', {
|
||||
// Configurer la vue de l'onglet Formulaire
|
||||
ADVANCED_SETTINGS: 'Paramètres avancés',
|
||||
FORM_NAME: "Nom du formulaire",
|
||||
FORM_STATUS: 'Statut du formulaire',
|
||||
FORM_NAME: "Votre tellform est appelé",
|
||||
FORM_STATUS: 'Statut',
|
||||
PUBLIC: 'Public',
|
||||
PRIVATE: "Privé",
|
||||
GA_TRACKING_CODE: "Code de suivi Google Analytics",
|
||||
DISPLAY_FOOTER: "Afficher le pied de formulaire ?",
|
||||
DISPLAY_FOOTER: "Afficher le pied de formulaire?",
|
||||
SAVE_CHANGES: 'Enregistrer les modifications',
|
||||
CANCEL: 'Annuler',
|
||||
DISPLAY_START_PAGE: "Afficher la page de démarrage ?",
|
||||
DISPLAY_END_PAGE: "Afficher la page de fin personnalisée ?",
|
||||
|
||||
GENERAL_TAB: 'General',
|
||||
SELF_NOTIFICATIONS_TAB: 'Self notifications',
|
||||
RESPONDANT_NOTIFICATIONS_TAB: 'Respondent notifications',
|
||||
|
||||
SEND_NOTIFICATION_TO: 'Envoyer à',
|
||||
NO_EMAIL_FIELD_WARNING: 'Erreur: Vous avez besoin d\'un champ e-mail dans votre formulaire pour envoyer l\'e-mail au répondant de votre formulaire',
|
||||
REPLY_TO: "Répondre à",
|
||||
EMAIL_SUBJECT: 'Sujet',
|
||||
EMAIL_MESSAGE: "Message",
|
||||
ENABLE_RESPONDENT_NOTIFICATIONS: 'Les notifications des répondants sont actuellement',
|
||||
ENABLE_SELF_NOTIFICATIONS: 'Les notifications automatiques sont actuellement',
|
||||
TOGGLE_ENABLED: 'Activé',
|
||||
TOGGLE_DISABLED: 'Désactivé',
|
||||
ADD_VARIABLE_BUTTON: "Ajouter une variable",
|
||||
|
||||
// Afficher les formulaires
|
||||
CREATE_A_NEW_FORM: "Créer un nouveau formulaire",
|
||||
CREATE_FORM: "Créer un formulaire",
|
||||
@ -34,6 +49,7 @@ angular.module('forms').config(['$translateProvider', function ($translateProvid
|
||||
LOGIC_JUMP: 'Saut logique',
|
||||
SHOW_BUTTONS: 'Boutons supplémentaires',
|
||||
SAVE_START_PAGE: "Enregistrer",
|
||||
ADD_OPTIONS_PLACEHOLDER: "Ajouter un choix par ligne. Un minimum d'un choix est requis",
|
||||
|
||||
// Affichage du formulaire d'administration
|
||||
ARE_YOU_SURE: 'Es-tu ABSOLUMENT sûr?',
|
||||
@ -46,9 +62,12 @@ angular.module('forms').config(['$translateProvider', function ($translateProvid
|
||||
DELETE_FORM_MD: "Supprimer le formulaire",
|
||||
DELETE: "Supprimer",
|
||||
FORM: 'Formulaire',
|
||||
VIEW: "Afficher",
|
||||
VIEW_MY_TELLFORM: "Afficher ma forme",
|
||||
LIVE: "Live",
|
||||
PREVIEW: 'Aperçu',
|
||||
|
||||
//Share Tab
|
||||
COPIED_LABEL: 'Copié',
|
||||
COPY: "Copier",
|
||||
COPY_AND_PASTE: "Copiez et collez ceci pour ajouter votre TellForm à votre site Web",
|
||||
CHANGE_WIDTH_AND_HEIGHT: "Changez les valeurs de largeur et de hauteur pour mieux vous convenir",
|
||||
@ -152,7 +171,7 @@ angular.module('forms').config(['$translateProvider', function ($translateProvid
|
||||
MULTIPLE_CHOICE: 'Choix multiple',
|
||||
DROPDOWN: 'Menu Déroulant',
|
||||
DATE: 'Date',
|
||||
PARAGRAPH_T: "Paragraphe",
|
||||
PARAGRAPH_FIELD: "Paragraphe",
|
||||
OUI_NON: 'Oui / Non',
|
||||
LEGAL: 'Légal',
|
||||
RATING: "Évaluation",
|
||||
|
||||
@ -5,16 +5,31 @@ angular.module('forms').config(['$translateProvider', function ($translateProvid
|
||||
$translateProvider.translations('de', {
|
||||
// Konfigurieren der Formularregisterkarte
|
||||
ADVANCED_SETTINGS: 'Erweiterte Einstellungen',
|
||||
FORM_NAME: 'Formularname',
|
||||
FORM_STATUS: 'Formularstatus',
|
||||
FORM_NAME: 'Ihr tellform heißt',
|
||||
FORM_STATUS: 'Status',
|
||||
PUBLIC: 'Öffentlich',
|
||||
PRIVATE: 'Privat',
|
||||
GA_TRACKING_CODE: 'Google Analytics Tracking-Code',
|
||||
DISPLAY_FOOTER: 'Formularfußzeile anzeigen?',
|
||||
DISPLAY_FOOTER: 'Fußzeile',
|
||||
SAVE_CHANGES: 'Änderungen speichern',
|
||||
CANCEL: 'Abbrechen',
|
||||
DISPLAY_START_PAGE: 'Startseite anzeigen?',
|
||||
DISPLAY_END_PAGE: 'Benutzerdefinierte Endseite anzeigen?',
|
||||
DISPLAY_START_PAGE: 'Startseite',
|
||||
DISPLAY_END_PAGE: 'Benutzerdefinierte Endseite',
|
||||
|
||||
GENERAL_TAB: 'Allgemein',
|
||||
SELF_NOTIFICATIONS_TAB: 'Selbstbenachrichtigungen',
|
||||
RESPONDANT_NOTIFICATIONS_TAB: 'Beantwortungsbenachrichtigungen',
|
||||
|
||||
SEND_NOTIFICATION_TO: 'Senden an',
|
||||
NO_EMAIL_FIELD_WARNING: 'Fehler: Sie benötigen ein E-Mail-Feld in Ihrem Formular, um die E-Mail an Ihr Formular zu senden.',
|
||||
REPLY_TO: 'Antworten auf',
|
||||
EMAIL_SUBJECT: "Betreff",
|
||||
EMAIL_MESSAGE: 'Nachricht',
|
||||
ENABLE_RESPONDENT_NOTIFICATIONS: 'Antwortbenachrichtigungen sind derzeit',
|
||||
ENABLE_SELF_NOTIFICATIONS: 'Selbstbenachrichtigungen sind derzeit',
|
||||
TOGGLE_ENABLED: 'Aktiviert',
|
||||
TOGGLE_DISABLED: 'Deaktiviert',
|
||||
ADD_VARIABLE_BUTTON: 'Variable hinzufügen',
|
||||
|
||||
// Listenformularansicht
|
||||
CREATE_A_NEW_FORM: 'Erstelle ein neues Formular',
|
||||
@ -34,6 +49,7 @@ angular.module('forms').config(['$translateProvider', function ($translateProvid
|
||||
LOGIC_JUMP: 'Logischer Sprung',
|
||||
SHOW_BUTTONS: 'Zusätzliche Schaltflächen',
|
||||
SAVE_START_PAGE: 'Speichern',
|
||||
ADD_OPTIONS_PLACEHOLDER: 'Fügen Sie eine Auswahl pro Zeile hinzu. Mindestens eine Wahl ist erforderlich.',
|
||||
|
||||
// Admin-Formularansicht
|
||||
ARE_YOU_SURE: "Bist du ABSOLUT sicher?",
|
||||
@ -46,12 +62,14 @@ angular.module('forms').config(['$translateProvider', function ($translateProvid
|
||||
DELETE_FORM_MD: 'Formular löschen',
|
||||
DELETE: 'Löschen',
|
||||
FORM: 'Formular',
|
||||
VIEW: 'Ansicht',
|
||||
VIEW_MY_TELLFORM: 'Mein tellform anzeigen',
|
||||
LIVE: 'Leben',
|
||||
PREVIEW: 'Vorschau',
|
||||
|
||||
//Share Tab
|
||||
COPIED_LABEL: 'Kopiert',
|
||||
COPY: 'Kopieren',
|
||||
COPY_AND_PASTE: 'Kopieren und einfügen, um Ihre TellForm auf Ihrer Website hinzuzufügen',
|
||||
CHANGE_WIDTH_AND_HEIGHT: 'Ändern Sie die Werte für Breite und Höhe, um Ihnen am besten zu entsprechen',
|
||||
POWERED_BY: 'Unterstützt von',
|
||||
TELLFORM_URL: "Ihr TellForm ist dauerhaft unter dieser URL",
|
||||
|
||||
@ -151,7 +169,7 @@ angular.module('forms').config(['$translateProvider', function ($translateProvid
|
||||
MULTIPLE_CHOICE: 'Mehrfachauswahl',
|
||||
DROPDOWN: 'Dropdown-Liste',
|
||||
DATE: 'Datum',
|
||||
PARAGRAPH_T: "Absatz",
|
||||
PARAGRAPH_FIELD: "Absatz",
|
||||
YES_NO: 'Ja / Nein',
|
||||
LEGAL: "Rechtliche",
|
||||
RATING: 'Bewertung',
|
||||
|
||||
@ -5,16 +5,31 @@ angular.module('forms').config(['$translateProvider', function ($translateProvid
|
||||
$translateProvider.translations('it', {
|
||||
// Configura la visualizzazione scheda modulo
|
||||
ADVANCED_SETTINGS: 'Impostazioni avanzate',
|
||||
FORM_NAME: 'Nome modulo',
|
||||
FORM_STATUS: 'Stato modulo',
|
||||
FORM_NAME: 'Il tuo tellform è chiamato',
|
||||
FORM_STATUS: 'Stato',
|
||||
PUBLIC: 'pubblico',
|
||||
PRIVATE: 'Privato',
|
||||
GA_TRACKING_CODE: 'Codice di monitoraggio di Google Analytics',
|
||||
DISPLAY_FOOTER: 'Visualizza piè di pagina?',
|
||||
DISPLAY_FOOTER: 'Piè di pagina',
|
||||
SAVE_CHANGES: 'Salva modifiche',
|
||||
CANCEL: 'Annulla',
|
||||
DISPLAY_START_PAGE: 'Visualizza pagina iniziale?',
|
||||
DISPLAY_END_PAGE: 'Mostra pagina finale personalizzata?',
|
||||
DISPLAY_START_PAGE: 'Pagina iniziale',
|
||||
DISPLAY_END_PAGE: 'Pagina finale personalizzata',
|
||||
|
||||
GENERAL_TAB: 'Generale',
|
||||
SELF_NOTIFICATIONS_TAB: 'Autodiagnosi',
|
||||
RESPONDANT_NOTIFICATIONS_TAB: 'Notifiche rispondenti',
|
||||
|
||||
SEND_NOTIFICATION_TO: 'Invia a',
|
||||
NO_EMAIL_FIELD_WARNING: 'Errore: Hai bisogno di un campo e-mail nel tuo modulo per inviare l\'email al tuo interlocutore',
|
||||
REPLY_TO: 'Rispondi a',
|
||||
EMAIL_SUBJECT: 'Oggetto',
|
||||
EMAIL_MESSAGE: 'Messaggio',
|
||||
ENABLE_RESPONDENT_NOTIFICATIONS: 'Notifiche rispondenti sono attualmente',
|
||||
ENABLE_SELF_NOTIFICATIONS: 'Le notifiche auto sono attualmente',
|
||||
TOGGLE_ENABLED: 'Abilitato',
|
||||
TOGGLE_DISABLED: 'disabilitato',
|
||||
ADD_VARIABLE_BUTTON: 'Aggiungi variabile',
|
||||
|
||||
// Visualizzazione dei moduli di elenco
|
||||
CREATE_A_NEW_FORM: 'Crea un nuovo modulo',
|
||||
@ -34,6 +49,7 @@ angular.module('forms').config(['$translateProvider', function ($translateProvid
|
||||
LOGIC_JUMP: 'Jump Logic',
|
||||
SHOW_BUTTONS: 'Pulsanti aggiuntivi',
|
||||
SAVE_START_PAGE: 'Salva',
|
||||
ADD_OPTIONS_PLACEHOLDER: "Aggiungi una scelta per riga. È necessario un minimo di una scelta.",
|
||||
|
||||
// Visualizzazione modulo di amministrazione
|
||||
ARE_YOU_SURE: 'Sei ASSOLUTAMENTE sicuro?',
|
||||
@ -46,12 +62,14 @@ angular.module('forms').config(['$translateProvider', function ($translateProvid
|
||||
DELETE_FORM_MD: 'Elimina modulo',
|
||||
DELETE: 'Elimina',
|
||||
FORM: 'Forma',
|
||||
VIEW: 'Visualizza',
|
||||
VIEW_MY_TELLFORM: 'Visualizza la mia informazione',
|
||||
LIVE: 'Live',
|
||||
PREVIEW: 'Anteprima',
|
||||
|
||||
// Share Tab
|
||||
COPIED_LABEL: "Copiato",
|
||||
COPY: 'Copia',
|
||||
COPY_AND_PASTE: 'Copia e incolla questo per aggiungere il tuo TellForm al tuo sito web',
|
||||
CHANGE_WIDTH_AND_HEIGHT: 'Modifica i valori di larghezza e di altezza per adattarti al meglio',
|
||||
POWERED_BY: 'Offerto da',
|
||||
TELLFORM_URL: 'Il tuo TellForm è permanente in questo URL',
|
||||
|
||||
@ -152,7 +170,7 @@ angular.module('forms').config(['$translateProvider', function ($translateProvid
|
||||
MULTIPLE_CHOICE: 'Scelta multipla',
|
||||
DROPDOWN: 'Dropdown',
|
||||
DATE: 'Data',
|
||||
PARAGRAPH_T: 'Paragrafo',
|
||||
PARAGRAPH_FIELD: 'Paragrafo',
|
||||
YES_NO: 'Sì / no',
|
||||
LEGAL: 'Legale',
|
||||
RATING: 'Valutazione',
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user