Compare commits

..

396 Commits

Author SHA1 Message Date
Leopere
42050dc078
Update README.md
updated demo url
2023-10-13 13:49:50 -04:00
Michael Schramm
ffe74eafcd
Merge pull request #182 from ru2saig/master
Updated install link for node-prune
2023-08-25 12:18:35 +02:00
Michael Schramm
10db1541c2 fix node prune location
fixes https://github.com/ohmyform/ohmyform/issues/184
2022-07-28 08:02:14 +02:00
Michael Schramm
2a9ef35ef2
Update CHANGELOG.md 2022-07-04 14:09:47 +02:00
Michael Schramm
405fb04ce6
Merge pull request #185 from joebailey26/patch-1
Fixed typo
2022-07-04 14:08:28 +02:00
Joe Bailey
a7d64bac79
Fixed typo 2022-06-09 09:03:09 +01:00
ru2saig
ee962f9afa Updated install link for node-prune 2022-05-23 00:49:08 +05:30
Michael Schramm
bef2eb7344 release 1.0.3 2022-03-27 20:24:54 +02:00
Michael Schramm
ed3d0e7392 release 1.0.3 2022-03-27 20:24:28 +02:00
Michael Schramm
deb81d2164
Update CHANGELOG.md 2022-03-16 16:32:17 +01:00
Michael Schramm
b6c651e47f
Merge pull request #167 from spencerflagg/master
Added minimal config example for Caddy server
2022-03-16 16:31:39 +01:00
Spencer Flagg
9887d1a898
Added minimal config example for Caddy server 2022-03-16 14:52:57 +01:00
Michael Schramm
701f43c98f release 1.0.2 2022-03-13 23:48:01 +01:00
Michael Schramm
912be0bc38 changed docker restart policy 2022-03-13 23:39:46 +01:00
Michael Schramm
9c58d92aee release 1.0.1 2022-03-01 23:43:45 +01:00
Michael Schramm
2cd3bd0e1d fix docker build 2022-02-28 09:08:10 +01:00
Michael Schramm
13ceb1fe52 add libc6-compat 2022-02-28 08:41:42 +01:00
Michael Schramm
6c34e65c3b upgrade to node 16 2022-02-28 08:37:52 +01:00
Michael Schramm
17ce7328bb update changelog for version 1.0.0 2022-02-28 08:34:29 +01:00
Michael Schramm
30de61172a update submodules 2022-02-13 23:14:24 +01:00
Michael Schramm
d3ec553b33 update submodules 2022-02-13 22:37:48 +01:00
Michael Schramm
4537b35c6b fix combined image 2022-01-13 16:47:35 +01:00
Michael Schramm
3ad5c4e86b update to latest api 2022-01-13 14:50:00 +01:00
Michael Schramm
2e47aaf645 update ui to latest 2022-01-03 20:19:09 +01:00
Michael Schramm
5ac5d3a6be update github actions 2022-01-03 14:36:47 +01:00
Michael Schramm
8de3d59d04 update github actions 2022-01-03 14:34:16 +01:00
Michael Schramm
bf575badb0 test new tags 2022-01-03 13:08:34 +01:00
Michael Schramm
3fcde897b7 update api and ui and fix env 2022-01-03 00:58:36 +01:00
Michael Schramm
52ddbc72f7
Merge pull request #148 from real-or-random/patch-1
don't mkdir /run/nginx if it exists
2022-01-02 16:16:35 +01:00
Tim Ruffing
1866b3054c
don't mkdir /run/nginx if it exists
Fixes #147.
2021-12-07 23:40:15 +01:00
Michael Schramm
4eb162b8e0 fix example env variable names
https://github.com/ohmyform/ohmyform/issues/134
2021-05-07 00:32:49 +02:00
Michael Schramm
feff3056a5 fix problem with node-prune on production build 2021-05-06 20:12:03 +02:00
Michael Schramm
e05c36c26a update ui to include improvements on api connection error and side menu once logged in 2021-05-04 18:31:19 +02:00
Michael Schramm
596999193a update api to apply version fix 2021-05-04 18:06:36 +02:00
Michael Schramm
d71bd22924 upgrade refs to latest version 2021-05-02 18:38:15 +02:00
Michael Schramm
a4454fa6ad update minimal example to not even require db 2021-05-02 18:37:46 +02:00
Michael Schramm
861b6aa951 update texts 2021-05-02 16:26:28 +02:00
Michael Schramm
5414f3b371 - switched to supervisord based combined container
- heroku deployments
2021-05-02 15:53:01 +02:00
Michael Schramm
46760ae135 remove email and password from heroku deploy 2021-05-02 14:07:35 +02:00
Michael Schramm
fa11cb1214 theoretical fix for heroku deployments 2021-05-02 14:04:25 +02:00
Michael Schramm
97441902a6 update ref to release 0.9.9 2021-02-14 14:20:04 +01:00
Michael Schramm
6ef9b8e52c
Merge pull request #120 from patcon/patch-1
Correct opencollective Sponsor link.
2021-01-07 07:19:12 +01:00
Patrick Connolly
27682231b7
Correct opencollective Sponsor link. 2020-11-15 14:04:30 -05:00
Michael Schramm
ca5e5a7714
Update CHANGELOG.md 2020-10-09 08:59:15 +02:00
Michael Schramm
adafe06c11
Merge pull request #118 from kmjayadeep/node-upgrade
upgrade node version to 14
2020-10-09 08:57:36 +02:00
Jayadeep KM
17c09b0853 add vesion 12 and 14 to node engine in package.json 2020-10-09 12:25:50 +05:30
Jayadeep KM
9ee877f3e6 upgrade node version for api and ui 2020-10-09 12:09:20 +05:30
Jayadeep KM
46816290fc upgrade node production and build stage versions to 14 2020-10-09 11:48:37 +05:30
Jayadeep KM
79e25a6d52 upgrade node version to 14 2020-10-09 11:46:09 +05:30
Michael Schramm
e4a9088014 fix dependency 2020-09-18 17:34:14 +02:00
Michael Schramm
7b4b772acc fix dependency 2020-09-18 17:29:42 +02:00
Michael Schramm
04dd4abb77 prepare 0.9.8 release 2020-09-02 11:31:32 +02:00
Michael Schramm
4387f48dc8 prepare 0.9.8 release 2020-09-02 11:26:25 +02:00
Michael Schramm
6e1f2b7edb update changelog for 0.9.6 2020-07-17 10:39:15 +02:00
Leopere
6e7194d64b
Correcting FUNDING.yml 2020-07-08 10:05:17 -04:00
Michael Schramm
59d59eb048 reduce docker layer size 2020-06-16 10:45:52 +02:00
Michael Schramm
034da458be
Merge pull request #98 from jmhbnz/master
Switched to non root user
2020-06-16 09:25:07 +02:00
James Blair
c6360ce3be Corrected ownership of schema file. 2020-06-16 19:18:21 +12:00
James Blair
d67a780185 Reverted "Corrected ownership of schema file."
This reverts commit b36a73afdb8ced00c7f1764de541ebf8f8868806.
2020-06-16 19:15:58 +12:00
James Blair
b36a73afdb Corrected ownership of schema file. 2020-06-16 19:12:18 +12:00
James Blair
97af16862a Applied #97 suggested dockerfile. 2020-06-16 18:52:24 +12:00
James Blair
961ff38eb5 Switched to non root user and alpine. 2020-06-16 18:21:17 +12:00
Michael Schramm
4d27b928d8 update changelog 2020-06-11 18:31:15 +02:00
Michael Schramm
e72dcbda9f add demo links and fix setting.service 2020-06-11 12:15:31 +02:00
Michael Schramm
5be5edcc71 add link to dockerhub 2020-06-11 10:42:01 +02:00
Michael Schramm
17908af385 add changelog for 0.9.4 release 2020-06-09 18:22:09 +02:00
Michael Schramm
0ea7aa9bf3 update to latest ui 2020-06-09 18:07:46 +02:00
Michael Schramm
b3d7f26b41 show current ref in heroku 2020-06-09 17:19:25 +02:00
Michael Schramm
40da6a3c3a use latest settings call 2020-06-09 16:50:07 +02:00
Michael Schramm
558306ed6b update api and ui 2020-06-09 16:41:37 +02:00
Michael Schramm
411c2c2987 update readme with status badges 2020-06-09 11:55:19 +02:00
Michael Schramm
0fe0ea3489 add general changelog 2020-06-04 16:30:30 +02:00
Michael Schramm
8501b26511 update dependencies 2020-06-04 15:57:53 +02:00
Michael Schramm
a9fbe35fae fix mongodb persistence path 2020-06-04 11:59:50 +02:00
Michael Schramm
8616f69ff6 add a few more examples 2020-06-04 11:48:33 +02:00
Michael Schramm
f5e31b78aa fix copy path and add mailtrap 2020-06-03 20:16:31 +02:00
Michael Schramm
4927124913 update proc files for heroku once more 2020-06-03 19:55:59 +02:00
Michael Schramm
6f7be0dec9 updates for heroku 2020-06-03 19:44:16 +02:00
Michael Schramm
5b5af35363 fix dockerhub submodules 2020-06-03 19:37:02 +02:00
Michael Schramm
3d6aca7371 fix yarn subcommand 2020-06-03 19:27:01 +02:00
Michael Schramm
3788f03363 install nest globally for startup 2020-06-03 14:39:57 +02:00
Michael Schramm
1ad35869cc switch to git clone since template build process does not have submodule support 2020-06-03 14:34:05 +02:00
Michael Schramm
f709a43f73 add submodule init 2020-06-03 14:23:23 +02:00
Michael Schramm
8cb5376517 update git submodules 2020-06-03 14:19:07 +02:00
Michael Schramm
2f8a6f5cbf fix license in package.json 2020-06-03 14:17:16 +02:00
Michael Schramm
a176a50ff2 switch to cwd for yarn 2020-06-03 14:02:39 +02:00
Michael Schramm
1ec3879ba4 add yarn dependency 2020-06-03 13:55:31 +02:00
Michael Schramm
c0ef337a3d improve build config for heroku 2020-06-03 13:50:07 +02:00
Michael Schramm
d3f463d801 add empty package.json 2020-06-03 13:40:48 +02:00
Michael Schramm
b253917350 add heroku deploy with data from https://github.com/ohmyform/ohmyform/pull/83 2020-06-03 13:27:57 +02:00
Oleg Lavrovsky
c38b4dcecc Added app.json, updated logo
(cherry picked from commit f4e812f954a08f0dfa05b25bf8b969d3f33d15d0)
2020-06-03 13:04:43 +02:00
Michael Schramm
d2dc2ef055
Merge pull request #85 from eapo/master
Update README.md
2020-06-03 12:56:54 +02:00
Michael Schramm
8c915ab083 update version 2020-06-03 12:12:24 +02:00
Michael Schramm
b913d67327
Merge pull request #87 from ohmyform/next
NG
2020-06-03 12:05:18 +02:00
Michael Schramm
5718a4f147 remove dependencies from other images and use gitmodules for docker build 2020-06-03 11:21:52 +02:00
Michael Schramm
2742bbd445 update to api version 0.9.0 2020-06-01 23:39:21 +02:00
Michael Schramm
0a9d629e31 finish submodules 2020-06-01 23:01:17 +02:00
Michael Schramm
8f1e926f31 add submodules 2020-06-01 22:58:33 +02:00
Michael Schramm
b691233be4 switch to AGPL 2020-06-01 22:52:57 +02:00
Michael Schramm
2c0f2795cb add fixed image 2020-06-01 22:11:00 +02:00
Leopere
2e9ab53f0a
Added todo list item 2020-06-01 12:53:00 -04:00
Michael Schramm
8fab0c2c9f update for new structure 2020-06-01 03:57:58 +02:00
Michael Schramm
e7208ea992 update for new structure 2020-06-01 03:43:50 +02:00
eapo
88f8b7d828
Update README.md 2020-05-22 04:56:48 +02:00
Leopere
a63603b530
Merge pull request #84 from opencollective/opencollective
Activating Open Collective
2020-05-14 18:03:14 -04:00
jess
7e7696b8cc
Update README.md
corrected Open Collective slug
2020-05-14 13:45:22 -07:00
Jess
79246e1792 Added financial contributors to the README 2020-05-14 13:42:00 -07:00
Michael Schramm
67468a0c8c
Merge pull request #82 from schoolofdata-ch/master
Fix PaaS deployment via Procfile
2020-05-05 11:53:59 +02:00
Oleg Lavrovsky
d7e8474275 Added grunt build to postinstall 2020-05-05 11:13:15 +02:00
Oleg Lavrovsky
e9fce972a3
Update Procfile 2020-05-04 18:14:53 +02:00
Leopere
3e72a094f1
Removed dead link. 2020-04-29 19:01:39 -04:00
Michael Schramm
2db7403e13
Merge pull request #67 from Chostakovitch/master
Fix file logger creation
2020-04-12 18:14:37 +02:00
Michael Schramm
66c7e831f9
Merge pull request #77 from wodka/bugfix/auth
fix auth errors
2020-04-12 18:12:41 +02:00
Michael Schramm
d01fa8b447 fix composer error message for mailhog, add more error handling 2020-04-12 18:07:20 +02:00
Michael Schramm
fbc1433338
Merge pull request #75 from monizb/patch-2
Fixed: Package Conflicts In Docker File
2020-03-21 16:34:15 +01:00
Monish Basaniwal
57aadf1457
Fixed: Package Conflicts In Docker File 2020-03-21 20:45:00 +05:30
Quentin Duchemin
769fb9f084
Fix file logger creation 2020-01-08 00:27:18 +01:00
Leopere
d06875cdd3
Update README.md 2019-11-20 19:46:14 -05:00
Michael Schramm
857e90680c
Merge pull request #60 from MartinDelille/42-contributing
Add CONTRIBUTING.md
2019-11-12 11:26:15 +01:00
Leopere
7a0154479f
Create FUNDING.yml 2019-11-02 14:41:12 -04:00
Martin Delille
190b9fb4b9
Add CONTRIBUTING.md 2019-10-31 17:39:15 +01:00
Leopere
991afad42a
Merge pull request #48 from mkotamies/clean-depencies
Remove grunt-contrib-uglify dependency. It's needed only as dev depen…
2019-10-11 17:00:53 -04:00
Leopere
cd5c864520
Merge pull request #57 from Leopere/Leopere-socialmedia-1
Adding more social
2019-10-10 14:23:46 -04:00
Leopere
6fbda81fc2
Adding more social 2019-10-10 14:21:16 -04:00
Michael Schramm
c48ef4adca
Merge pull request #56 from wodka/bugfix/bcrypt_error
bugfix hashPassword call that is made with null
2019-10-10 15:21:24 +02:00
wodka
2e5184c0db bugfix call that is made with null 2019-10-10 15:20:14 +02:00
Leopere
44f5d688e9
Merge pull request #45 from ahmedkrmn/non-root-user-docker
Modify docker file to use non-root user 
fixes #43
2019-10-10 05:46:20 -04:00
Michael Schramm
59c526a02c
Merge pull request #55 from wodka/bugfix/bcrypt_error
switch from bcrypt to bcryptjs in legacy app
2019-10-10 09:46:19 +02:00
wodka
9e34fa9c50 switch from bcrypt to bcryptjs in legacy app 2019-10-10 09:44:42 +02:00
Leopere
820b24886d
Merge pull request #53 from mhmoosazadeh/issue52
Resolving issue52
2019-10-09 17:59:33 -04:00
mohammad.moosazadeh
6ab6992896 Update Dockerfile 2019-10-10 00:53:51 +03:30
Leopere
60bfb27aee
Merge pull request #51 from marcelscruz/add-links-to-readme
Add link to README sections
2019-10-09 11:55:05 -04:00
Marcel Cruz
61bd605c77 Add link to README sections 2019-10-09 16:43:30 +02:00
Ahmed Karaman
9fb70fc3c3
Use the same name/id for both the user and the group 2019-10-09 16:26:32 +02:00
Michael Schramm
6d793c32ea
Merge pull request #50 from petmat/fix-post-install-bower
Fix error running npm install in root directory.
2019-10-09 15:58:14 +02:00
Matti Petrelius
c4bbe74b1a Fix error running npm install in root directory. 2019-10-09 16:11:44 +03:00
Mikko Kotamies
42400cafa7 Remove grunt-contrib-uglify dependency. It's needed only as dev dependency 2019-10-09 16:03:03 +03:00
Ahmed Karaman
aee4781083
Fix merge conflict 2019-10-09 05:13:11 +02:00
Ahmed Karaman
c159943d97
Fix syntax error and change group name/id 2019-10-09 05:10:25 +02:00
Ahmed Karaman
e9415784cc
Modify docker file to use non-root user 2019-10-09 01:00:14 +02:00
wodka
6bfa3ac728 update dockerfile 2019-09-11 23:38:53 +02:00
Michael Schramm
ba1c7a7683
Merge pull request #39 from wodka/features/security_20190911
security update
2019-09-11 10:31:41 +02:00
wodka
8862a71b12 security update 2019-09-11 10:28:58 +02:00
Michael Schramm
56a6565427
Merge pull request #29 from wodka/features/api_next
Implement existing API with nestjs
2019-09-11 00:09:43 +02:00
wodka
2dac042b0a add api docker build 2019-09-11 00:02:40 +02:00
wodka
10d6492949 add build logic for new ui 2019-09-10 23:36:35 +02:00
wodka
6641455ef2 update api response 2019-09-10 23:29:08 +02:00
wodka
1e0f82d257 add validation for id's 2019-09-03 22:18:11 +02:00
wodka
8b345515ab continue admin area and api 2019-09-03 17:26:57 +02:00
wodka
b4e9f2ae9b add registration view 2019-09-02 00:24:42 +02:00
wodka
0d1ebbabfb start new ui 2019-09-02 00:19:38 +02:00
wodka
32cb5ecf0c replace old authentication with bcrypt 2019-08-31 21:07:32 +02:00
wodka
aaa546bccd add registration and emails 2019-08-30 17:58:19 +02:00
wodka
70859f6871 start mailing service, and public form output 2019-08-29 19:07:09 +02:00
Leopere
3d7ed579c9
Temporarily disabled Heroku deploy until further notice. 2019-08-26 12:55:24 -04:00
wodka
93f63f7421 add first form endpoint 2019-08-04 00:48:39 +02:00
wodka
fdcc9fda3f fix api doc for auth endpoints 2019-07-30 13:03:20 +02:00
wodka
7c30574bf2 add working authentication 2019-07-30 00:46:56 +02:00
Leopere
cd40581cb4 Updated TellForm logo to OhMyForm 2019-07-28 20:39:59 -04:00
wodka
f7fab04e54 add form and user schema 2019-07-29 00:54:58 +02:00
wodka
72a539b720 initial commit for nestjs api 2019-07-29 00:54:43 +02:00
Michael Schramm
e0a0d07bd6
Merge pull request #24 from wodka/features/matomo
add matomo tracker
2019-07-24 16:43:19 +02:00
wodka
5dc388c629 add tracker 2019-07-24 16:41:29 +02:00
Leopere
cc6a5615de
Added TODO for adding "stunning examples" 2019-07-18 22:02:28 -04:00
Michael Schramm
4d42069e55
Merge pull request #10 from wodka/features/rename
get rid of tellform names
2019-07-18 12:38:58 +02:00
wodka
cc12ed924c get rid of tellform names 2019-07-18 12:35:07 +02:00
Michael Schramm
9ff5b931bc
Merge pull request #9 from wodka/features/fix_crypto
fix crypto with email verification
2019-07-18 11:32:42 +02:00
wodka
3df55e01c7 fix crypto with email verification 2019-07-18 11:31:56 +02:00
Michael Schramm
2fbd7c51d6
Merge pull request #6 from wodka/features/internal_server_error_crash
fix error if subdomain cannot be located and disable subdomain per de…
2019-07-18 00:05:55 +02:00
wodka
d96308ddcc fix error if subdomain cannot be located and disable subdomain per default 2019-07-18 00:04:00 +02:00
Leopere
ed72b85a23
Create SUBLICENSE.md AGPL 3.0
Applicable to code commits from within commits from the OhMyForm developers forward.  Discluding any PR's that were submitted to TellForm that were merged, however any changes that were required in order to merge the code into OhMyForm are licensed under AGPL 3.0.
2019-07-15 16:38:25 -04:00
Michael Schramm
6d75c011a9
Merge pull request #4 from wodka/features/security_updates
improve security
2019-07-15 21:07:08 +02:00
wodka
dab7dd6ae6 upgrade all libraries
fix form editing
2019-07-15 21:04:39 +02:00
Michael Schramm
02fe527e52
Merge pull request #1 from wodka/features/missing
Combine unfinished work and fix it
2019-07-15 16:53:07 +02:00
wodka
990898d88e remove logs and fixes for dockerfile 2019-07-15 16:30:35 +02:00
wodka
e175abb9e3 further update files 2019-07-15 15:02:21 +02:00
wodka
f7b1dd05e9 fix analytics
upgrade missing dependencies
upgrade libraries
change service naming
2019-07-15 10:15:06 +02:00
wodka
886c5dea65 remove tellform from locales 2019-07-15 00:48:39 +02:00
wodka
4997514f01 give visitor information only authenticated 2019-07-15 00:26:37 +02:00
Leopere
5d3c3ca851
More cleanup to README.md
Env vars moved to https://github.com/ohmyform/website/
Commented out Roadmap until further notice.
2019-07-14 10:54:18 -04:00
wodka
42c110beea Merge remote-tracking branch 'upstream/master' into features/missing
# Conflicts:
#	.travis.yml
2019-07-14 16:46:52 +02:00
Leopere
d59c3f5d18
Fixed discord links
Somewhere there existed a temporary membership invite link I've now revoked all links except this one.
2019-07-14 10:32:25 -04:00
Leopere
d4eae2fa9e Removed superfluous "documentation" 2019-07-12 13:31:08 -04:00
wodka
b83b431c95 Merge remote-tracking branch 'origin/fixedSignupErrors' into features/missing
# Conflicts:
#	public/modules/users/config/users.client.routes.js
#	public/modules/users/controllers/settings.client.controller.js
#	public/modules/users/views/settings/change-password.client.view.html
2019-07-11 23:36:34 +02:00
wodka
1caf34c230 Merge remote-tracking branch 'origin/fixUXBugs' into features/missing
# Conflicts:
#	.gitignore
#	app/views/form.server.view.pug
#	package-lock.json
#	package.json
#	public/form_modules/forms/base/config/i18n/french.js
#	public/modules/core/config/core.client.routes.js
#	public/modules/core/config/i18n/french.js
#	public/modules/core/controllers/header.client.controller.js
#	public/modules/forms/admin/css/admin-form.css
#	public/modules/users/controllers/authentication.client.controller.js
#	public/modules/users/services/auth.client.service.js
#	scripts/create_admin.js
#	server.js
2019-07-11 23:32:25 +02:00
wodka
423154790a Merge branch '2.20' into features/missing
# Conflicts:
#	app/models/form.server.model.js
#	package-lock.json
#	package.json
#	public/modules/forms/admin/views/admin-form.client.view.html
#	public/modules/forms/admin/views/directiveViews/form/edit-submissions-form.client.view.html
2019-07-11 23:22:49 +02:00
wodka
2276ca100f Merge branch 'master' into 2.20
# Conflicts:
#	config/env/secure.js
#	package-lock.json
#	package.json
#	public/form_modules/forms/base/css/form.css
#	public/modules/forms/admin/config/i18n/french.js
2019-07-11 23:18:55 +02:00
wodka
6735cc84d7 update package json 2019-07-11 23:16:24 +02:00
David Baldwynn
80fdcb5d4f added support for computing analytics on backend 2019-07-11 22:49:26 +02:00
David Baldwynn
b9428a5df5 fixed configure tab in form 2019-07-11 22:44:35 +02:00
David Baldwynn
b5702f72a5 fixed title overflow bug for list-forms and admin-form pages 2019-07-11 22:42:07 +02:00
David Baldwynn
2c598098a2 got docker to work in development with synced directory 2017-11-23 21:30:22 -08:00
David Baldwynn
9723d577d3 fixed jshint errors 2017-11-23 19:05:44 -08:00
David Baldwynn
222ce4d641 removed duplicate property in constants.js 2017-11-23 19:02:07 -08:00
David Baldwynn
d1c952747f Merge branch 'fixUXBugs' of github.com:tellform/tellform into fixUXBugs 2017-11-23 18:55:16 -08:00
David Baldwynn
6838fa94e3 merged with 2.20 2017-11-23 18:53:16 -08:00
David Baldwynn
d8c5b00581 bumped version in bower.json and package.json 2017-11-23 18:52:10 -08:00
David Baldwynn
5f65451661 added docker-compose file for development work 2017-11-23 18:40:37 -08:00
David Baldwynn
22ff9ca5cb
Merge pull request #271 from tellform/fixFormUpdate
Fix form update
2017-11-23 18:28:39 -08:00
David Baldwynn
f92d6f4ce3
Merge pull request #270 from tellform/updateSetupScript
Update setup script
2017-11-21 18:34:31 -08:00
David Baldwynn
8bda1a3a97 fixed hasAuthorization bug 2017-11-21 17:40:05 -08:00
David Baldwynn
784ae9011e fixed security flaw with removeSensitiveModelData 2017-11-21 17:26:52 -08:00
David Baldwynn
bd4af8aa7c
Merge branch 'fixUXBugs' into updateSetupScript 2017-11-21 16:01:33 -08:00
David Baldwynn
bc2f49b090 Merge branch 'fixUXBugs' into updateSetupScript 2017-11-21 15:59:18 -08:00
David Baldwynn
65a4e3fcba
Merge pull request #272 from tellform/addSeleniumTests
Add selenium tests
2017-11-21 15:57:16 -08:00
David Baldwynn
c561130f75 Merge branch 'fixUXBugs' into addSeleniumTests 2017-11-21 15:56:49 -08:00
David Baldwynn
6213ec227d updated field-icon test 2017-11-21 15:56:10 -08:00
David Baldwynn
1163debc33 made all server models exportable 2017-11-21 15:51:24 -08:00
David Baldwynn
4d4bb65298 disabled create_admin script in test mode 2017-11-21 15:49:58 -08:00
David Baldwynn
4f82a55227 added selenium tests 2017-11-21 15:35:06 -08:00
David Baldwynn
188c48b26c fixed password issue with setup script admin creation 2017-11-21 15:33:07 -08:00
David Baldwynn
df83c6bb93 fixed create_admin script 2017-11-21 15:30:09 -08:00
David Baldwynn
f076b482e9 moved to node v7 2017-11-21 14:06:08 -08:00
David Baldwynn
c9057b4a8a Merge branch '2.20' into addSeleniumTests 2017-11-21 13:45:28 -08:00
David Baldwynn
2ae316b6b3 Merge branch 'fixUXBugs' into fixFormUpdate 2017-11-21 13:43:38 -08:00
David Baldwynn
7fc2a19adc Merge branch '2.20' into fixFormUpdate 2017-11-21 13:42:51 -08:00
David Baldwynn
b2e027e0bf Merge branch '2.20' into updateSetupScript 2017-11-21 13:41:42 -08:00
David Baldwynn
0a4b83186a Merge branch 'fixUXBugs' of github.com:tellform/tellform into fixUXBugs 2017-11-21 13:40:47 -08:00
David Baldwynn
20cc79f218 Merge branch '2.20' into fixUXBugs 2017-11-21 13:40:27 -08:00
David Baldwynn
9ff247fde7 Merge branch 'master' into 2.20 2017-11-21 13:39:27 -08:00
David Baldwynn
0b22d06326
Merge pull request #268 from tellform/fixFormUpdate
Added tests for Form Updated and fixed bugs
2017-11-21 13:34:01 -08:00
David Baldwynn
7c6dc90592 added ASCII art for setup script 2017-11-21 13:33:25 -08:00
David Baldwynn
39125972c3 removed user and pass from mongoose options 2017-11-21 13:12:34 -08:00
David Baldwynn
31985ddda7 added setup script that runs on first run of server 2017-11-21 13:10:43 -08:00
David Baldwynn
563b8c6cd2 Modified setup script to search for admin user by email AND username 2017-11-21 12:37:57 -08:00
David Baldwynn
c97f207699 added username validation to setup script 2017-11-21 12:33:47 -08:00
David Baldwynn
e27f27f7f0 added email validation to setup script 2017-11-21 12:31:13 -08:00
David Baldwynn
acb067d626 fixed incorrect ENV variable name for redis url 2017-11-21 11:50:35 -08:00
David Baldwynn
9d2a548dca Added new form_route tests for update route 2017-11-21 11:46:57 -08:00
David Baldwynn
8c1964146f removed newOptionSchema property from FieldSchema 2017-11-21 11:37:48 -08:00
David Baldwynn
f6546deb59 removed globalId 2017-11-21 11:37:01 -08:00
David Baldwynn
fb26fbd4f9 disabled subdomains for server-side tests 2017-11-21 10:54:23 -08:00
David Baldwynn
d8268e4a9b Added test for preserving visitorData on update and removed debugger statements 2017-11-21 10:50:58 -08:00
David Baldwynn
68858ca1a3 fixed fieldType constant 2017-11-21 10:42:32 -08:00
David Baldwynn
2bd1598a01 Handled case in setup script where admin user already exists 2017-11-21 10:12:29 -08:00
David Baldwynn
a08e5c5317 removed username from .env file in setup script 2017-11-21 09:59:42 -08:00
David Baldwynn
7107bab614 added support for custom smtp server in setup script 2017-11-21 09:58:44 -08:00
David Baldwynn
2993c6e4f8 removed reCAPTCHA token from setup script 2017-11-21 07:58:11 -08:00
David Baldwynn
fca5524751 fixed admin script 2017-11-21 07:39:00 -08:00
David Baldwynn
801677bdd7 fixed angular-ui sorting bug 2017-11-21 07:24:22 -08:00
David Baldwynn
91528889e5 got tests to pass 2017-11-21 00:05:01 -08:00
David Baldwynn
94bcae9d5b added clarifying comment for formatDeviceData 2017-11-20 21:24:20 -08:00
David Baldwynn
88299a2b27 simplified form_field property deletion logic in form_submission model 2017-11-20 21:21:32 -08:00
David Baldwynn
632d2b409d removed submissions property from form model 2017-11-20 21:15:47 -08:00
David Baldwynn
e0ad256904 removed setting analytics to empty array after submissions are deleted 2017-11-20 21:11:28 -08:00
David Baldwynn
2c9e931257 removed unnessecary console.log() statement from passport helpers 2017-11-20 21:07:04 -08:00
David Baldwynn
c7d28d641c removed commented out code in send-email-notifications 2017-11-20 21:05:58 -08:00
David Baldwynn
eb658af30d got jshint to pass 2017-11-20 21:04:07 -08:00
David Baldwynn
a165cdc242 moved privateFields dict to constants.js 2017-11-20 20:58:53 -08:00
David Baldwynn
75d06037a3 removed formValidity filter from admin js 2017-11-20 20:54:38 -08:00
David Baldwynn
9c24d15a32 remove empty test case from header.client.controller.test.js 2017-11-20 20:30:41 -08:00
David Baldwynn
4d8fe4c23a removed redundant ng-true-value and ng-false-value from bs-switch 2017-11-20 20:28:56 -08:00
David Baldwynn
218d96dc2b changed loading to saveInProgress for v-busy button in configure directive 2017-11-20 20:27:14 -08:00
David Baldwynn
00d437ea1e added 'copied' label after copy button is clicked 2017-11-20 20:23:25 -08:00
David Baldwynn
f3f9232c62 removed getUserState from Auth service 2017-11-20 19:58:43 -08:00
David Baldwynn
1c4ce87084 removed deprecated properties from mock models 2017-11-20 19:56:49 -08:00
David Baldwynn
a3e0e89019 removed unused template property from configureTabs array 2017-11-20 18:57:43 -08:00
David Baldwynn
8d3e605261 fixed create new form bug 2017-11-20 18:30:16 -08:00
David Baldwynn
5ad2875378 fixed icon bug on field dragging 2017-11-19 22:42:59 -08:00
David Baldwynn
d74c04fa86 added padding for share panel 2017-11-19 22:10:07 -08:00
David Baldwynn
f897b39549 fixed disabled switch for respondent notifications bug 2017-11-19 21:37:27 -08:00
David Baldwynn
66409c15d8 fixed bugs in listForms duplication 2017-11-19 21:31:26 -08:00
David Baldwynn
9d040e9672 fixed flashing between configure tabs 2017-11-19 21:26:49 -08:00
David Baldwynn
56b7497dca set default form language to user's language 2017-11-19 21:09:57 -08:00
David Baldwynn
14243285bb fixed form create modal bug 2017-11-19 21:07:00 -08:00
David Baldwynn
2523631d96 fixed lcovMerge 2017-11-19 18:42:10 -08:00
David Baldwynn
a8ae7e2105 fixed ng-quill dependency 2017-11-19 18:36:32 -08:00
David Baldwynn
902d2b31e2 fixed test for settings controller 2017-11-19 18:32:18 -08:00
David Baldwynn
0e7ee4261a added jspdf to bower deps 2017-11-19 21:20:46 -05:00
David Baldwynn
e01b4d53e3 disabled key to option when # of options > 26 2017-11-19 21:18:31 -05:00
David Baldwynn
b6b93d15f0 fixed delete button state change bug 2017-11-19 21:15:26 -05:00
David Baldwynn
153d12a4e3 Merge branch '2.20' into fixUXBugs 2017-11-19 20:44:59 -05:00
David Baldwynn
70eec8731c fixed key to option 2017-11-19 16:53:33 -08:00
David Baldwynn
a217567b9b fixed key-to-option attributes 2017-11-19 16:10:30 -08:00
David Baldwynn
58380d23e3 fixed double scrolling bug with radio field 2017-11-19 15:38:08 -08:00
David Baldwynn
1721db9efe slowed down field scrolling 2017-11-19 15:32:15 -08:00
David Baldwynn
ff509f2b5c added updating of translation when user changes profile language 2017-11-19 15:23:38 -08:00
David Baldwynn
6a992d74ad Added tests for password, settings and verify controllers 2017-11-19 15:11:43 -08:00
David Baldwynn
cb3fe0932f refactored user settings controller 2017-11-19 13:34:39 -08:00
David Baldwynn
35ea6276a7 added tests for password controller 2017-11-19 13:31:06 -08:00
David Baldwynn
d1159087d4 added tests for authentication controller 2017-11-19 13:12:18 -08:00
David Baldwynn
0e7eb457cc got clientside tests to pass 2017-11-19 11:12:46 -08:00
David Baldwynn
c911e53609 Merge remote-tracking branch 'origin/fixUXBugs' into 2.20 2017-11-15 14:49:28 -05:00
David Baldwynn
29633b0b8b got all serverside tests to pass 2017-11-15 11:45:39 -08:00
David Baldwynn
995e94f7f5 fixed send email tests 2017-11-15 11:43:36 -08:00
David Baldwynn
ef9265576c removed complex form save logic 2017-11-15 11:23:21 -08:00
David Baldwynn
22fab93a55 added default values for analytics page 2017-11-15 11:02:28 -08:00
David Baldwynn
c21477fb02 added styling for quilljs editor 2017-11-15 10:56:10 -08:00
David Baldwynn
2a0cdcca20 added routable tabs for share form config panel 2017-11-15 10:47:57 -08:00
David Baldwynn
69f1f42816 reorganized css for admin-form 2017-11-15 10:12:01 -08:00
David Baldwynn
ab830fdc9e fixed form footer toggle in config general admin panel 2017-11-15 09:41:19 -08:00
David Baldwynn
3606c9444b fixed by zero mongodb bug for aggregation 2017-11-15 04:31:14 -08:00
David Baldwynn
965962b5e0 removed console.logs from server and client side 2017-11-15 04:19:14 -08:00
David Baldwynn
b37f4f7593 removed unused 'ispreview' attr from submit-form directive 2017-11-15 04:16:47 -08:00
David Baldwynn
d23342ac57 removed textAngular 2017-11-15 04:14:39 -08:00
David Baldwynn
522ee6f54f got 'view my form link' to open a new tab 2017-11-15 04:13:00 -08:00
David Baldwynn
6c41e6fce2 fixed signout glitching bug 2017-11-15 04:10:15 -08:00
David Baldwynn
a193d78d90 fixes language dropdown for form create modal 2017-11-15 03:57:46 -08:00
David Baldwynn
146cf9256d removed unused code from edit-form directive 2017-11-15 03:48:34 -08:00
David Baldwynn
f619b6e1ca added subject quilljs fields for self and respondent notifications 2017-11-15 03:46:40 -08:00
David Baldwynn
98d874783f got email notifications to work with quilljs 2017-11-15 02:46:54 -08:00
David Baldwynn
f3638a1d42 fixed small ux bugs 2017-11-15 00:29:45 -08:00
David Baldwynn
758eb5a786 got textAngular to semi-work with template variables 2017-11-14 23:26:47 -08:00
David Baldwynn
70801dfd96 got working for server 2017-11-14 02:45:08 -05:00
David Baldwynn
49141e6dc0
Merge pull request #262 from tellform/fixResponseCountOnFormList
Fix incorrect responses count for form list view
2017-11-13 23:02:03 -08:00
David Baldwynn
499f2c97a8
Merge pull request #261 from tellform/fixedScrollspy
Fixed ScrollSpy for fields
2017-11-13 22:18:35 -08:00
David Baldwynn
9d51608a62 added email notifications to json form data 2017-11-13 22:09:15 -08:00
David Baldwynn
56bbc68980 fixed shape dropdown for rating field 2017-11-13 21:49:16 -08:00
David Baldwynn
67a3013945 readded page loader gif for form view 2017-11-13 21:26:30 -08:00
David Baldwynn
2e5bd5beb5 fixed form validation bug with radio 2017-11-13 21:25:05 -08:00
David Baldwynn
f5e32e9548 got validity working for rating field 2017-11-13 21:07:27 -08:00
David Baldwynn
04001244d1 fixed prev and next field buttons 2017-11-13 18:22:15 -08:00
David Baldwynn
6848b740b3 moved from on('scroll' to using duScroll for tracking activeField 2017-11-13 18:14:52 -08:00
David Baldwynn
15a508cca7 change name of form assets from form-vendor.min.js to vendor.min.js 2017-11-13 16:56:24 -08:00
David Baldwynn
ed6d917e6b fixed incorrect responses count for form list view 2017-11-13 16:52:42 -08:00
David Baldwynn
09d430a7e1 added cosmetic changes to analytics panel 2017-11-13 15:21:29 -08:00
David Baldwynn
1f9a0ed1e4
Merge pull request #254 from tellform/fixSlowAnalyticsBug
Fix Slow Form Analytics Bug
2017-11-13 15:00:18 -08:00
David Baldwynn
060cd5980f Merge branch 'fixSlowAnalyticsBug' of github.com:tellform/tellform into fixSlowAnalyticsBug 2017-11-13 14:52:52 -08:00
David Baldwynn
28fe150149 Merge branch '2.20' into fixSlowAnalyticsBug 2017-11-13 14:51:53 -08:00
David Baldwynn
421d3b53b8
Merge pull request #260 from tellform/improvedAddOptionUX
Improved UX of Adding an Option
2017-11-11 21:59:47 -08:00
David Baldwynn
f786f24f7c
Merge branch '2.20' into fixSlowAnalyticsBug 2017-11-11 21:50:43 -08:00
David Baldwynn
62953a6ffc changed dropdown add option to list of strings that are seperated by lines 2017-11-11 21:44:53 -08:00
David Baldwynn
4f2682554d merged with master 2017-11-11 20:05:06 -08:00
David Baldwynn
5516e76e19 Merge branch 'fixSlowAnalyticsBug' of github.com:tellform/tellform into fixSlowAnalyticsBug 2017-11-06 15:04:42 -08:00
David Baldwynn
ffb2a23cc6 fixed duplicate npm dep in prod and dev 2017-11-06 15:03:52 -08:00
David Baldwynn
04e71be14a
Merge branch '2.20' into fixSlowAnalyticsBug 2017-11-06 14:47:01 -08:00
David Baldwynn
4fe1e8e848
Merge pull request #230 from tellform/emailNotifications
Add Email Notifications
2017-11-06 14:45:03 -08:00
David Baldwynn
68235423c8
Merge branch 'master' into fixSlowAnalyticsBug 2017-11-06 14:26:04 -08:00
David Baldwynn
a40ecad008 added aggregating for form analytics 2017-11-05 19:40:28 -08:00
David Baldwynn
ef23ceb07d Merge branch '2.20' into fixedSignupErrors 2017-11-05 16:26:35 -08:00
David Baldwynn
a77a59c3ba Merge branch '2.20' into emailNotifications 2017-11-05 16:26:09 -08:00
David Baldwynn
43ce60b985 Merge branch 'master' into 2.20 2017-11-05 16:24:19 -08:00
David Baldwynn
8521ede885 Merge branch 'master' into 2.20 2017-11-05 15:43:07 -08:00
David Baldwynn
4e18e70b18 fix html bug with textAngular attribute in config admin panel 2017-11-05 15:37:27 -08:00
David Baldwynn
15d023c133 fixed editable variables in textAngular editor 2017-11-02 14:29:02 -07:00
David Baldwynn
72b3e31819 fixed form expected object but got array error 2017-11-02 09:57:05 -07:00
David Baldwynn
22875abab5 fixed signin validation bug 2017-11-02 09:54:14 -07:00
David Baldwynn
b2f2599fa5 fix css bug for dropdown field in admin view 2017-11-02 09:18:41 -07:00
David Baldwynn
692e4e43f5 updated styling of config panel 2017-11-01 21:15:27 -07:00
David Baldwynn
4d94ed50ee fixed config admin panel field input padding 2017-11-01 19:20:58 -07:00
David Baldwynn
5120a2f287 fixed dropdowns for configure admin panel 2017-11-01 19:13:53 -07:00
David Baldwynn
eaea95078c fix url tab bug with configure tabs 2017-11-01 17:56:03 -07:00
David Baldwynn
522dd85f89 got respondent-notifications to work 2017-11-01 17:13:55 -07:00
David Baldwynn
33368f016c got basic email templating to work 2017-11-01 16:44:05 -07:00
David Baldwynn
9faa514ce9 added backend logic for email notifications and got it to pass server tests 2017-11-01 15:13:31 -07:00
David Baldwynn
a7c0cdc056 got tests for send-email-notification template logic to pass 2017-11-01 13:38:42 -07:00
David Baldwynn
cc62f6db3a Added test for parseTemplate for send-email-notifications 2017-11-01 12:53:52 -07:00
David Baldwynn
ddd4c2b22c Merge branch '2.20' into emailNotifications 2017-11-01 12:46:15 -07:00
David Baldwynn
bbb3137372 got all tests to pass 2017-11-01 12:17:35 -07:00
David Baldwynn
f291eda21e fixed add variable dropdown only showing email fields on subject input for config 2017-11-01 11:27:07 -07:00
David Baldwynn
983d2ce20b fixed bug with detecting whether form has emails for config panel 2017-11-01 11:25:24 -07:00
David Baldwynn
19880b0d07 add support when no email field is detected for self notifications 2017-11-01 11:15:58 -07:00
David Baldwynn
b5b8df90af got subject field to support variables 2017-11-01 11:06:08 -07:00
David Baldwynn
5a6b957495 added variable support in editor 2017-10-31 16:05:50 -07:00
David Baldwynn
aa8efe8508 got static url for configure tabs to work 2017-10-31 12:32:23 -07:00
David Baldwynn
12e1fd0947 got static url for admin tabs to work 2017-10-31 12:28:01 -07:00
David Baldwynn
798d28d92d added routable tabs for configure panel 2017-10-31 10:36:01 -07:00
David Baldwynn
86d8b88231 got url-linked tabs to work for admin panel 2017-10-31 10:21:10 -07:00
David Baldwynn
8f55df1393
Merge pull request #246 from tellform/increaseTestCoverage
Increased Test Coverage
2017-10-30 12:03:53 -07:00
David Baldwynn
22cf37424f added share and design form directives 2017-10-30 12:00:30 -07:00
David Baldwynn
5451475636 added translations for email notification panel 2017-10-30 11:41:34 -07:00
David Baldwynn
3652b41fc8 Merge branch 'master' into emailNotifications 2017-10-30 11:22:07 -07:00
David Baldwynn
56cf745270 added check to not load .env on travis run 2017-10-30 11:12:44 -07:00
David Baldwynn
c871d320f1 remove swig 2017-10-30 11:01:13 -07:00
David Baldwynn
814466cd1d switched to npm for travis 2017-10-30 10:47:54 -07:00
David Baldwynn
56fda0d312 switched travis scripts to yarn 2017-10-30 10:35:05 -07:00
David Baldwynn
5805c9b529 fixed travis test command 2017-10-30 09:54:03 -07:00
David Baldwynn
fc4d9a12f0 fixed lcovMerge for travis 2017-10-30 09:40:09 -07:00
David Baldwynn
26716e256d migrating to yarn 2017-10-29 21:54:30 -07:00
David Baldwynn
8d9e66548b remove references to i18n-lint 2017-10-29 21:37:22 -07:00
David Baldwynn
8ea1f63c96 fixed travis settings 2017-10-29 21:32:02 -07:00
David Baldwynn
b2b09171f3 fixed api tests 2017-10-29 21:22:43 -07:00
David Baldwynn
d0d273a718 removed secure configuration 2017-10-29 20:23:36 -07:00
David Baldwynn
774d256507 removed unused code from user.server.model.js 2017-10-29 20:14:40 -07:00
David Baldwynn
9a0a301c3d got rid of unnessecary user middleware 2017-10-29 20:11:58 -07:00
David Baldwynn
3e3e7ccfe6 removed hasRole and hasAdminRole from passport_helpers 2017-10-29 19:02:01 -07:00
David Baldwynn
e1db98ec95 added tests for api routes 2017-10-29 18:17:20 -07:00
David Baldwynn
99a6cb5009 fixed hasAuthorization bug for API 2017-10-29 17:46:08 -07:00
David Baldwynn
15eb66e455 added test for /users/me routes 2017-10-29 17:34:27 -07:00
David Baldwynn
aed7c6c43b added test for changing password route 2017-10-29 17:20:34 -07:00
David Baldwynn
9fffdf5328 added tests to cover reset password routes 2017-10-29 15:47:01 -07:00
David Baldwynn
3dbfe2f88d added helper function to remove sensitive data from forms/users 2017-10-29 14:15:13 -07:00
David Baldwynn
aa554c8210 added coveralls support 2017-10-29 13:02:41 -07:00
David Baldwynn
759a4efdd1 got all tests to pass 2017-10-29 12:44:12 -07:00
David Baldwynn
88fbf9e51f got user tests to pass 2017-10-29 12:20:49 -07:00
David Baldwynn
6fbfa35473 fixed on-finish-render directive tests 2017-10-29 12:17:54 -07:00
David Baldwynn
f427092144 fixed field directive tests 2017-10-29 12:16:34 -07:00
David Baldwynn
ebcc0c63cd fixed field-icon tests 2017-10-29 12:13:22 -07:00
David Baldwynn
7d65f0161b removed duplicate translation keyword for form admin panel 2017-10-29 12:07:42 -07:00
David Baldwynn
538030c551 got edit-form-directive tests to pass 2017-10-29 12:01:30 -07:00
David Baldwynn
76d6d54cd5 got edit-submissions-form directive tests to pass 2017-10-29 11:54:40 -07:00
David Baldwynn
eaef327b37 fixed reloading bug for settings pages 2017-10-28 01:19:09 -07:00
David Baldwynn
8de93d4413 Merge branch 'master' into emailNotifications 2017-10-26 08:37:03 -07:00
David Baldwynn
00e1d43858 got basic emailNotifications to work 2017-10-05 21:53:13 -07:00
David Baldwynn
ae946bd619 Merge branch 'master' into emailNotifications 2017-10-05 21:27:57 -07:00
David Baldwynn
346537fea5 added emailNotifications to form model 2017-10-05 21:16:10 -07:00
280 changed files with 1603 additions and 34250 deletions

View File

@ -1,104 +0,0 @@
{
"projectName": "TellForm",
"projectOwner": "tellform",
"files": [
"README.md"
],
"imageSize": 100,
"commit": true,
"contributors": [
{
"login": "whitef0x0",
"name": "David Baldwynn",
"avatar_url": "https://avatars2.githubusercontent.com/u/1160417?v=3",
"profile": "http://baldwynn.me",
"contributions": [
"code",
"tool",
"infra",
"doc",
"example",
"design",
"fundingFinding",
"review",
"test"
]
},
{
"login": "lalop",
"name": "Samuel Laulhau",
"avatar_url": "https://avatars2.githubusercontent.com/u/313117?v=3",
"profile": "https://samuellaulhau.fr",
"contributions": [
"code",
"translation"
]
},
{
"login": "arunpattnaik",
"name": "Arun Pattnaik",
"avatar_url": "https://avatars0.githubusercontent.com/u/313507?v=3",
"profile": "http://arun.co",
"contributions": [
"design"
]
},
{
"login": "toanalien",
"name": "Thiên Toán",
"avatar_url": "https://avatars0.githubusercontent.com/u/5405744?v=3",
"profile": "https://toanalien.com",
"contributions": [
"bug",
"code",
"doc"
]
},
{
"login": "AdrianP-",
"name": "Adrian Portabales",
"avatar_url": "https://avatars2.githubusercontent.com/u/8615608?v=3",
"profile": "https://github.com/AdrianP-",
"contributions": [
"bug",
"code"
]
},
{
"login": "spekulatius",
"name": "Peter Thaleikis",
"avatar_url": "https://avatars3.githubusercontent.com/u/8433587?v=3",
"profile": "https://github.com/spekulatius",
"contributions": [
"doc"
]
},
{
"login": "mickaelandrieu",
"name": "Mickaël Andrieu",
"avatar_url": "https://avatars1.githubusercontent.com/u/1247388?v=3",
"profile": "http://www.mickael-andrieu.com",
"contributions": [
"doc"
]
},
{
"login": "ahmad-luqman",
"name": "Ahmad Luqman",
"avatar_url": "https://avatars1.githubusercontent.com/u/1522464?v=3",
"profile": "https://github.com/ahmad-luqman",
"contributions": [
"doc"
]
},
{
"login": "PeterDaveHello",
"name": "Peter Dave Hello",
"avatar_url": "https://avatars0.githubusercontent.com/u/3691490?v=3",
"profile": "https://www.peterdavehello.org/",
"contributions": [
"doc"
]
}
]
}

View File

@ -1,5 +0,0 @@
{
"directory": "public/lib",
"analytics": false,
"registry": "https://registry.bower.io"
}

View File

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

View File

@ -1,8 +1,29 @@
.git
.github
.idea
.vagrant
coverage
design
e2e_coverage
Vagrantfile
data
docker
node_modules
selenium
examples
.all-contributorsrc
.csslintrc
.editorconfig
.env.example
.gitignore
.jshintrc
.slugignore
*.md
conf.json
docker-compose.yml
Dockerfile
Procfile
protractor.conf.js
pg_data
ui/.next
ui/node_modules
api/node_modules
api/pg_data
api/data
api/maria_data
api/dist

View File

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

View File

@ -1,125 +0,0 @@
# TellForm Configuration File
## TODO: Have commented out examples that would work.
###################################
# Common configuration variables
###################################
# Set this to the path where Mailu data and configuration is stored
# Mac users: Change to a Docker accessible folder
ROOT=/opt/tellform_data
# Set to what environment you will be running TellForm in (production or development)
NODE_ENV=development
# Set to a randomly generated 16 bytes string
SECRET_KEY=ChangeMeChangeMe
# URI of Mongo database that TellForm will connect to
#DO NOT CHANGE
MONGODB_URI=mongodb://mongo/tellform
# URL Redis server that TellForm will connect to
#DO NOT CHANGE
REDIS_URL=redis://redis:6379
# Port that the TellForm Node app will listen on
PORT=5000
# Domain that TellForm's admin panel will be hosted at
BASE_URL=tellform.dev
# Port that SocketIO server (for analytics) will listen on
SOCKET_PORT=20523
#Choose what kind of TLS you want.
#Can be either 'cert' (supply your certificates in ./cert/), 'notls' (no https at all) or 'letsencrypt' that autoconfigures your instance with letsencrypt
TLS_FLAVOR=notls
###################################
# Optional features
###################################
# Set this to enable coveralls.io support
COVERALLS_REPO_TOKEN=
# Disable signups for your TellForm instance
SIGNUP_DISABLED=FALSE
# Disable per-user custom subdomains
SUBDOMAINS_DISABLED=FALSE
# Url that subdomains will be hosted at (has to have domain name as ADMIN_URL)
# Only used when SUBDOMAINS_DISABLED=FALSE
SUBDOMAIN_URL=*.tellform.dev
# Enable running TellForm in pm2's 'cluster' mode
ENABLE_CLUSTER_MODE=FALSE
###################################
# Mail settings
# IMPORTANT: These settings need to be set
# to be set in order for your instance to work
###################################
# Set this to set the username credential of your SMTP service
MAILER_EMAIL_ID=
# Set this to set the password credential of your SMTP service
MAILER_PASSWORD=
# Set this to set the email address that all email should be sent from for signup/verification emails
MAILER_FROM=
# Set this to any services from https://nodemailer.com/smtp/well-known/ to use a 'well-known' email provider
MAILER_SERVICE_PROVIDER=
# Set these if you are not using a 'MAILER_SERVICE_PROVIDER' and want to specify your SMTP server's address and port
MAILER_SMTP_HOST=
MAILER_SMTP_PORT=
# Set this if you are using a custom SMTP server that supports SSL
MAILER_SMTP_SECURE
###################################
# Automatic Admin Creation Settings
###################################
# Set this to "TRUE" if you wish to automatically create an admin user on startup
CREATE_ADMIN=FALSE
# Set this to set the email used by your default admin account
ADMIN_EMAIL=admin@admin.com
# Set this to set the username of your default admin acconut
ADMIN_USERNAME=root
# Set this to set the password of your default admin account
ADMIN_PASSWORD=root
###################################
# Advanced settings
###################################
# Set this to server your websockets server on a seperate URL
SOCKETS_URL=
# Set this to change the port that TellForm will listen on
PORT=5000
# Set this to your Google Analytics ID to enable tracking with GA
GOOGLE_ANALYTICS_ID=
# Set this to your Sentry.io DSN code to enable front-end JS error tracking with Sentry.io
RAVEN_DSN
# Set this to set the 'name' meta property in the HTML <head>
APP_NAME=
# Set this to set the 'keywords' meta property in the HTML <head>
APP_KEYWORDS=
# Set this to set the 'description' meta property in the HTML head
APP_DESC=

12
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,12 @@
# These are supported funding model platforms
# github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
# patreon: # Replace with a single Patreon username
open_collective: ohmyform-sustainability
# ko_fi: # Replace with a single Ko-fi username
# tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
# community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
# liberapay: Leopere
# issuehunt: # Replace with a single IssueHunt username
# otechie: # Replace with a single Otechie username
# custom: https://patron.ohmyform.com

45
.github/workflows/docker-image.yml vendored Normal file
View File

@ -0,0 +1,45 @@
name: Docker Image CI
on:
push:
branches:
- master
release:
types:
- published
jobs:
build:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
steps:
- name: Check out the repo
uses: actions/checkout@v2
with:
submodules: true
- name: Log in to Docker Hub
uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
with:
images: ohmyform/ohmyform
tags: |
type=raw,value=latest
type=semver,pattern={{major}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{version}}
- name: Build and push Docker image
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

5
.gitignore vendored
View File

@ -22,7 +22,7 @@ Oscar_Credentials.*
npm-debug.log
node_modules/
public/lib/
public/dist
public/dist/
app/tests/coverage/
.bower-*/
.idea/
@ -61,6 +61,9 @@ local.properties
.c9/
data/
mongod
pg_data
maria
*.sqlite
# General
# =======

7
.gitmodules vendored Normal file
View File

@ -0,0 +1,7 @@
[submodule "ui"]
path = ui
url = https://github.com/ohmyform/ui
[submodule "api"]
path = api
url = https://github.com/ohmyform/api

View File

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

View File

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

228
CHANGELOG.md Normal file
View File

@ -0,0 +1,228 @@
# Change Log
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
<!--
Template for next version
## [Unreleased]
### Added
### Changed
### Fixed
### Security
-->
## [Unreleased]
### Added
### Changed
### Fixed
* Fixed typo https://github.com/ohmyform/ohmyform/pull/185
* node prune location (https://github.com/ohmyform/ohmyform/issues/184)
### Security
## [1.0.3] - 2022-03-27
### Updates
* https://github.com/ohmyform/api/releases/tag/1.0.3
* https://github.com/ohmyform/ui/releases/tag/1.0.3
### Added
* minimal configuration example for caddy server (https://github.com/ohmyform/ohmyform/pull/167)
## [1.0.2] - 2022-03-13
### Updates
* https://github.com/ohmyform/api/releases/tag/1.0.2
* https://github.com/ohmyform/ui/releases/tag/1.0.2
### Changed
- docker restart policy (https://github.com/ohmyform/ohmyform/issues/164)
## [1.0.1] - 2022-03-01
### Updates
* https://github.com/ohmyform/api/releases/tag/1.0.1
* https://github.com/ohmyform/ui/releases/tag/1.0.1
## [1.0.0] - 2022-02-28
### Updates
* https://github.com/ohmyform/api/releases/tag/1.0.0
* https://github.com/ohmyform/ui/releases/tag/1.0.0
### Changed
- switched to supervisord based combined container
- upgrade to node 16
### Fixed
- heroku deployments
- fix problem with node-prune on production build
- variable names in examples (https://github.com/ohmyform/ohmyform/issues/134)
- error if /run/nginx already exists (https://github.com/ohmyform/ohmyform/pull/148)
- fix combine images
## [0.9.9] - 2021-02-14
### Added
- Submission export
- Lokalize reference
- more languages
### Changed
- updated french translations by @Vercety87
- upgrade to node 14 (https://github.com/ohmyform/ohmyform/issues/99)
### Fixed
- missing dependency to @apollo/client
- footer rendering during authentication check
### Security
- authentication check for profile page
## [0.9.8] - 2020-09-02
### Changed
- improved german translation (https://github.com/ohmyform/ui/pull/28)
### Fixed
- colors for landing page buttons
- menu selection type
### Security
- upgraded dependencies
## [0.9.6] - 2020-07-17
### Added
- slug for fields to be able to set value by url parameter
- form submission hokks
- default index.html for api without bundled ui
- slug for form fields can now be saved
- submission webhooks with ability to customize json payload
```
{
form: ID
submission: ID
created: DateTime
lastModified: DateTime
fields: [
{
field: ID
slug: String
value: any
}
]
}
```
### Changed
- minify containers to reduce layer size
### Fixed
- bug in settings resolver with nullable fields
- bug if user was deleted and form still exists
- do not show login note if it is not set
- typo in dropdown options https://github.com/ohmyform/ohmyform/issues/96
- query parms are not parsed https://github.com/ohmyform/ui/pull/27 https://github.com/ohmyform/ohmyform/issues/100
- errors because of missing user reference (https://github.com/ohmyform/ohmyform/issues/102)
### Security
- container now runs as non root user
## [0.9.5] - 2020-06-10
### Added
- `DEFAULT_ROLE` -> `admin` | `superuser` | `user` - with `user` being the default, making it possible that new users can create their own forms after creating
- `LOGIN_NOTE` -> markdown for Login Page, to show info text on login page
- `HIDE_CONTRIB` -> ability to hide contribution banner
- mobile improvements for lists and home page
- markdown support for page paragraphs and field description
- hideable omf badge
- login notes
- username in admin toolbar
- github stars in multiple places
### Changed
- verified spanish translations https://github.com/ohmyform/ui/pull/23
### Fixed
- di on setting resolver, prevented signup settings to be visible in ui
- return admin of form also for admins
- yes / no field fixed on admin and user view
- prev property error on div
- rating field default on admin
- number field defaults
- translations for field validation
- number validation
- side menu only shows accessible entries
## [0.9.4] - 2020-06-09
### Added
- Fetch Server Settings to determine if signup is available
- `SPA` env variable to have static page with loading spinner before redirect
- `de`, `fr`, `es`, `it`, `cn` base folders for translations
- finish translating `de` and `en`
- add `yarn translation:sort` to order translations (to ensure the same order
when we add / change translations)
- add `yarn translation:missing <lang>` to print a list of missing translations
for the given language (this takes `en` as a baseline)
- travis for tests
- eslint with prettier
- `SIGNUP_DISABLED=true` to prevent users from signing up
### Changed
- `export` uses now spa mode for initial loading screen
### Fixed
- [OMF#93](https://github.com/ohmyform/ohmyform/issues/93) dropdown options are not saved
- redirect attempts on static export
- startup error with invalid create admin config
## [0.9.3] - 2020-06-04
### Added
- nginx example
- load balanced example
- minimal example
### Fixed
- docker-compose mongo data dir to persist data

View File

@ -34,7 +34,7 @@ This Code of Conduct applies both within project spaces and in public spaces whe
##Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at team@tellform.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at team@ohmyform.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.

40
CONTRIBUTING.md Executable file
View File

@ -0,0 +1,40 @@
# Contributing
## Table of Contents
<!-- TOC depthFrom:1 depthTo:6 withLinks:1 updateOnSave:1 orderedList:0 -->
- [Contributing](#contributing)
- [Table of Contents](#table-of-contents)
- [Report issues](#report-issues)
- [Write code](#write-code)
- [Clone the project](#clone-the-project)
- [Run the server locally](#run-the-server-locally)
<!-- /TOC -->
## Translating
You can help us [translate](https://app.lokalise.com/public/379418475ede5d5c6937b0.31012044/) ever part of OhMyForm, when you want to add a new language please write either in discord or create a new issue.
## Report issues
You can [create issues](https://github.com/ohmyform/ohmyform/issues/new) to report bug or request feature.
## Write code
Here is how to setup your development environment:
### Clone the project
```
$ git clone https://github.com/ohmyform/ohmyform
```
### Run the server locally
```
$ docker-compose up
```
You should have at this point have a working instance to play with at <http://localhost:5000> within moments.

View File

@ -1,77 +1,73 @@
FROM node:10-alpine
MAINTAINER OhMyForm <admin@ohmyform.com>
## Build UI
FROM node:16-alpine as ui
# Install some needed packages
RUN apk add --no-cache \
git \
&& rm -rf /tmp/*
WORKDIR /usr/src/ui
## 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
RUN apk --update --no-cache add curl bash g++ make libpng-dev
WORKDIR /opt/app
RUN mkdir -p /opt/app/public/lib
# install node-prune (https://github.com/tj/node-prune)
RUN curl -sf https://gobinaries.com/tj/node-prune | sh
## 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 ui/ .
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
RUN yarn install --frozen-lockfile
RUN yarn build
## 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=
# remove development dependencies
RUN npm prune --production
ENV CREATE_ADMIN=FALSE
ENV ADMIN_EMAIL=admin@ohmyform.com
ENV ADMIN_USERNAME=root
ENV ADMIN_PASSWORD=root
# run node prune
# there is some problem running node prune that then prevents the frontend to load (just start with /form/1 and it will crash)
#RUN /usr/local/bin/node-prune
ENV APP_NAME=OhMyForm
ENV APP_KEYWORDS=
ENV APP_DESC=
## Build API
FROM node:16-alpine as api
LABEL maintainer="OhMyForm <admin@ohmyform.com>"
# optional ENV settings
ENV COVERALLS_REPO_TOKEN=
ENV GOOGLE_ANALYTICS_ID=
ENV RAVEN_DSN=
WORKDIR /usr/src/api
## 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.
RUN apk --update --no-cache add curl bash g++ make libpng-dev
## 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"]
# install node-prune (https://github.com/tj/node-prune)
RUN curl -sf https://gobinaries.com/tj/node-prune | sh
COPY api/ .
RUN touch /usr/src/api/src/schema.gql && chown 9999:9999 /usr/src/api/src/schema.gql
RUN yarn install --frozen-lockfile
RUN yarn build
# remove development dependencies
RUN npm prune --production
# run node prune
RUN /usr/local/bin/node-prune
## Production Image.
FROM node:16-alpine
RUN apk --update add supervisor nginx && rm -rf /var/cache/apk/*
WORKDIR /usr/src
COPY --from=api /usr/src/api /usr/src/api
COPY --from=ui /usr/src/ui /usr/src/ui
RUN addgroup --gid 9999 ohmyform && adduser -D --uid 9999 -G ohmyform ohmyform
ENV SECRET_KEY=ChangeMe \
CREATE_ADMIN=FALSE \
ADMIN_EMAIL=admin@ohmyform.com \
ADMIN_USERNAME=root \
ADMIN_PASSWORD=root \
NODE_ENV=production
EXPOSE 3000
RUN mkdir -p /run/nginx/
RUN touch /usr/src/supervisord.log && chmod 777 /usr/src/supervisord.log
COPY supervisord.conf /etc/supervisord.conf
COPY nginx.conf /etc/nginx/nginx.conf
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"]
# CMD [ "yarn", "start:prod" ]

View File

@ -1,21 +1,661 @@
## License
(The MIT License)
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
Preamble
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.

View File

@ -1 +1 @@
web: ./node_modules/.bin/forever -m 5 server.js
web: node api/dist/main

128
README.md
View File

@ -1,31 +1,30 @@
# OhMyForm 0.2.1
![OhMyForm](public/logo.png)
# OhMyForm
<!-- TODO: Code Shelter maybe. -->
<!-- [![Code Shelter](https://www.codeshelter.co/static/badges/badge-flat.svg)](https://www.codeshelter.co/) -->
<!-- TODO: Travis CI maybe. -->
<!-- [![Build Status](https://travis-ci.org/tellform/tellform.svg?branch=master)](https://travis-ci.org/tellform/tellform) -->
![Project Status](https://img.shields.io/badge/status-0.2.1-green.svg)
<!-- TODO: Codeacy maybe. -->
<!-- [![Codacy Badge](https://api.codacy.com/project/badge/Grade/3491e86eb7194308b8fc80711d736ede)](https://www.codacy.com/app/david-baldwin/tellform?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=tellform/tellform&amp;utm_campaign=Badge_Grade) -->
<!--
Moving over to Discord so that I can manage things without hassle.
[![Gitter](https://badges.gitter.im/tellform/Lobby.svg)](https://gitter.im/tellform/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
-->
![Discord](https://img.shields.io/discord/595773457862492190.svg?label=Discord%20Chat)
> An *open source alternative to TypeForm* that can create [stunning mobile-ready forms](https://ohmyform.com/examples) , surveys and questionnaires.
![Project Status](https://badgen.net/github/checks/ohmyform/ohmyform)
![Latest Release](https://badgen.net/github/tag/ohmyform/ohmyform)
[![Docker Pulls](https://badgen.net/docker/pulls/ohmyform/ohmyform)](https://hub.docker.com/r/ohmyform/ohmyform)
[![Lokalise](https://badgen.net/badge/Lokalise/EN/green?icon=libraries)](https://app.lokalise.com/public/379418475ede5d5c6937b0.31012044/)
![Last Commit](https://badgen.net/github/last-commit/ohmyform/ohmyform)
[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/ohmyform/ohmyform/tree/production)
[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/ohmyform/ohmyform/tree/master)
[Demo](https://demo.ohmyform.org/) Username and password are just `demo`. We will reset the demo instance at least once for every new release and possibly more often so don't rely on it for sending actual forms expect no notice for resets.
> An *open source alternative to TypeForm* that can create stunning mobile-ready forms, surveys and questionnaires.
[![Discord](https://img.shields.io/discord/595773457862492190.svg?label=Discord%20Chat)](https://discord.gg/MJqAuAZ)
[![Financial Contributors on Open Collective](https://opencollective.com/ohmyform-sustainability/all/badge.svg?label=financial+contributors)](https://opencollective.com/ohmyform-sustainability)
## Table of Contents
<!-- TOC depthFrom:1 depthTo:6 withLinks:1 updateOnSave:1 orderedList:0 -->
- [OhMyForm 0.2.1](#ohmyform-021)
- [OhMyForm](#ohmyform-091)
- [Table of Contents](#table-of-contents)
- [Features](#features)
- [Currently following features are implemented:](#currently-following-features-are-implemented)
- [On the Roadmap for v1.0.0](#on-the-roadmap-for-v100)
- [On the Roadmap](#on-the-roadmap)
- [How to Contribute](#how-to-contribute)
- [Quickstart](#quickstart)
- [Where to get help](#where-to-get-help)
@ -34,74 +33,81 @@ Moving over to Discord so that I can manage things without hassle.
## Features
### Currently following features are implemented:
- Multi-Language Support (Semi implemented)
- Multi-Language Support
- 11 possible question types
- Editable start and end pages
- Export Submissions to XLS, JSON or CSV
- Native Analytics and Google Analytics Support
- Custom Subdomains for each User
- Embeddable Forms
- Forms as a Service API
- Customizable Notifications on Form Submission
- Web Hooks on Form Submission
- Deployable with Heroku and DockerHub
- PostgreSQL and sqlite
### On the Roadmap for v1.0.0
<!-- TODO: Determine roadmap for OhMyForm if it is to be different from OhMyForm's roadmap. -->
<!-- ### On the Roadmap (Tentative pending [refactor](https://github.com/ohmyform/ohmyform/pull/1)) -->
### On the Roadmap
- Custom Subdomains for each User
- Implement encryption for all form data
- Add Typeform API integration
- Add plugin/3rd party integration support (ala Slack)
- Add plugin/3rd party integration support (aka Slack)
- Create wiki for easy installation and setup
- Add Stripe/Payment Form field
- Add Custom Background and Dropdown Field Images
- Add File Upload Form Field
- Deployable with Heroku and DockerHub
<!-- TODO: add a CONTRIBUTING.md.
<!-- TODO: add a CONTRIBUTING.md. -->
## How to Contribute
Please checkout our CONTRIBUTING.md on ways to contribute to TellForm. -->
Please checkout our [contributing guide](CONTRIBUTING.md) on ways to contribute to OhMyForm.
## Quickstart
Follow documentation hosted on [OhMyForm.com](http://ohmyform.com/docs/install/) it will be the main and hopefully only location to obtain the up to date documentation.
<!-- TODO: Reconsider location of the following. -->
<!-- ## Configuration
If you pull the repository do not forget to execute: `git submodule update --init`
TellForm's configuration is done with environment variables. To set an option for TellForm, open/create your .env file and set add `ENV_VAR=somevalue` to set the ENV_VAR variable to the value `somevalue`.
### Some technical Insights
| Property | Valid Values | Default Value | Description | Required? |
|-------------------------|--------------------------------------------------------|----------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------|--------------------------------------------|
| NODE_ENV | "development", "production", "test" or "secure" | development | Set which version of the app you want to run (either secure/SSL, dev, prod or test) | No |
| SESSION_SECRET | Any string | CHANGE_ME_PLEASE | Value used to compute session hash | No |
| BASE_URL | A valid URL | localhost:3000 | URL where the admin panel will live | Yes |
| MONGODB_URI | A valid MongoDB URI | localhost/mean | URI of the MONGODB server/db that your server will use | Yes |
| REDIS_URL | A valid Redis URI | redis://127.0.0.1:6379 | URI of the Redis instance that your server will use | Only if ENABLE_CLUSTER_MODE=TRUE |
| SOCKET_PORT | A valid port number from 0 - 65535 | 20523 | Port that your SocketIO server will bind to | No |
| SOCKET_URL | A valid URL | ws.tellform.com | Url that your SocketIO server will bind to | No |
| SIGNUP_DISABLED | "TRUE" or "FALSE" | FALSE | Set this flag to disable signups. | No |
| SUBDOMAINS_DISABLED | "TRUE" or "FALSE" | FALSE | Set this flag to disable subdomains. (Useful if hosting behind an uncontrolled domain or without a wildcard SSL cert) | No |
| ENABLE_CLUSTER_MODE | "TRUE" or "FALSE" | FALSE | Disable support for running TellForm with pm2's cluster mode. Disabling this allows you to not run a Redis instance. | No |
| MAILER_EMAIL_ID | A string | N/A | Username credential for the SMTP MAIL service used to send signup/verification/lost password emails. | Yes |
| MAILER_PASSWORD | A string | | Password credential for the SMTP MAIL service used to send signup/verification/lost password emails. | Yes |
| MAILER_FROM | A valid email | noreply@tellform.com | Email address that all mail should be sent from. | No |
| MAILER_SERVICE_PROVIDER | A service from [https://nodemailer.com/smtp/well-known/](https://nodemailer.com/smtp/well-known/) | | A "well-known" email service that is supported by nodemail. If MAILER_SMTP_HOST is enabled, this is ignored. | Only if MAILER_SMTP_HOST is not set |
| MAILER_SMTP_HOST | A valid URL | | URL to the SMTP server of your choice | Only if MAILER_SERVICE_PROVIDER is not set |
| MAILER_SMTP_PORT | A valid port number from 0 - 65535 | | Port of the SMTP server of your choice. | Only if MAILER_SMTP_HOST is set |
| MAILER_SMTP_SECURE | "TRUE" or "FALSE" | FALSE | Boolean that enables/disables SSL support for your SMTP client. | Only if MAILER_SMTP_HOST is set |
| CREATE_ADMIN | "TRUE" or "FALSE" | FALSE | Setting this variable will create a admin user on startup with credentials as specified below | No |
| ADMIN_EMAIL | A valid email | admin@admin.com | Email of generated admin user | No |
| ADMIN_USERNAME | A string | root | Username of generated admin user | No |
| ADMIN_PASSWORD | A string | root | Password of generated admin user | No |
| APP_NAME | A string | TellForm | Sets the `<title>` property of your webapp. | No |
| APP_DESC | A string | Opensource form builder alternative to TypeForm | Sets the,property of your webapp. | No |
| APP_KEYWORDS | A comma-seperated list of phrases/words | typeform, pdfs, forms, opensource, formbuilder, google forms, nodejs | Sets the value of the <meta> description attribute. | No |
| RAVEN_DSN | A valid Sentry.io DSN | N/A | Set this to your Sentry.io Public DSN to enable remote logging | No |
| GOOGLE_ANALYTICS_ID | A valid Google Analytics ID | N/A | Set this to your GA id to enable GA tracking on your TellForm instance | No | | -->
[API](https://github.com/ohmyform/api/tree/master/doc)
[UI](https://github.com/ohmyform/ui/tree/master/doc)
## Where to get help
![Discord](https://img.shields.io/discord/595773457862492190.svg?label=Discord%20Chat)
[Discord Chat](https://discord.gg/Y2TTePM)
[![Discord](https://img.shields.io/discord/595773457862492190.svg?label=Discord%20Chat)](https://discord.gg/Y2TTePM)
## Alternative Social
[Twitter](https://twitter.com/OhMyForm)
[Instagram](https://www.instagram.com/ohmyform/)
## Contributors
### Code Contributors
This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].
[![Contributors](https://opencollective.com/ohmyform-sustainability/contributors.svg?width=890&button=false)](https://github.com/ohmyform/ohmyform/graphs/contributors)
### Financial Contributors
Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/ohmyform-sustainability/contribute)]
#### Individuals
[![Individuals](https://opencollective.com/static/images/opencollective-og-default.png)](https://opencollective.com/ohmyform-sustainability)
#### Organizations
Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/ohmyform-sustainability/contribute)]
[![](https://opencollective.com/ohmyform-sustainability/organization/0/avatar.svg)](https://opencollective.com/ohmyform-sustainability/organization/0/website)
[![](https://opencollective.com/ohmyform-sustainability/organization/1/avatar.svg)](https://opencollective.com/ohmyform-sustainability/organization/1/website)
[![](https://opencollective.com/ohmyform-sustainability/organization/2/avatar.svg)](https://opencollective.com/ohmyform-sustainability/organization/2/website)
[![](https://opencollective.com/ohmyform-sustainability/organization/3/avatar.svg)](https://opencollective.com/ohmyform-sustainability/organization/2/website)
[![](https://opencollective.com/ohmyform-sustainability/organization/4/avatar.svg)](https://opencollective.com/ohmyform-sustainability/organization/2/website)
[![](https://opencollective.com/ohmyform-sustainability/organization/5/avatar.svg)](https://opencollective.com/ohmyform-sustainability/organization/2/website)
<!-- TODO: Figure out how to generate that contributors table. -->

25
Vagrantfile vendored
View File

@ -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

1
api Submodule

@ -0,0 +1 @@
Subproject commit c2c421baa6f41e4e6fb812fa2978956aaf7aa0dd

107
app.json
View File

@ -1,37 +1,74 @@
{
"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"
}
]
"name": "ohmyform",
"description": "Opensource alternative to TypeForm",
"repository": "https://github.com/ohmyform/ohmyform",
"logo": "https://raw.githubusercontent.com/ohmyform/ohmyform/master/public/logo.png",
"keywords": [
"form",
"survey"
],
"env": {
"BASE_URL": {
"description": "Domain name where the site will be hosted",
"value": "ohmyform.herokuapp.com"
},
"MAILER_URI": {
"description": "Set this to a uri like smtp://localhost:1025 - for more information check https://nodemailer.com/",
"value": "smtp://smtp.mailtrap.io:2525"
},
"MAILER_FROM": {
"description": "Email address from which signup/verifications are sent",
"value": "OhMyForm <no-reply@localhost>"
},
"CREATE_ADMIN": {
"description": "Use this to automatically create an admin user on startup",
"value": "TRUE"
},
"ADMIN_EMAIL": {
"description": "The email address used by your default admin account",
"value": ""
},
"ADMIN_USERNAME": {
"description": "Username of your default admin account",
"value": "admin"
},
"LOGIN_NOTE": {
"description": "Note next to login form",
"value": "Welcome to your new OhMyForm instance, you can remove this login note in the dyno configuration!"
},
"ADMIN_PASSWORD": {
"description": "Password for your default admin account",
"value": ""
},
"SECRET_KEY": {
"description": "Database secret",
"generator": "secret"
},
"NODE_ENV": {
"description": "Node environment (production or development)",
"value": "production"
},
"NPM_CONFIG_PRODUCTION": {
"description": "Allow Dev Dependencies so we can compile typescript",
"value": "false"
},
"DATABASE_DRIVER": {
"description": "Database Driver to use",
"value": "postgres"
},
"DATABASE_SSL": {
"description": "Use SSL Connection for database",
"value": "true"
}
},
"addons": [
"heroku-postgresql",
"heroku-redis",
"mailtrap"
],
"buildpacks": [
{
"url": "heroku/nodejs"
}
]
}

View File

@ -1,32 +0,0 @@
'use strict';
var config = require('../../config/config');
/**cd
* Module dependencies.
*/
exports.index = function(req, res) {
res.render('index', {
user: req.user || null,
request: req
});
};
exports.form = function(req, res) {
//Allow form to be embedded
res.removeHeader('X-Frame-Options');
res.render('form', {
user: req.user || null,
request: req
});
};
exports.redoc = function(req, res) {
res.render('redoc', {
request: req
});
};

View File

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

View File

@ -1,346 +0,0 @@
'use strict';
/**
* Module dependencies.
*/
var mongoose = require('mongoose'),
errorHandler = require('./errors.server.controller'),
Form = mongoose.model('Form'),
FormSubmission = mongoose.model('FormSubmission'),
config = require('../../config/config'),
diff = require('deep-diff'),
_ = require('lodash');
/**
* Delete a forms submissions
*/
exports.deleteSubmissions = function(req, res) {
var submission_id_list = req.body.deleted_submissions,
form = req.form;
FormSubmission.remove({ form: req.form, _id: {$in: submission_id_list} }, function(err){
if(err){
res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
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');
});
});
};
/**
* Submit a form entry
*/
exports.createSubmission = function(req, res) {
var timeElapsed = 0;
if(typeof req.body.timeElapsed === 'number'){
timeElapsed = req.body.timeElapsed;
}
var submission = new FormSubmission({
form: req.body._id,
form_fields: req.body.form_fields,
timeElapsed: timeElapsed,
percentageComplete: req.body.percentageComplete,
ipAddr: req.body.ipAddr,
geoLocation: req.body.geoLocation,
device: req.body.device
});
submission.save(function(err, submission){
if (err) {
console.error(err.message);
return res.status(500).send({
message: errorHandler.getErrorMessage(err)
});
}
res.status(200).send('Form submission successfully saved');
});
};
/**
* Get List of Submissions for a given Form
*/
exports.listSubmissions = function(req, res) {
var _form = req.form;
FormSubmission.find({ form: _form._id }).sort('created').lean().exec(function(err, _submissions) {
if (err) {
console.error(err);
res.status(500).send({
message: errorHandler.getErrorMessage(err)
});
}
res.json(_submissions);
});
};
/**
* 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);
form.admin = req.user._id;
form.save(function(err) {
debugger;
if (err) {
return res.status(500).send({
message: errorHandler.getErrorMessage(err)
});
}
return res.json(form);
});
};
/**
* Show the current form
*/
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);
}
return res.status(404).send({
message: 'Form Does Not Exist'
});
}
return res.json(newForm);
}
};
/**
* Show the current form for rendering form live
*/
var readForRender = exports.readForRender = function(req, res) {
var newForm = req.form;
if (!newForm.isLive && !req.user) {
return res.status(401).send({
message: 'Form is Not Public'
});
}
delete newForm.lastModified;
delete newForm.__v;
delete newForm.created;
if(newForm.startPage && !newForm.startPage.showStart){
delete newForm.startPage;
}
return res.json(newForm);
};
/**
* Update a form
*/
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){
form.analytics = {
visitors: [],
gaCode: ''
}
}
if (req.body.changes) {
var formChanges = req.body.changes;
formChanges.forEach(function (change) {
diff.applyChange(form._doc, true, change);
});
} else {
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++){
var field = req.body.form.form_fields[i];
if(!checkForValidId.exec(field._id+'')){
delete field._id;
}
}
form = _.extend(form, updatedForm);
}
form.save(function(err, savedForm) {
if (err) {
res.status(500).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.json(savedForm);
}
});
};
/**
* Delete a form
*/
exports.delete = function(req, res) {
var form = req.form;
Form.remove({_id: form._id}, function(err) {
if (err) {
res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.json(form);
}
});
};
/**
* Get All of Users' Forms
*/
exports.list = function(req, res) {
//Allow 'admin' user to view all forms
var searchObj = {admin: req.user};
if(req.user.isAdmin()) searchObj = {};
Form.find(searchObj)
.sort('-created')
.select('title language admin submissions isLive')
.populate('admin.username', 'admin._id')
.lean()
.exec(function(err, forms) {
if (err) {
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);
}
});
};
/**
* Form middleware
*/
exports.formByID = function(req, res, next, id) {
if (!mongoose.Types.ObjectId.isValid(id)) {
return res.status(400).send({
message: 'Form is invalid'
});
}
Form.findById(id)
.populate('admin')
.exec(function(err, form) {
if (err) {
return next(err);
} else if (!form || form === null) {
res.status(404).send({
message: 'Form not found'
});
}
else {
//Remove sensitive information from User object
var _form = form;
_form.admin.password = null;
_form.admin.salt = null;
_form.provider = null;
req.form = _form;
return next();
}
});
};
/**
* FastForm middleware
*/
exports.formByIDFast = function(req, res, next, id) {
if (!mongoose.Types.ObjectId.isValid(id)) {
return res.status(400).send({
message: 'Form is invalid'
});
}
Form.findById(id)
.lean()
.select('title language form_fields startPage endPage hideFooter isLive design analytics.gaCode')
.exec(function(err, form) {
if (err) {
return next(err);
} else if (!form || form === null) {
res.status(404).send({
message: 'Form not found'
});
}
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;
return next();
}
});
};
/**
* Form authorization middleware
*/
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({
message: 'User '+req.user.username+' is not authorized to edit Form: '+form.title
});
}
return next();
};

View File

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

View File

@ -1,238 +0,0 @@
'use strict';
/**
* Module dependencies.
*/
var errorHandler = require('../errors.server.controller'),
mongoose = require('mongoose'),
passport = require('passport'),
config = require('../../../config/config'),
User = mongoose.model('User'),
tokgen = require('../../libs/tokenGenerator'),
fs = require('fs'),
i18n = require('i18n'),
async = require('async'),
pug = require('pug');
var nev = require('email-verification')(mongoose);
// NEV setup and configuration ================
var config_nev = function () {
nev.configure({
verifyMailOptions: {
from: config.mailer.from
},
confirmMailOptions: {
from: config.mailer.from
},
persistentUserModel: User,
tempUserCollection: config.tempUserCollection,
emailAndUsernameUnique: true,
expirationTime: 86400, // 24 hours
verificationURL: config.baseUrl+'/#!/verify/${URL}',
transportOptions: config.mailer.options,
verifySendMailCallback: function(err, info) {
if (err) {
throw err;
}
}
}, function(err, options){
if(err) {
throw err;
}
});
nev.generateTempUserModel(User, function(err){
if(err) {
throw err;
}
});
};
config_nev();
exports.validateVerificationToken = function(req, res){
const fn = pug.compileFile(__dirname + "/../../views/welcome.email.view.pug");
var renderedHtml = fn(res.locals);
var emailTemplate = {
subject: i18n.__('WELCOME_EMAIL_SUBJECT', config.app.title),
html: renderedHtml,
text: i18n.__('WELCOME_EMAIL_TEXT')
};
nev.confirmTempUser(req.params.token, emailTemplate, function(err, user) {
if(err) {
return res.status(500).send( {message: err } );
} else if (user){
return res.status(200).send('User successfully verified');
}
// redirect to resend verification email
else {
return res.status(400).send( {message: 'Verification token is invalid or has expired'} );
}
});
};
exports.resendVerificationEmail = function(req, res, next){
const fn = pug.compileFile(__dirname + "/../../views/verification.email.view.pug");
var renderedHtml = fn(res.locals);
var emailTemplate = {
subject: i18n.__('VERIFICATION_EMAIL_SUBJECT'),
html: renderedHtml,
text: i18n.__('VERIFICATION_EMAIL_TEXT')
};
nev.resendVerificationEmail(req.body.email, emailTemplate, function(err, userFound) {
if(err) {
return res.status(500).send( {message: errorHandler.getErrorMessage(err) } );
}
if (userFound){
res.status(200).send('Verification email successfully Re-Sent');
}else {
// user hasn't been found yet
res.status(400).send( {message: 'Error: User has not been registered yet'} );
}
});
};
/**
* Signup
*/
exports.signup = function(req, res) {
// For security measures we remove the roles from the req.body object
delete req.body.roles;
// Init Variables
var user = new User(req.body);
// Set language to visitor's language
user.language = req.cookies['userLang'];
// Add missing user fields
user.provider = 'local';
// 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)
});
}
// new user created
if (newTempUser) {
const fn = pug.compileFile(__dirname + "/../../views/verification.email.view.pug");
var renderedHtml = fn(res.locals);
var URL = newTempUser[nev.options.URLFieldName];
var emailTemplate = {
subject: i18n.__('VERIFICATION_EMAIL_SUBJECT'),
html: renderedHtml,
text: i18n.__('VERIFICATION_EMAIL_TEXT')
};
nev.sendVerificationEmail(user.email, URL, emailTemplate, function (sendEmailErr, info) {
if (sendEmailErr) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
}
return res.status(200).send('An email has been sent to you. Please check it to verify your account.');
});
} else {
return res.status(400).send({message: 'User with username/email already exists!'});
}
});
};
/**
* Signin after passport authentication
*/
exports.signin = function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err || !user) {
res.status(400).send(info);
} else {
// Remove sensitive data before login
user.password = null;
user.salt = null;
user.provider = null;
req.login(user, function(loginErr) {
if (loginErr) {
return res.status(400).send({
message: errorHandler.getErrorMessage(loginErr)
});
}
res.cookie('langCookie', user.language, { maxAge: 90000, httpOnly: true });
return res.json(user);
});
}
})(req, res, next);
};
/**
* Signout
*/
exports.signout = function(req, res) {
if(req.cookies.hasOwnProperty('userLang')){
res.clearCookie('userLang');
}
req.logout();
return res.status(200).send('You have successfully logged out.');
};
/* 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);
}
if (!user) {
return res.status(400).send({
message: 'User does not Exist'
});
}
user.apiKey = tokgen();
user.save(function(userSaveErr, _user) {
if (userSaveErr) {
return res.status(400).send({
message: errorHandler.getErrorMessage(userSaveErr)
});
}
var newUser = _user.toObject();
delete newUser.salt;
delete newUser.__v;
delete newUser.passwordHash;
delete newUser.provider;
return res.json(newUser);
});
});
};

View File

@ -1,66 +0,0 @@
'use strict';
/**
* 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();
});
};
/**
* Require login routing middleware
*/
exports.requiresLogin = function(req, res, next) {
if (!req.isAuthenticated()) {
return res.status(401).send({
message: 'User is not logged in'
});
} else {
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'
});
}
});
};
};

View File

@ -1,283 +0,0 @@
'use strict';
/**
* Module dependencies.
*/
var _ = require('lodash'),
errorHandler = require('../errors.server.controller'),
mongoose = require('mongoose'),
passport = require('passport'),
User = mongoose.model('User'),
config = require('../../../config/config'),
nodemailer = require('nodemailer'),
async = require('async'),
crypto = require('crypto'),
pug = require('pug');
var smtpTransport = nodemailer.createTransport(config.mailer.options);
/**
* Forgot for reset password (forgot POST)
*/
exports.forgot = function(req, res) {
async.waterfall([
// Generate random token
function(done) {
crypto.randomBytes(20, function(err, buffer) {
var token = buffer.toString('hex');
done(err, token);
});
},
// Lookup user by username
function(token, done) {
if (req.body.username) {
User.findOne({
$or: [
{'username': req.body.username.toLowerCase()},
{'email': req.body.username}
]
}, '-salt -password', function(err, user) {
if(err){
return res.status(500).send({
message: err.message
});
}
if (!user) {
var tempUserModel = mongoose.model(config.tempUserCollection);
tempUserModel.findOne({
$or: [
{'username': req.body.username.toLowerCase()},
{'email': req.body.username}
]
}).lean().exec(function(err, user) {
if(err){
return res.status(500).send({
message: err.message
});
}
if(!user){
return res.status(400).send({
message: 'No account with that username or email has been found'
});
}
return res.status(400).send({
message: 'The account associated with this email has not been activated yet'
});
});
} else {
user.resetPasswordToken = token;
user.resetPasswordExpires = Date.now() + 3600000; // 1 hour
user.save(function(err) {
done(err, token, user);
});
}
});
} else {
return res.status(400).send({
message: 'Username field must not be blank'
});
}
},
function(token, user, done) {
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);
},
// If valid email, send reset email using service
function(emailHTML, user, done) {
var mailOptions = {
to: user.email,
from: config.mailer.from,
subject: 'Password Reset',
html: emailHTML
};
var userEmail = user.email;
var user = userEmail.split('@')[0];
var domain = userEmail.split('@')[1];
var obfuscatedUser = user.substring(0, 1) + user.substring(1).replace(/./g, '*');
var domainName = domain.split('.')[0];
var tld = domain.split('.')[1];
var obfuscatedDomainName = domainName.replace(/./g, '*');
var obfuscatedEmail = obfuscatedUser + '@' + obfuscatedDomainName + '.' + tld;
smtpTransport.sendMail(mailOptions, function(err) {
done(err, obfuscatedEmail);
});
}
], 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.'
});
} else {
return res.send({
message: 'An email has been sent to ' + obfuscatedEmail + ' with further instructions.'
});
}
});
};
/**
* Reset password GET from email token
*/
exports.validateResetToken = function(req, res) {
User.findOne({
resetPasswordToken: req.params.token,
resetPasswordExpires: {
$gt: Date.now()
}
}, function(err, user) {
if(err){
return res.status(500).send({
message: err.message
});
}
if (!user) {
return res.redirect('/#!/password/reset/invalid');
}
res.redirect('/#!/password/reset/' + req.params.token);
});
};
/**
* Reset password POST from email token
*/
exports.reset = function(req, res, next) {
if(req.body.newPassword.length < 4){
return res.status(400).send({
message: 'Password must be at least 4 characters long'
});
}
if(req.body.newPassword !== req.body.verifyPassword){
return res.status(400).send({
message: 'Passwords do not match'
});
}
// Init Variables
var passwordDetails = req.body;
async.waterfall([
function(done) {
User.findOne({
resetPasswordToken: req.params.token,
resetPasswordExpires: {
$gt: Date.now()
}
}, function(err, user) {
if (!err && user) {
user.password = passwordDetails.newPassword;
user.resetPasswordToken = null;
user.resetPasswordExpires = null;
user.save(function(err, savedUser) {
if (err) {
done(err, null);
}
done(null, savedUser);
});
} else {
done('Password reset token is invalid or has expired.', null);
}
});
},
function(user, done) {
const fn = pug.compileFile(__dirname + "/../../views/templates/reset-password-confirm-email.server.view.pug");
var renderedHtml = fn(res.locals);
done(null, renderedHtml, user);
},
// If valid email, send reset email using service
function(emailHTML, user, done) {
var mailOptions = {
to: user.email,
from: config.mailer.from,
subject: 'Your password has been changed',
html: emailHTML
};
smtpTransport.sendMail(mailOptions, function(err) {
done(err);
});
}
], function(err) {
if (err) {
res.status(500).send({
message: err.message || err
});
}
return res.json({
message: 'Successfully changed your password!'
});
});
};
/**
* Change Password
*/
exports.changePassword = function(req, res) {
// Init Variables
var passwordDetails = req.body;
if (req.user) {
if (passwordDetails.newPassword) {
User.findById(req.user.id, function(err, user) {
if (!err && user) {
if (user.authenticate(passwordDetails.currentPassword)) {
if (passwordDetails.newPassword === passwordDetails.verifyPassword) {
user.password = passwordDetails.newPassword;
user.save(function(err) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
req.login(user, function(err) {
if (err) {
res.status(400).send(err);
} else {
res.send({
message: 'Password changed successfully'
});
}
});
}
});
} else {
res.status(400).send({
message: 'Passwords do not match'
});
}
} else {
res.status(400).send({
message: 'Current password is incorrect'
});
}
} else {
res.status(400).send({
message: 'User is not found'
});
}
});
} else {
res.status(400).send({
message: 'Please provide a new password'
});
}
} else {
res.status(400).send({
message: 'User is not signed in'
});
}
};

View File

@ -1,60 +0,0 @@
'use strict';
/**
* Module dependencies.
*/
var _ = require('lodash'),
errorHandler = require('../errors.server.controller.js'),
mongoose = require('mongoose');
/**
* Update user details
*/
exports.update = function(req, res) {
// Init Variables
var user = req.user;
// For security measurement we remove the roles from the req.body object
delete req.body.roles;
if (user) {
// Merge existing user
user = _.extend(user, req.body);
user.updated = Date.now();
user.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);
}
});
});
} 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;
res.json(req.user || null);
res.end();
};

View File

@ -1,79 +0,0 @@
'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'],
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'],
langCodeToWord: {
'en': 'English',
'fr': 'Français',
'es': 'Español',
'it': 'Italiàno',
'de': 'Deutsch'
},
wordToLangCode: {
'English': 'en',
'Français': 'fr',
'Español': 'es',
'Italiàno': 'it',
'Deutsch': 'de'
},
expressionStringTypes: ['field == static',
'field != static',
'field > static',
'field >= static',
'field <= static',
'field < static',
'field contains static',
'field !contains static',
'field begins static',
'field !begins static',
'field ends static',
'field !ends static'],
userRoleTypes: ['user', 'admin', 'superuser'],
regex: {
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,}))$/
}
};

View File

@ -1,39 +0,0 @@
'use strict';
// Plugin
module.exports = function timestamp (schema, options) {
options || (options = {})
// Options
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 }
}
if (useVirtual) {
// Use the ObjectID for extracting the created time
schema.virtual(createdPath).get(function () {
return new Date(this._id.generationTime * 1000)
})
} else {
if (!schema.paths[createdPath]) {
fields[createdPath] = {
type: Date
, default: Date.now
}
}
}
schema.add(fields)
// Update the modified timestamp on save
schema.pre('save', function (next) {
this[modifiedPath] = new Date
next()
})
}

View File

@ -1,8 +0,0 @@
'use strict';
var TokenGenerator = require('uuid-token-generator');
var tokgen = new TokenGenerator(256, TokenGenerator.BASE62);
module.exports = function() {
return tokgen.generate();
};

View File

@ -1,447 +0,0 @@
'use strict';
/**
* Module dependencies.
*/
var mongoose = require('mongoose'),
Schema = mongoose.Schema,
_ = require('lodash'),
timeStampPlugin = require('../libs/timestamp.server.plugin'),
async = require('async'),
Random = require('random-js'),
mt = Random.engines.mt19937();
mt.autoSeed();
//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,
match: [/((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)/],
},
action: String,
text: String,
bgColor: {
type: String,
match: [/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/],
default: '#5bc0de'
},
color: {
type: String,
match: [/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/],
default: '#ffffff'
}
});
var VisitorDataSchema = new Schema({
socketId: {
type: String
},
referrer: {
type: String
},
lastActiveField: {
type: Schema.Types.ObjectId
},
timeElapsed: {
type: Number
},
isSubmitted: {
type: Boolean
},
language: {
type: String
},
ipAddr: {
type: String,
default: ''
},
deviceType: {
type: String,
enum: ['desktop', 'phone', 'tablet', 'other'],
default: 'other'
},
userAgent: {
type: String
}
});
var formSchemaOptions = {
toJSON: {
virtuals: true
}
};
/**
* Form Schema
*/
var FormSchema = new Schema({
title: {
type: String,
trim: true,
required: 'Form Title cannot be blank'
},
language: {
type: String,
enum: ['en', 'fr', 'es', 'it', 'de'],
default: 'en',
required: 'Form must have a language'
},
analytics:{
gaCode: {
type: String
},
visitors: [VisitorDataSchema]
},
form_fields: [FieldSchema],
submissions: [{
type: Schema.Types.ObjectId,
ref: 'FormSubmission'
}],
admin: {
type: Schema.Types.ObjectId,
ref: 'User',
required: 'Form must have an Admin'
},
startPage: {
showStart:{
type: Boolean,
default: false
},
introTitle:{
type: String,
default: 'Welcome to Form'
},
introParagraph:{
type: String
},
introButtonText:{
type: String,
default: 'Start'
},
buttons:[ButtonSchema]
},
endPage: {
showEnd:{
type: Boolean,
default: false
},
title:{
type: String,
default: 'Thank you for filling out the form'
},
paragraph:{
type: String
},
buttonText:{
type: String,
default: 'Go back to Form'
},
buttons:[ButtonSchema]
},
hideFooter: {
type: Boolean,
default: false
},
isLive: {
type: Boolean,
default: true
},
design: {
colors:{
backgroundColor: {
type: String,
match: [/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/],
default: '#fff'
},
questionColor: {
type: String,
match: [/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/],
default: '#333'
},
answerColor: {
type: String,
match: [/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/],
default: '#333'
},
buttonColor: {
type: String,
match: [/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/],
default: '#fff'
},
buttonTextColor: {
type: String,
match: [/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/],
default: '#333'
}
},
font: String
}
}, 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',
useVirtual: false
});
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;
}
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);

View File

@ -1,215 +0,0 @@
'use strict';
/**
* Module dependencies.
*/
var mongoose = require('mongoose'),
util = require('util'),
timeStampPlugin = require('../libs/timestamp.server.plugin'),
_ = require('lodash'),
Schema = mongoose.Schema,
LogicJumpSchema = require('./logic_jump.server.model'),
tokgen = require('../libs/tokenGenerator');
var FieldOptionSchema = new Schema({
option_id: {
type: Number
},
option_title: {
type: String
},
option_value: {
type: String,
trim: true
}
});
var RatingFieldSchema = new Schema({
steps: {
type: Number,
min: 1,
max: 10
},
shape: {
type: String,
enum: [
'Heart',
'Star',
'thumbs-up',
'thumbs-down',
'Circle',
'Square',
'Check Circle',
'Smile Outlined',
'Hourglass',
'bell',
'Paper Plane',
'Comment',
'Trash'
]
},
validShapes: {
type: [String]
}
});
/**
* FormField Schema
*/
function BaseFieldSchema(){
Schema.apply(this, arguments);
this.add({
globalId: {
type: String,
},
isSubmission: {
type: Boolean,
default: false
},
submissionId: {
type: Schema.Types.ObjectId
},
title: {
type: String,
trim: true
},
description: {
type: String,
default: ''
},
logicJump: LogicJumpSchema,
ratingOptions: RatingFieldSchema,
fieldOptions: [FieldOptionSchema],
required: {
type: Boolean,
default: true
},
disabled: {
type: Boolean,
default: false
},
deletePreserved: {
type: Boolean,
default: false
},
validFieldTypes: {
type: [String]
},
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'
]
},
fieldValue: Schema.Types.Mixed
});
this.plugin(timeStampPlugin, {
createdPath: 'created',
modifiedPath: 'lastModified',
useVirtual: false
});
this.pre('save', function (next) {
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;
}
next();
});
}
util.inherits(BaseFieldSchema, Schema);
var FormFieldSchema = new BaseFieldSchema();
FormFieldSchema.pre('validate', function(next) {
var error = new mongoose.Error.ValidationError(this);
//If field is rating check that it has ratingOptions
if(this.fieldType !== 'rating'){
if(this.ratingOptions && this.ratingOptions.steps && this.ratingOptions.shape){
error.errors.ratingOptions = new mongoose.Error.ValidatorError({path: 'ratingOptions', message: 'ratingOptions is only allowed for type \'rating\' fields.', type: 'notvalid', value: this.ratingOptions});
console.error(error);
return(next(error));
}
}else{
//Setting default values for ratingOptions
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;
}
}
//If field is multiple choice check that it has field
if(this.fieldType !== 'dropdown' && this.fieldType !== 'radio' && this.fieldType !== 'checkbox'){
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();
});
//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();
});
var Field = mongoose.model('Field', FormFieldSchema);
var RatingOptions = mongoose.model('RatingOptions', RatingFieldSchema);
module.exports = FormFieldSchema;

View File

@ -1,104 +0,0 @@
'use strict';
/**
* Module dependencies.
*/
var mongoose = require('mongoose'),
Schema = mongoose.Schema,
timeStampPlugin = require('../libs/timestamp.server.plugin'),
FieldSchema = require('./form_field.server.model.js');
/**
* Form Submission Schema
*/
var FormSubmissionSchema = new Schema({
form_fields: [FieldSchema],
form: {
type: Schema.Types.ObjectId,
ref: 'Form',
required: true
},
ipAddr: {
type: String
},
geoLocation: {
Country: {
type: String
},
City: {
type: String
}
},
device: {
type: {
type: String
},
name: {
type: String
}
},
timeElapsed: {
type: Number
},
percentageComplete: {
type: Number
}
});
FormSubmissionSchema.pre('save', function (next) {
//Iterate through form fields and format data
for(var i = 0; i < this.form_fields.length; i++){
if(this.form_fields[i].fieldType === 'dropdown'){
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;
}
next();
});
FormSubmissionSchema.path('form_fields', {
set: function(form_fields){
for (var i = 0; i < form_fields.length; i++) {
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;
}
return form_fields;
}
});
FormSubmissionSchema.plugin(timeStampPlugin, {
createdPath: 'created',
modifiedPath: 'lastModified',
useVirtual: false
});
module.exports = FormSubmissionSchema;

View File

@ -1,84 +0,0 @@
'use strict';
/**
* Module dependencies.
*/
var mongoose = require('mongoose'),
Schema = mongoose.Schema,
_ = require('lodash');
var schemaOptions = {
toObject: {
virtuals: true
},
toJSON: {
virtuals: true
}
};
var LogicJumpSchema = new Schema({
expressionString: {
type: String,
enum: [
'field == static',
'field != static',
'field > static',
'field >= static',
'field <= static',
'field < static',
'field contains static',
'field !contains static',
'field begins static',
'field !begins static',
'field ends static',
'field !ends static'
]
},
fieldA: {
type: Schema.Types.ObjectId,
ref: 'FormField'
},
valueB: {
type: Schema.Types.String
},
jumpTo: {
type: Schema.Types.ObjectId,
ref: 'FormField'
},
enabled: {
type: Schema.Types.Boolean,
default: false
}
}, schemaOptions);
/*
IS EQUAL TO statement
var scope = {
a: val1,
b: val2
};
math.eval('a == b', scope);
IS NOT EQUAL TO statement
var scope = {
a: val1,
b: val2
};
math.eval('a !== b', scope);
BEGINS WITH statement
ENDS WITH statement
CONTAINS statement
DOES NOT CONTAIN statement
*/
mongoose.model('LogicJump', LogicJumpSchema);
module.exports = LogicJumpSchema;

View File

@ -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();
});
};

View File

@ -1,195 +0,0 @@
'use strict';
/**
* Module dependencies.
*/
var mongoose = require('mongoose'),
Schema = mongoose.Schema,
crypto = require('crypto'),
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);
};
/**
* User Schema
*/
var UserSchema = new Schema({
firstName: {
type: String,
trim: true,
default: ''
},
lastName: {
type: String,
trim: true,
default: ''
},
email: {
type: String,
trim: true,
lowercase: true,
unique: 'Account already exists with this email',
match: [/.+\@.+\..+/, '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 \'-\''],
required: [true, 'Username is required']
},
passwordHash: {
type: String,
default: ''
},
salt: {
type: String
},
provider: {
type: String,
default: 'local'
},
providerData: {},
additionalProvidersData: {},
roles: {
type: [{
type: String,
enum: ['user', 'admin', 'superuser']
}],
default: ['user']
},
language: {
type: String,
enum: ['en', 'fr', 'es', 'it', 'de'],
default: 'en',
},
lastModified: {
type: Date
},
created: {
type: Date,
default: Date.now
},
/* For reset password */
resetPasswordToken: {
type: String
},
resetPasswordExpires: {
type: Date
},
token: String,
apiKey: {
type: String,
unique: true,
index: true,
sparse: true
}
});
UserSchema.virtual('displayName').get(function () {
return this.firstName + ' ' + this.lastName;
});
UserSchema.plugin(timeStampPlugin, {
createdPath: 'created',
modifiedPath: 'lastModified',
useVirtual: false
});
/**
* Hook a pre save method to hash the password
*/
UserSchema.virtual('password').set(function (password) {
this.passwordHash = this.hashPassword(password);
});
UserSchema.virtual('password').get(function () {
return this.passwordHash;
});
/**
* Create instance method for hashing a password
*/
UserSchema.methods.hashPassword = function(password) {
var encoding = 'base64';
var iterations = 10000;
var keylen = 128;
var size = 64;
var digest = 'SHA1';
//Generate salt if it doesn't exist yet
if(!this.salt){
this.salt = crypto.randomBytes(size).toString(encoding);
}
if (password) {
return crypto.pbkdf2Sync(password, new Buffer(this.salt, encoding), iterations, keylen, digest).toString(encoding);
} else {
return password;
}
};
/**
* Create instance method for authenticating user
*/
UserSchema.methods.authenticate = function(password) {
return this.password === this.hashPassword(password);
};
/**
* Find possible not used username
*/
UserSchema.statics.findUniqueUsername = function(username, suffix, callback) {
var _this = this;
var possibleUsername = username + (suffix || '');
_this.findOne({
username: possibleUsername
}, function(err, user) {
if (!err) {
if (!user) {
return callback(possibleUsername);
} else {
return _this.findUniqueUsername(username, (suffix || 0) + 1, callback);
}
}
return callback(null);
});
};
/**
* Function to check if user has Admin priviledges
*/
UserSchema.methods.isAdmin = function() {
if(this.roles.indexOf('admin') !== -1){
return true;
}
return false;
};
module.exports = mongoose.model('User', UserSchema);

View File

@ -1,17 +0,0 @@
'use strict';
/**
* Module dependencies.
*/
var forms = require('../../app/controllers/forms.server.controller'),
core = require('../../app/controllers/core.server.controller');
module.exports = function(app) {
// Core routing
app.route('/')
.get(core.index);
app.route('/subdomain/api/')
.get(core.redoc);
};

View File

@ -1,56 +0,0 @@
'use strict';
/**
* Module dependencies.
*/
var forms = require('../../app/controllers/forms.server.controller'),
auth = require('../../config/passport_helpers'),
config = require('../../config/config'),
core = require('../../app/controllers/core.server.controller');
module.exports = function(app) {
// Form Routes
if(!config.subdomainsDisabled) {
app.route('/subdomain/:userSubdomain((?!api$)[A-Za-z0-9]+)/')
.get(core.form);
app.route('/subdomain/:userSubdomain((?!api$)[A-Za-z0-9]+)/forms/([a-zA-Z0-9]+)')
.post(forms.createSubmission);
app.route('/subdomain/:userSubdomain((?!api$)[A-Za-z0-9]+)/forms/:formIdFast([a-zA-Z0-9]+)/render')
.get(forms.readForRender);
app.route('/forms/:formId([a-zA-Z0-9]+)/render')
.get(auth.isAuthenticatedOrApiKey, forms.hasAuthorization, forms.readForRender);
} else {
app.route('/forms/:formIdFast([a-zA-Z0-9]+)/render')
.get(forms.readForRender);
app.route('/view/')
.get(core.form);
}
app.route('/forms/:formIdFast([a-zA-Z0-9]+)')
.post(forms.createSubmission)
app.route('/forms')
.get(auth.isAuthenticatedOrApiKey, forms.list)
.post(auth.isAuthenticatedOrApiKey, forms.create);
app.route('/forms/:formId([a-zA-Z0-9]+)')
.get(forms.read)
.post(forms.createSubmission)
.put(auth.isAuthenticatedOrApiKey, forms.hasAuthorization, forms.update)
.delete(auth.isAuthenticatedOrApiKey, forms.hasAuthorization, forms.delete);
app.route('/forms/:formId([a-zA-Z0-9]+)/submissions')
.get(auth.isAuthenticatedOrApiKey, forms.hasAuthorization, forms.listSubmissions)
.delete(auth.isAuthenticatedOrApiKey, forms.hasAuthorization, forms.deleteSubmissions);
// Slower formId middleware
app.param('formId', forms.formByID);
// Fast formId middleware
app.param('formIdFast', forms.formByIDFast);
};

View File

@ -1,39 +0,0 @@
'use strict';
/**
* Module dependencies.
*/
var passport = require('passport'),
config = require('../../config/config'),
auth = require('../../config/passport_helpers');
module.exports = function(app) {
// User Routes
var users = require('../../app/controllers/users.server.controller');
// Setting up the users profile api
app.route('/users/me').get(auth.isAuthenticatedOrApiKey, users.getUser);
app.route('/users').put(auth.isAuthenticatedOrApiKey, users.update);
// Setting up the users account verification api
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);
app.route('/auth/forgot').post(users.forgot);
app.route('/auth/reset/:token').get(users.validateResetToken);
app.route('/auth/reset/:token').post(users.reset);
// Setting up the users authentication api
if(!config.signupDisabled) {
app.route('/auth/signup').post(users.signup);
}
app.route('/auth/signin').post(users.signin);
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);
};

View File

@ -1,80 +0,0 @@
'use strict';
/**
* Module dependencies.
*/
var mongoose = require('mongoose'),
errorHandler = require('../controllers/errors.server.controller'),
Form = mongoose.model('Form'),
request = require('request');
// Create the chat configuration
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));
}
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();
}
});
});
};
io.on('connection', function(current_socket) {
// a user has visited our page - add them to the visitorsData object
current_socket.on('form-visitor-data', function(data) {
visitorsData[current_socket.id] = data;
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() {
current_socket.disconnect(true);
});
}
});
current_socket.on('disconnect', function() {
var data = visitorsData[current_socket.id];
if(data && !data.isSubmitted && !data.isSaved) {
data.isSaved = true;
saveVisitorData(data, function() {
delete visitorsData[current_socket.id];
});
} else {
delete visitorsData[current_socket.id];
}
});
});
};

View File

@ -1,94 +0,0 @@
'use strict';
require('../../server.js');
/**
* Module dependencies.
*/
var should = require('should'),
mongoose = require('mongoose'),
User = mongoose.model('User'),
Form = mongoose.model('Form');
/**
* Globals
*/
var user, myForm;
/**
* Unit tests
*/
describe('Form Model Unit Tests:', function() {
this.timeout(15000);
beforeEach(function(done) {
user = new User({
firstName: 'Full',
lastName: 'Name',
email: 'test@test.com',
username: 'aueoaueoa',
password: 'password',
provider: 'local'
});
user.save(function(err) {
if(err) {
return done(err);
}
myForm = new Form({
title: 'Form Title',
admin: user,
language: 'en',
form_fields: [
{'fieldType':'textfield', title:'First Name', 'fieldValue': ''},
{'fieldType':'checkbox', title:'nascar', 'fieldValue': ''},
{'fieldType':'checkbox', title:'hockey', 'fieldValue': ''}
]
});
done();
});
});
describe('Method Save', function() {
it('should be able to save without problems', function(done) {
myForm.save(function(err) {
should.not.exist(err);
done();
});
});
it('should be able to show an error when try to save without title', function(done) {
var _form = myForm;
_form.title = '';
_form.save(function(err) {
should.exist(err);
should.equal(err.errors.title.message, 'Form Title cannot be blank');
done();
});
});
});
describe('Method Find', function(){
beforeEach(function(done){
myForm.save(function(err) {
return done(err);
});
});
it('should be able to findOne my form without problems', function(done) {
Form.findOne({title: myForm.title}).exec(function(err, form) {
should.not.exist(err);
should.exist(form);
should.equal(form.toObject().id, myForm.toObject().id);
done();
});
});
});
afterEach(function(done) {
Form.remove().exec(function() {
User.remove().exec(done);
});
});
});

View File

@ -1,390 +0,0 @@
'use strict';
var should = require('should'),
lodash = require('lodash'),
app = require('../../server'),
request = require('supertest'),
Session = require('supertest-session'),
mongoose = require('mongoose'),
User = mongoose.model('User'),
Form = mongoose.model('Form'),
Field = mongoose.model('Field'),
FormSubmission = mongoose.model('FormSubmission'),
async = require('async');
/**
* Globals
*/
var user, myForm, userSession;
// Create user credentials
var credentials = {
username: 'aeokjqjqkqaeoaoe',
email: 'aeoaekjqjqqjkoeoa@test.com',
password: 'password'
};
/**
* Form routes tests
*/
describe('Form Routes Unit tests', function() {
beforeEach(function(done) {
// Create a new user
user = new User({
firstName: 'Full',
lastName: 'Name',
email: credentials.email,
username: credentials.username,
password: credentials.password,
provider: 'local'
});
// Save a user to the test db and create new Form
user.save(function(err) {
should.not.exist(err);
myForm = {
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': ''})
],
isLive: true
};
//Initialize Session
userSession = Session(app);
done();
});
});
it(' > should not be able to create a Form if not logged in', function(done) {
userSession.post('/forms')
.send({form: myForm})
.expect(401)
.end(function(FormSaveErr, FormSaveRes) {
console.log(FormSaveRes.text);
// Call the assertion callback
done(FormSaveErr);
});
});
it(' > should not be able to get list of users\' Forms if not logged in', function(done) {
userSession.get('/forms')
.expect(401)
.end(function(FormSaveErr, FormSaveRes) {
// Call the assertion callback
done(FormSaveErr);
});
});
it(' > should be able to read/get a live Form if not signed in', function(done) {
// Create new Form model instance
var FormObj = new Form(myForm);
// Save the Form
FormObj.save(function(err, form) {
if(err) return done(err);
userSession.get('/subdomain/' + credentials.username + '/forms/' + form._id + '/render')
.expect(200)
.end(function(err, res) {
if(err) return done(err)
// Set assertion
(res.body).should.be.an.Object.with.property('title', myForm.title);
// Call the assertion callback
done();
});
});
});
it(' > should be able to read/get a non-live Form if not signed in', function(done) {
// Create new Form model instance
var FormObj = new Form(myForm);
FormObj.isLive = false;
// Save the Form
FormObj.save(function(err, form) {
if(err) return done(err);
userSession.get('/subdomain/' + credentials.username + '/forms/' + form._id + '/render')
.expect(401, {message: 'Form is Not Public'})
.end(function(err, res) {
done(err);
});
});
});
it(' > should not be able to delete an Form if not signed in', function(done) {
// Set Form user
myForm.admin = user;
// Create new Form model instance
var FormObj = new Form(myForm);
// Save the Form
FormObj.save(function() {
// Try deleting Form
userSession.delete('/forms/' + FormObj._id)
.expect(401)
.end(function(FormDeleteErr, FormDeleteRes) {
// Handle Form error error
done(FormDeleteErr);
});
});
});
describe(' > Login as User', function() {
//Initialize Session
var authenticatedSession;
var loginSession = Session(app);
beforeEach(function(done) {
loginSession.post('/auth/signin')
.send(credentials)
.expect('Content-Type', /json/)
.expect(200)
.end(function(signinErr, signinRes) {
if(signinErr) {
return done(signinErr);
}
authenticatedSession = loginSession;
return done();
});
});
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"})
.end(function(FormSaveErr, FormSaveRes) {
// Call the assertion callback
done(FormSaveErr);
});
});
it(' > should not be able to save a Form if no title is provided', function(done) {
// Set Form with a invalid title field
myForm.title = '';
// Save a new Form
authenticatedSession.post('/forms')
.send({form: myForm})
.expect(500)
.end(function(FormSaveErr, FormSaveRes) {
// Handle Form save error
if (FormSaveErr) {
return done(FormSaveErr);
}
// Set message assertion
(FormSaveRes.body.message).should.equal('Form Title cannot be blank');
done();
});
});
it(' > should be able to create a Form if form_fields are undefined', function(done) {
myForm.analytics = null;
myForm.form_fields = null;
loginSession.post('/forms')
.send({form: myForm})
.expect(200)
.end(function(FormSaveErr, FormSaveRes) {
// Call the assertion callback
done(FormSaveErr);
});
});
it(' > should be able to update a Form if signed in and Form is valid', function(done) {
// Save a new Form
loginSession.post('/forms')
.send({form: myForm})
.expect('Content-Type', /json/)
.expect(200)
.end(function(FormSaveErr, FormSaveRes) {
// Handle Form save error
if (FormSaveErr) {
return done(FormSaveErr);
}
// Update Form title
myForm.title = 'WHY YOU GOTTA BE SO FORMULAIC?';
// Update an existing Form
loginSession.put('/forms/' + FormSaveRes.body._id)
.send({form: myForm})
.expect('Content-Type', /json/)
.expect(200)
.end(function(FormUpdateErr, FormUpdateRes) {
// Handle Form update error
if (FormUpdateErr){
done(FormUpdateErr);
}
// Set assertions
(FormUpdateRes.body._id).should.equal(FormSaveRes.body._id);
(FormUpdateRes.body.title).should.match(myForm.title);
// Call the assertion callback
done();
});
});
});
it(' > should be able to delete a Form if signed in', function(done) {
// Save a new Form
loginSession.post('/forms')
.send({form: myForm})
.expect('Content-Type', /json/)
.expect(200)
.end(function(FormSaveErr, FormSaveRes) {
// Handle Form save error
if (FormSaveErr) {
return done(FormSaveErr);
}
// Delete an existing Form
loginSession.delete('/forms/' + FormSaveRes.body._id)
.send(myForm)
.expect('Content-Type', /json/)
.expect(200)
.end(function(FormDeleteErr, FormDeleteRes) {
// Handle Form error error
if (FormDeleteErr) {
return done(FormDeleteErr);
}
// Set assertions
should.exist(FormDeleteRes.body);
(FormDeleteRes.body._id).should.equal(FormSaveRes.body._id);
// Call the assertion callback
done();
});
});
});
it(' > should be able to save new form while logged in', function(done){
// Save a new Form
authenticatedSession.post('/forms')
.send({form: myForm})
.expect('Content-Type', /json/)
.expect(200)
.end(function(FormSaveErr, FormSaveRes) {
// Handle Form save error
if (FormSaveErr) return done(FormSaveErr);
var _form = FormSaveRes.body;
// Get a list of Forms
authenticatedSession.get('/forms/'+_form._id)
.expect('Content-Type', /json/)
.expect(200)
.end(function(FormsGetErr, FormsGetRes) {
// Handle Form save error
if (FormsGetErr) return done(FormsGetErr);
var fetchedForm = FormsGetRes.body;
// Set assertions
(fetchedForm.admin.email).should.equal(user.email);
(fetchedForm.title).should.match(_form.title);
// Call the assertion callback
done();
});
});
});
it(' > should be able to get list of users\' forms sorted by date created while logged in', function(done) {
var myForm1 = {
title: 'First Form',
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': ''})
],
isLive: true
};
var myForm2 = {
title: 'Second Form',
language: 'en',
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': ''})
],
isLive: true
};
var FormObj1 = new Form(myForm1);
var FormObj2 = new Form(myForm2);
async.waterfall([
function(callback) {
FormObj1.save(function(err){
callback(err);
});
},
function(callback) {
FormObj2.save(function(err){
callback(err);
});
},
function(callback) {
loginSession.get('/forms')
.expect(200)
.end(function(err, res) {
res.body.length.should.equal(2);
res.body[0].title.should.equal('Second Form');
res.body[1].title.should.equal('First Form');
// Call the assertion callback
callback(err);
});
}
], function (err) {
done(err);
});
});
afterEach('should be able to signout user', function(done){
authenticatedSession.get('/auth/signout')
.expect(200)
.end(function(signoutErr, signoutRes) {
// Handle signout error
if (signoutErr) {
return done(signoutErr);
}
authenticatedSession.destroy();
done();
});
});
});
afterEach(function(done) {
Form.remove({}).exec(function() {
User.remove({}).exec(function() {
userSession.destroy();
done();
});
});
});
});

View File

@ -1,258 +0,0 @@
'use strict';
/**
* Module dependencies.
*/
var should = require('should'),
mongoose = require('mongoose'),
User = mongoose.model('User'),
Form = mongoose.model('Form'),
Field = mongoose.model('Field'),
_ = require('lodash'),
async = require('async'),
config = require('../../config/config'),
FormSubmission = mongoose.model('FormSubmission');
var exampleDemo = {
address: '880-9650 Velit. St.',
city: '',
dateOfBirth: '10',
displayName: 'Test User',
email: 'polydaic@gmail.com',
firstName: 'Test User',
hin: '',
lastName: 'AddDemographic',
lastUpdateDate: '2014-10-01 00:00:00',
monthOfBirth: '05',
officialLanguage: 'English',
phone: '250-222-2222',
phone2: '',
postal: 'S4M 7T8',
province: 'BC',
sex: 'F',
sexDesc: 'Female',
sin: '',
spokenLanguage: 'English',
title: 'MS.',
yearOfBirth: '2015'
};
var sampleFormFields = [
{'fieldType':'textfield', 'title':'What\'s your first name', 'fieldValue': ''},
{'fieldType':'textfield', 'title':'And your last name', 'fieldValue': ''},
{'fieldType':'radio', 'title':'And your sex', 'fieldOptions': [{ 'option_id': 0, 'option_title': 'Male', 'option_value': 'M' }, { 'option_id': 1, 'option_title': 'Female', 'option_value': 'F' }], 'fieldValue': ''},
{'fieldType':'date', 'title':'When were you born?', 'fieldValue': ''},
{'fieldType':'number', 'title':'What\'s your phone #?', 'fieldValue': ''}
];
var sampleSubmission = [
{'fieldType':'textfield', 'title':'What\'s your first name', 'fieldValue': 'David'},
{'fieldType':'textfield', 'title':'And your last name', 'fieldValue': 'Baldwynn'},
{'fieldType':'radio', 'title':'And your sex', 'fieldValue': 'M', 'fieldOptions': [{ 'option_id': 0, 'option_title': 'Male', 'option_value': 'M' }, { 'option_id': 1, 'option_title': 'Female', 'option_value': 'F' }]},
{'fieldType':'date', 'title':'When were you born?', 'fieldValue': 'Tue Oct 06 2015 15:17:48 GMT-0700 (PDT)'},
{'fieldType':'number', 'title':'What\'s your phone #?', 'fieldValue': '6043158008'}
];
_.mixin({
deepOmit: function(obj, iteratee, context) {
var r = _.omit(obj, iteratee, context);
_.each(r, function(val, key) {
if (typeof(val) === 'object')
r[key] = _.deepOmit(val, iteratee, context);
});
return r;
}
});
/**
* Globals
*/
var user, myForm, mySubmission;
/**
* Unit tests
*/
describe('FormSubmission Model Unit Tests:', function() {
this.timeout(15000);
beforeEach(function(done) {
user = new User({
firstName: 'Full',
lastName: 'Name',
displayName: 'Full Name',
email: 'test1@test.com'+Date.now(),
username: 'test1'+Date.now(),
password: 'password',
provider: 'local'
});
user.save(function(err) {
if(err){
done(err);
return;
}
myForm = new Form({
title: 'Form Title1',
admin: user._id,
language: 'en',
form_fields: [
{'fieldType':'textfield', 'title':'What\'s your first name', 'fieldValue': ''},
{'fieldType':'textfield', 'title':'And your last name', 'fieldValue': ''},
{'fieldType':'radio', 'title':'And your sex', 'fieldOptions': [{ 'option_id': 0, 'option_title': 'Male', 'option_value': 'M' }, { 'option_id': 1, 'option_title': 'Female', 'option_value': 'F' }], 'fieldValue': ''},
{'fieldType':'date', 'title':'When were you born?', 'fieldValue': ''},
{'fieldType':'number', 'title':'What\'s your phone #?', 'fieldValue': ''}
]
});
myForm.save(function(err, form){
if(err){
return done(err);
}
var submissionFields = _.clone(myForm.form_fields);
for(var z=0; z<submissionFields.length; z++){
submissionFields[z] = _.extend(myForm.form_fields[z], sampleSubmission[z]);
}
mySubmission = new FormSubmission({
admin: user._id,
form: myForm._id,
timeElapsed: 17.55,
form_fields: submissionFields
});
done();
});
});
});
describe('Method Save', function() {
this.timeout(15000);
var oscar_demo_num;
beforeEach(function(done){
var myFieldMap = {};
myForm.save(function(err, form){
if(err) done(err);
else done();
});
});
it('should be able to save a FormSubmission without problems', function(done) {
mySubmission.save(function(err, submission) {
should.not.exist(err);
should.exist(submission);
done();
});
});
});
describe('Method Find', function(){
beforeEach(function(done){
mySubmission.save(function(err) {
done();
});
});
it('should be able to findOne FormSubmission without problems', function(done) {
FormSubmission.findOne({_id: mySubmission._id}).exec(function(err,submission) {
should.not.exist(err);
should.exist(submission);
should.deepEqual(submission.toObject(), mySubmission.toObject());
done();
});
});
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} } })
.exec(function(err, submission){
should.not.exist(err);
should.exist(submission);
should.deepEqual(submission.toObject(), mySubmission.toObject());
done();
});
});
});
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() {
FormSubmission.remove().exec(done);
});
});
});
});

View File

@ -1,242 +0,0 @@
'use strict';
var should = require('should'),
_ = require('lodash'),
app = require('../../server'),
request = require('supertest'),
Session = require('supertest-session'),
mongoose = require('mongoose'),
User = mongoose.model('User'),
Form = mongoose.model('Form'),
Field = mongoose.model('Field'),
FormSubmission = mongoose.model('FormSubmission'),
agent = request.agent(app);
/**
* Globals
*/
var credentials, user;
/**
* Form routes tests
*/
describe('Form Submission Routes Unit tests', function() {
var FormObj, _Submission, submissionSession, _SubmissionBody
beforeEach(function(done) {
// Create user credentials
credentials = {
email: 'test@test.com',
username: 'test',
password: 'password'
};
// Create a new user
user = new User({
firstName: 'Full',
lastName: 'Name',
email: credentials.email,
username: credentials.username,
password: credentials.password,
provider: 'local'
});
// Save a user to the test db and create new Form
user.save(function(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': ''})
]
});
FormObj.save(function(formSaveErr, form) {
if (formSaveErr) done(formSaveErr);
_Submission = {
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}
],
percentageComplete: 100,
timeElapsed: 11.55,
ipAddr: '123.233.232.232',
geoLocation: {
Country: 'Canada',
City: 'Vancouver'
},
device:{
type: 'Mobile',
name: 'iPhone'
}
};
_SubmissionBody ={
_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}
],
percentageComplete: 100,
timeElapsed: 11.55,
ipAddr: '123.233.232.232',
geoLocation: {
Country: 'Canada',
City: 'Vancouver'
},
device:{
type: 'Mobile',
name: 'iPhone'
}
};
FormObj = form;
//Initialize Session
submissionSession = Session(app);
done();
});
});
});
it(' > should be able to create a Form Submission without signing in', function(done) {
//Create Submission
submissionSession.post('/forms/' + FormObj._id)
.send(_SubmissionBody)
.expect(200)
.end(function(err, res) {
should.not.exist(err);
done();
});
});
it(' > should be able to get Form Submissions if signed in', function(done) {
//Create Submission
submissionSession.post('/forms/' + FormObj._id)
.send(_SubmissionBody)
.expect(200)
.end(function(err, res) {
should.not.exist(err);
submissionSession.post('/auth/signin')
.send(credentials)
.expect('Content-Type', /json/)
.expect(200)
.end(function(signinErr, signinRes) {
should.not.exist(signinErr);
submissionSession.get('/forms/' + FormObj._id + '/submissions')
.expect('Content-Type', /json/)
.expect(200)
.end(function(submissionErr, res) {
// Set assertion
should.not.exist(submissionErr);
// Call the assertion callback
done();
});
});
});
});
it(' > should be able to delete Form Submission if signed in', function(done) {
// Create new FormSubmission model instance
var SubmissionObj = new FormSubmission(_Submission);
SubmissionObj.save(function (err, submission) {
should.not.exist(err);
// Sign n as user
submissionSession.post('/auth/signin')
.send(credentials)
.expect('Content-Type', /json/)
.expect(200)
.end(function(signinErr, signinRes) {
// Handle signin error
should.not.exist(signinErr);
var submission_ids = _.map([submission], '_id');
//Delete form submissions
submissionSession.delete('/forms/' + FormObj._id + '/submissions')
.send({deleted_submissions: submission_ids})
.expect(200)
.end(function(submissionErr, res) {
// Set assertions
should.not.exist(submissionErr);
(res.text).should.equal('Form submissions successfully deleted');
// Call the assertion callback
done();
});
});
});
});
it(' > should not be able to get Form Submissions if not signed in', function(done) {
// Attempt to fetch form submissions
submissionSession.get('/forms/' + FormObj._id + '/submissions')
.expect(401)
.end(function(err, res) {
should.not.exist(err);
// Call the assertion callback
done();
});
});
it(' > should not be able to delete Form Submission if not signed in', function(done) {
var SubmissionObj = new FormSubmission(_Submission);
SubmissionObj.save(function (err, submission) {
should.not.exist(err);
var submission_ids = _.map([submission], '_id');
// Attempt to delete form submissions
submissionSession.delete('/forms/' + FormObj._id + '/submissions')
.send({deleted_submissions: submission_ids})
.expect(401)
.end(function (submissionErr, res) {
// Set assertions
should.not.exist(submissionErr);
// Call the assertion callback
done();
});
});
});
afterEach(function(done) {//logout current user if there is one
FormSubmission.remove().exec(function() {
Form.remove().exec(function (err) {
User.remove({}).exec(function() {
submissionSession.destroy();
done();
});
});
});
});
});

View File

@ -1,70 +0,0 @@
// Dependencies
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()
before(function () {
FooModel.remove(function (err) {
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()
})
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()
})
})
})
describe('#custom()', function () {
var FooSchema = new Schema()
FooSchema.plugin(timestamp, {
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)
})
})
it('should have custom properties', function (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()
})
})
})
})

View File

@ -1,90 +0,0 @@
'use strict';
/**
* Module dependencies.
*/
var should = require('should'),
mongoose = require('mongoose'),
User = mongoose.model('User');
/**
* Globals
*/
var user, user2;
/**
* Unit tests
*/
describe('User Model Unit Tests:', function() {
beforeEach(function(done) {
user = new User({
firstName: 'Full',
lastName: 'Name',
username: 'test',
email: 'test@test.com',
password: 'password',
provider: 'local'
});
user2 = new User({
firstName: 'Full',
lastName: 'Name',
username: 'test',
email: 'test@test.com',
password: 'password',
provider: 'local'
});
done();
});
describe('Method Save', function() {
it('should begin with no users', function(done) {
User.find({}, function(err, users) {
users.should.have.length(0);
done();
});
});
it('should be able to save without problems', function(done) {
user.save(done);
});
it('should fail to save an existing user again', function(done) {
user.save(function() {
user2.save(function(err) {
should.exist(err);
done();
});
});
});
it('should be able to show an error when try to save without username', function(done) {
user.username = '';
user.save(function(err) {
should.exist(err);
done();
});
});
});
describe('Method findUniqueUsername', function() {
beforeEach(function(done) {
User.find({}, function(err, users) {
users.should.have.length(0);
user.save(done);
});
});
it('should be able to find unique version of existing username without problems', function(done) {
User.findUniqueUsername(user.username, null, function (availableUsername) {
availableUsername.should.not.equal(user.username);
done();
});
});
});
afterEach(function(done) {
User.remove().exec(done);
});
});

View File

@ -1,112 +0,0 @@
'use strict';
var should = require('should'),
app = require('../../server'),
Session = require('supertest-session'),
mongoose = require('mongoose'),
User = mongoose.model('User'),
config = require('../../config/config'),
tmpUser = mongoose.model(config.tempUserCollection);
/**
* Globals
*/
var credentials, _User, activateToken, userSession;
/**
* Form routes tests
*/
describe('User CRUD tests', function() {
this.timeout(30000);
beforeEach(function() {
// Create user credentials
credentials = {
email: 'test732@test.com',
username: 'test732',
password: 'password3223'
};
//Create a new user
_User = {
email: credentials.email,
username: credentials.username,
password: credentials.password
};
//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);
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();
});
});
});
});
});
});
});
afterEach(function(done) {
User.remove().exec(function () {
tmpUser.remove().exec(function(){
userSession.destroy();
done();
});
});
});
});

View File

@ -1,8 +0,0 @@
extends layout.server.view.pug
block content
section.public-form.auth.sigin-view.valign-wrapper
div.row.valign
h3.col-md-12.text-center=__('404_HEADER')
div.col-md-4.col-md-offset-4
div.col-md-12.text-center(style="padding-bottom: 50px;")=__('404_BODY', url)

View File

@ -1,12 +0,0 @@
extends layout.server.view.pug
block content
section.public-form.auth.sigin-view.valign-wrapper
div.row.valign
h3.col-md-12.text-center=__('500_HEADER')
div.col-md-4.col-md-offset-4
if process.env.NODE_ENV == 'development' || process.env.NODE_ENV == 'test'
div.col-md-12.text-center(style="padding-bottom: 50px;")
| #{error}
else
div.col-md-12.text-center(style="padding-bottom: 50px;")=__('500_BODY')

View File

@ -1,109 +0,0 @@
doctype html
html(lang='en', xmlns='http://www.w3.org/1999/xhtml')
head
title=title
// General META
meta(charset='utf-8')
meta(http-equiv='Content-type', content='text/html;charset=UTF-8')
meta(http-equiv='X-UA-Compatible', content='IE=edge,chrome=1')
meta(name='viewport', content='width=device-width,initial-scale=1,maximum-scale=1')
meta(name='apple-mobile-web-app-capable', content='yes')
meta(name='apple-mobile-web-app-status-bar-style', content='black')
// Semantic META
meta(name='keywords', content='keywords')
meta(name='description', content='description')
// Facebook META
meta(property='og:site_name', content=title)
meta(property='og:title', content=title)
meta(property='og:description', content='description')
meta(property='og:url', content='url')
meta(property='og:image', content='/img/brand/logo.png')
meta(property='og:type', content='website')
// Twitter META
meta(name='twitter:title', content=title)
meta(name='twitter:description', content='description')
meta(name='twitter:url', content='url')
meta(name='twitter:image', content='/img/brand/logo.png')
style.
.loader {
position: fixed;
left: 0px;
top: 0px;
width: 100%;
height: 100%;
z-index: 9999;
background: url('/static/dist/page-loader.gif') 50% 35% no-repeat rgb(249,249,249);
background-size: 50px 50px;
}
// 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
script(type='text/javascript').
var signupDisabled = !{signupDisabled};
var socketPort = false;
var socketUrl = false;
var subdomainsDisabled = !{subdomainsDisabled};
//Embedding socketPort
if socketPort
script(type='text/javascript').
socketPort = !{socketPort}
//Embedding socketUrl
if socketUrl
script(type='text/javascript').
socketUrl = "!{socketUrl}"
//JSEP
script(src='https://cdn.jsdelivr.net/npm/jsep@0.3.4/build/jsep.min.js', type='text/javascript')
script(src='/static/lib/jquery/dist/jquery.min.js', type='text/javascript')
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')
//Bower CSS dependencies
each bowerCssFile in bowerCssFiles
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.min.css')
// end Bower CSS dependencies
//Application CSS Files
each cssFile in cssFiles
link(rel='stylesheet', href=cssFile)
// end Application CSS Files
//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')
//Application JavaScript Files
each jsFile in formJSFiles
script(type='text/javascript', src=jsFile)
// end Application Javascript dependencies
if process.env.NODE_ENV === 'development'
//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();
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").
window.heap=window.heap||[],heap.load=function(e,t){window.heap.appid=e,window.heap.config=t=t||{};var r=t.forceSSL||"https:"===document.location.protocol,a=document.createElement("script");a.type="text/javascript",a.async=!0,a.src=(r?"https:":"http:")+"//cdn.heapanalytics.com/js/heap-"+e+".js";var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(a,n);for(var o=function(e){return function(){heap.push([e].concat(Array.prototype.slice.call(arguments,0)))}},p=["addEventProperties","addUserProperties","clearEventProperties","identify","removeEventProperty","setEventProperties","track","unsetEventProperty"],c=0;c<p.length;c++)heap[p[c]]=o(p[c])};
heap.load("2213510609");

View File

@ -1,55 +0,0 @@
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')
//Embedding The User Object
script(type='text/javascript').
var user = null;
//Embedding The signupDisabled Boolean
script(type='text/javascript').
var signupDisabled = !{signupDisabled};
var socketPort = false;
var socketUrl = false;
var subdomainsDisabled = !{subdomainsDisabled};
var locale = "en";
//Embedding locale
if locale
script(type='text/javascript').
locale = "!{locale}";
//Embedding socketPort
if socketPort
script(type='text/javascript').
socketPort = !{socketPort}
//Embedding socketUrl
if socketUrl
script(type='text/javascript').
socketUrl = "!{socketUrl}"
//Socket.io Client Dependency
script(src='https://cdn.socket.io/socket.io-1.4.5.js')
//Bower JS dependencies
each bowerJSFile in bowerJSFiles
script(type='text/javascript', src=bowerJSFile)
// end Bower JS dependencies
script(type='text/javascript', src='https://cdnjs.cloudflare.com/ajax/libs/angular-strap/2.3.8/angular-strap.min.js')
//Application JavaScript Files
each jsFile in jsFiles
script(type='text/javascript', src=jsFile)
// end Application Javascript dependencies
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();

View File

@ -1,61 +0,0 @@
doctype html
html(lang='en', xmlns='http://www.w3.org/1999/xhtml')
head
title=title
// General META
meta(charset='utf-8')
meta(http-equiv='Content-type', content='text/html;charset=UTF-8')
meta(http-equiv='X-UA-Compatible', content='IE=edge,chrome=1')
meta(name='viewport', content='width=device-width,initial-scale=1,maximum-scale=1')
meta(name='apple-mobile-web-app-capable', content='yes')
meta(name='apple-mobile-web-app-status-bar-style', content='black')
// Semantic META
meta(name='keywords', content=keywords)
meta(name='description', content=description)
// Facebook META
meta(property='og:site_name', content=title)
meta(property='og:title', content=title)
meta(property='og:description', content=description)
meta(property='og:url', content=url)
meta(property='og:image', content='/img/brand/logo.png')
meta(property='og:type', content='website')
// Twitter META
meta(name='twitter:title', content=title)
meta(name='twitter:description', content=description)
meta(name='twitter:url', content=url)
meta(name='twitter:image', content='/img/brand/logo.png')
// Fav Icon
link(href='/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='https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,900')
//Bower CSS dependencies
each bowerCssFile in bowerCssFiles
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
each cssFile in cssFiles
link(rel='stylesheet', href=cssFile)
// end Application CSS Files
body
block content
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='', defer='')
script(type="text/javascript").
window.heap=window.heap||[],heap.load=function(e,t){window.heap.appid=e,window.heap.config=t=t||{};var r=t.forceSSL||"https:"===document.location.protocol,a=document.createElement("script");a.type="text/javascript",a.async=!0,a.src=(r?"https:":"http:")+"//cdn.heapanalytics.com/js/heap-"+e+".js";var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(a,n);for(var o=function(e){return function(){heap.push([e].concat(Array.prototype.slice.call(arguments,0)))}},p=["addEventProperties","addUserProperties","clearEventProperties","identify","removeEventProperty","setEventProperties","track","unsetEventProperty"],c=0;c<p.length;c++)heap[p[c]]=o(p[c])};
heap.load("2213510609");

View File

@ -1,62 +0,0 @@
doctype html
html(lang='en', xmlns='http://www.w3.org/1999/xhtml')
head
title #{title}
// General META
meta(charset='utf-8')
meta(http-equiv='Content-type', content='text/html;charset=UTF-8')
meta(http-equiv='X-UA-Compatible', content='IE=edge,chrome=1')
meta(name='viewport', content='width=device-width,initial-scale=1,maximum-scale=1')
meta(name='apple-mobile-web-app-capable', content='yes')
meta(name='apple-mobile-web-app-status-bar-style', content='black')
// Semantic META
meta(name='keywords', content='#{keywords}')
meta(name='description', content='#{description}')
// Facebook META
meta(property='og:site_name', content='#{title}')
meta(property='og:title', content='#{title}')
meta(property='og:description', content='#{description}')
meta(property='og:url', content='{{url}}')
meta(property='og:image', content='/img/brand/logo.png')
meta(property='og:type', content='website')
// Twitter META
meta(name='twitter:title', content='#{title}')
meta(name='twitter:description', content='#{description}')
meta(name='twitter:url', content='{{url}}')
meta(name='twitter:image', content='/img/brand/logo.png')
// Fav Icon
link(href='/static/modules/core/img/brand/favicon.ico', rel='shortcut icon', type='image/x-icon')
link(rel='stylesheet', href='https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,900')
//Application CSS Files
each cssFile in cssFiles
link(rel='stylesheet', href='#{cssFile}')
// end Application CSS Files
body
redoc(spec-url='/static/swagger.json')
script(src='https://rebilly.github.io/ReDoc/releases/latest/redoc.min.js')
//Bower JS dependencies
each bowerJSFile in bowerJSFiles
script(type='text/javascript', src='#{bowerJSFile}')
// end Bower JS dependencies
script(type='text/javascript', src='https://cdnjs.cloudflare.com/ajax/libs/angular-strap/2.3.8/angular-strap.min.js')
if process.env.NODE_ENV === 'development'
script(src='https://cdn.ravenjs.com/2.3.0/angular/raven.min.js')
script Raven.config('https://825fefd6b4ed4a4da199c1b832ca845c@sentry.tellform.com/2').install();
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='', defer='')
script(type="text/javascript").
window.heap=window.heap||[],heap.load=function(e,t){window.heap.appid=e,window.heap.config=t=t||{};var r=t.forceSSL||"https:"===document.location.protocol,a=document.createElement("script");a.type="text/javascript",a.async=!0,a.src=(r?"https:":"http:")+"//cdn.heapanalytics.com/js/heap-"+e+".js";var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(a,n);for(var o=function(e){return function(){heap.push([e].concat(Array.prototype.slice.call(arguments,0)))}},p=["addEventProperties","addUserProperties","clearEventProperties","identify","removeEventProperty","setEventProperties","track","unsetEventProperty"],c=0;c<p.length;c++)heap[p[c]]=o(p[c])};
heap.load("2213510609");

View File

@ -1,64 +0,0 @@
<!DOCTYPE html>
<html>
<head>@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700&subset=latin,latin-ext);
<style>
a {
color: #007ee6;
text-decoration: none;
}
</style>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
</head>
<body style="padding: 0; width: 100% !important; -webkit-text-size-adjust: 100%; margin: 0; -ms-text-size-adjust: 100%;" marginheight="0" marginwidth="0">
<center>
<table cellpadding="8" cellspacing="0" style="*width: 540px; padding: 0; width: 100% !important; background: #ffffff; margin: 0; background-color: #ffffff;" border="0">
<tr>
<td valign="top">
<table cellpadding="0" cellspacing="0" style="border-radius: 6px; -webkit-border-radius: 6px; border: 1px #c0c0c0 solid; -moz-border-radius: 6px;" border="0" align="center">
<tr>
<td colspan="3" height="6"></td>
</tr>
<tr>
<td>
<table cellpadding="0" cellspacing="0" style="line-height: 25px;" border="0" align="center">
<tr>
<td colspan="3" height="30"></td>
</tr>
<tr>
<td width="36"></td>
<td width="454" align="left" style="color: #444444; border-collapse: collapse; font-size: 11pt; font-family: 'Open Sans', 'Lucida Grande', 'Segoe UI', Arial, Verdana, 'Lucida Sans Unicode', Tahoma, 'Sans Serif'; max-width: 454px;" valign="top">
<p>Hello there!</p>
<p>This is a courtesy message to confirm that your password was just changed.</p>
<p>Thanks so much for using our services! If you have any questions, or suggestions, please feel free to email us here at&nbsp;<a href="mailto:team@tellform.com">team@tellform.com</a>.</p>
<p> - The #{appName} team</p>
</td>
<td width="36"></td>
</tr>
<tr>
<td colspan="3" height="36"></td>
</tr>
</table>
</td>
</tr>
</table>
<table cellpadding="0" cellspacing="0" align="center" border="0">
<tr>
<td height="10"></td>
</tr>
<tr>
<td style="padding: 0; border-collapse: collapse;">
<table cellpadding="0" cellspacing="0" align="center" border="0">
<tr style="color: #c0c0c0; font-size: 11px; font-family: 'Open Sans', 'Lucida Grande', 'Segoe UI', Arial, Verdana, 'Lucida Sans Unicode', Tahoma, 'Sans Serif'; -webkit-text-size-adjust: none;">
<td width="400" align="left"></td>
<td width="128" align="right">© TellForm 2017</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</center>
</body>
</html>

View File

@ -1,41 +0,0 @@
doctype html
html
head
body(style='padding: 0; width: 100% !important; -webkit-text-size-adjust: 100%; margin: 0; -ms-text-size-adjust: 100%', marginheight='0', marginwidth='0')
center
table(cellpadding='8', cellspacing='0', style='*width: 540px; padding: 0; width: 100% !important; background: #ffffff; margin: 0; background-color: #ffffff', border='0')
tbody
tr
td(valign='top')
table(cellpadding='0', cellspacing='0', style='border-radius: 6px; -webkit-border-radius: 6px; border: 1px #c0c0c0 solid; -moz-border-radius: 6px', border='0', align='center')
tbody
tr
td(colspan='3', height='6')
tr
td
table(cellpadding='0', cellspacing='0', style='line-height: 25px', border='0', align='center')
tbody
tr
td(colspan='3', height='30')
tr
td(width='36')
td(width='454', align='left', style="color: #444444; border-collapse: collapse; font-size: 11pt; font-family: 'Open Sans', 'Lucida Grande', 'Segoe UI', Arial, Verdana, 'Lucida Sans Unicode', Tahoma, 'Sans Serif'; max-width: 454px", valign='top')
p=__('EMAIL_GREETING')
p=__('RESET_PASSWORD_CONFIRMATION_EMAIL_BODY_1')
p=__("VERIFICATION_EMAIL_PARAGRAPH_2")
a(href='mailto:team@tellform.com') team@tellform.com
p=__('EMAIL_SIGNATURE')
td(width='36')
tr
td(colspan='3', height='36')
table(cellpadding='0', cellspacing='0', align='center', border='0')
tbody
tr
td(height='10')
tr
td(style='padding: 0; border-collapse: collapse')
table(cellpadding='0', cellspacing='0', align='center', border='0')
tbody
tr(style="color: #c0c0c0; font-size: 11px; font-family: 'Open Sans', 'Lucida Grande', 'Segoe UI', Arial, Verdana, 'Lucida Sans Unicode', Tahoma, 'Sans Serif'; -webkit-text-size-adjust: none")
td(width='400', align='left')
td(width='128', align='right') © TellForm 2017

View File

@ -1,66 +0,0 @@
<!DOCTYPE html>
<html>
<head>@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700&subset=latin,latin-ext);
<style>
a {
color: #007ee6;
text-decoration: none;
}
</style>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
</head>
<body style="padding: 0; width: 100% !important; -webkit-text-size-adjust: 100%; margin: 0; -ms-text-size-adjust: 100%;" marginheight="0" marginwidth="0">
<center>
<table cellpadding="8" cellspacing="0" style="*width: 540px; padding: 0; width: 100% !important; background: #ffffff; margin: 0; background-color: #ffffff;" border="0">
<tr>
<td valign="top">
<table cellpadding="0" cellspacing="0" style="border-radius: 6px; -webkit-border-radius: 6px; border: 1px #c0c0c0 solid; -moz-border-radius: 6px;" border="0" align="center">
<tr>
<td colspan="3" height="6"></td>
</tr>
<tr>
<td>
<table cellpadding="0" cellspacing="0" style="line-height: 25px;" border="0" align="center">
<tr>
<td colspan="3" height="30"></td>
</tr>
<tr>
<td width="36"></td>
<td width="454" align="left" style="color: #444444; border-collapse: collapse; font-size: 11pt; font-family: 'Open Sans', 'Lucida Grande', 'Segoe UI', Arial, Verdana, 'Lucida Sans Unicode', Tahoma, 'Sans Serif'; max-width: 454px;" valign="top">
<p>Hello there!</p>
<p>Here is a special link that will allow you to reset your password. Please note it will expire in one hour for your protection:</p>
<p><a href="{{url}}">Reset Your Password</a></p>
<p>If you did not request this, please ignore this email and your password will remain unchanged.</p>
<p>Thanks so much for using our services! If you have any questions, or suggestions, please feel free to email us here at&nbsp;<a href="mailto:team@tellform.com">team@tellform.com</a>.</p>
<p> - The {{appName}} team</p>
</td>
<td width="36"></td>
</tr>
<tr>
<td colspan="3" height="36"></td>
</tr>
</table>
</td>
</tr>
</table>
<table cellpadding="0" cellspacing="0" align="center" border="0">
<tr>
<td height="10"></td>
</tr>
<tr>
<td style="padding: 0; border-collapse: collapse;">
<table cellpadding="0" cellspacing="0" align="center" border="0">
<tr style="color: #c0c0c0; font-size: 11px; font-family: 'Open Sans', 'Lucida Grande', 'Segoe UI', Arial, Verdana, 'Lucida Sans Unicode', Tahoma, 'Sans Serif'; -webkit-text-size-adjust: none;">
<td width="400" align="left"></td>
<td width="128" align="right">© TellForm 2017</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</center>
</body>
</html>

View File

@ -1,44 +0,0 @@
doctype html
html
head
body(style='padding: 0; width: 100% !important; -webkit-text-size-adjust: 100%; margin: 0; -ms-text-size-adjust: 100%', marginheight='0', marginwidth='0')
center
table(cellpadding='8', cellspacing='0', style='*width: 540px; padding: 0; width: 100% !important; background: #ffffff; margin: 0; background-color: #ffffff', border='0')
tbody
tr
td(valign='top')
table(cellpadding='0', cellspacing='0', style='border-radius: 6px; -webkit-border-radius: 6px; border: 1px #c0c0c0 solid; -moz-border-radius: 6px', border='0', align='center')
tbody
tr
td(colspan='3', height='6')
tr
td
table(cellpadding='0', cellspacing='0', style='line-height: 25px', border='0', align='center')
tbody
tr
td(colspan='3', height='30')
tr
td(width='36')
td(width='454', align='left', style="color: #444444; border-collapse: collapse; font-size: 11pt; font-family: 'Open Sans', 'Lucida Grande', 'Segoe UI', Arial, Verdana, 'Lucida Sans Unicode', Tahoma, 'Sans Serif'; max-width: 454px", valign='top')
p=__('EMAIL_GREETING')
p=__('RESET_PASSWORD_REQUEST_EMAIL_PARAGRAPH_1')
p
a(href=url)=__('RESET_PASSWORD_REQUEST_EMAIL_LINK_TEXT')
p=__('RESET_PASSWORD_REQUEST_EMAIL_PARAGRAPH_2')
p=__('VERIFICATION_EMAIL_PARAGRAPH_2')
a(href='mailto:team@tellform.com') team@tellform.com
p=__('EMAIL_SIGNATURE')
td(width='36')
tr
td(colspan='3', height='36')
table(cellpadding='0', cellspacing='0', align='center', border='0')
tbody
tr
td(height='10')
tr
td(style='padding: 0; border-collapse: collapse')
table(cellpadding='0', cellspacing='0', align='center', border='0')
tbody
tr(style="color: #c0c0c0; font-size: 11px; font-family: 'Open Sans', 'Lucida Grande', 'Segoe UI', Arial, Verdana, 'Lucida Sans Unicode', Tahoma, 'Sans Serif'; -webkit-text-size-adjust: none")
td(width='400', align='left')
td(width='128', align='right') © TellForm 2017

View File

@ -1,49 +0,0 @@
html
head
style.
a {
color: #007ee6;
text-decoration: none;
}
meta(content='text/html; charset=utf-8', http-equiv='Content-Type')
body(style='padding: 0; width: 100% !important; -webkit-text-size-adjust: 100%; margin: 0; -ms-text-size-adjust: 100%', marginheight='0', marginwidth='0')
center
table(cellpadding='8', cellspacing='0', style='*width: 540px; padding: 0; width: 100% !important; background: #ffffff; margin: 0; background-color: #ffffff', border='0')
tbody
tr
td(valign='top')
table(cellpadding='0', cellspacing='0', style='border-radius: 6px; -webkit-border-radius: 6px; border: 1px #c0c0c0 solid; -moz-border-radius: 6px', border='0', align='center')
tbody
tr
td(colspan='3', height='6')
tr
td
table(cellpadding='0', cellspacing='0', style='line-height: 25px', border='0', align='center')
tbody
tr
td(colspan='3', height='30')
tr
td(width='36')
td(width='454', align='left', style="color: #444444; border-collapse: collapse; font-size: 11pt; font-family: 'Open Sans', 'Lucida Grande', 'Segoe UI', Arial, Verdana, 'Lucida Sans Unicode', Tahoma, 'Sans Serif'; max-width: 454px", valign='top')
p=__('EMAIL_GREETING')
p=__('VERIFICATION_EMAIL_PARAGRAPH_1')
p
a(href='http://${URL}')=__('VERIFICATION_EMAIL_LINK_TEXT')
p=__('VERIFICATION_EMAIL_PARAGRAPH_2')
a(href='mailto:team@tellform.com')
| team@tellform.com
p=__('EMAIL_SIGNATURE')
td(width='36')
tr
td(colspan='3', height='36')
table(cellpadding='0', cellspacing='0', align='center', border='0')
tbody
tr
td(height='10')
tr
td(style='padding: 0; border-collapse: collapse')
table(cellpadding='0', cellspacing='0', align='center', border='0')
tbody
tr(style="color: #c0c0c0; font-size: 11px; font-family: 'Open Sans', 'Lucida Grande', 'Segoe UI', Arial, Verdana, 'Lucida Sans Unicode', Tahoma, 'Sans Serif'; -webkit-text-size-adjust: none")
td(width='400', align='left')
td(width='128', align='right') © TellForm 2017

View File

@ -1,48 +0,0 @@
html
head
style.
a {
color: #007ee6;
text-decoration: none;
}
meta(content='text/html; charset=utf-8', http-equiv='Content-Type')
body(style='padding: 0; width: 100% !important; -webkit-text-size-adjust: 100%; margin: 0; -ms-text-size-adjust: 100%', marginheight='0', marginwidth='0')
center
table(cellpadding='8', cellspacing='0', style='*width: 540px; padding: 0; width: 100% !important; background: #ffffff; margin: 0; background-color: #ffffff', border='0')
tbody
tr
td(valign='top')
table(cellpadding='0', cellspacing='0', style='border-radius: 6px; -webkit-border-radius: 6px; border: 1px #c0c0c0 solid; -moz-border-radius: 6px', border='0', align='center')
tbody
tr
td(colspan='3', height='6')
tr
td
table(cellpadding='0', cellspacing='0', style='line-height: 25px', border='0', align='center')
tbody
tr
td(colspan='3', height='30')
tr
td(width='36')
td(width='454', align='left', style="color: #444444; border-collapse: collapse; font-size: 11pt; font-family: 'Open Sans', 'Lucida Grande', 'Segoe UI', Arial, Verdana, 'Lucida Sans Unicode', Tahoma, 'Sans Serif'; max-width: 454px", valign='top')
p=__('EMAIL_GREETING')
p=__('WELCOME_EMAIL_PARAGRAPH_1')
p=__('WELCOME_EMAIL_PARAGRAPH_2')
a(href='mailto:team@tellform.com')
| team@tellform.com
p=__('EMAIL_SIGNATURE')
td(width='36')
tr
td(colspan='3', height='36')
table(cellpadding='0', cellspacing='0', align='center', border='0')
tbody
tr
td(height='10')
tr
td(style='padding: 0; border-collapse: collapse')
table(cellpadding='0', cellspacing='0', align='center', border='0')
tbody
tr(style="color: #c0c0c0; font-size: 11px; font-family: 'Open Sans', 'Lucida Grande', 'Segoe UI', Arial, Verdana, 'Lucida Sans Unicode', Tahoma, 'Sans Serif'; -webkit-text-size-adjust: none")
td(width='400', align='left')
td(width='128', align='right') © TellForm 2017

View File

@ -1,62 +0,0 @@
{
"name": "tellform",
"description": "Opensource alternative to TypeForm",
"version": "2.0.0",
"homepage": "https://github.com/tellform/tellform",
"authors": [
"David Baldwynn <polydaic@gmail.com> (http://baldwynn.me)"
],
"license": "MIT",
"private": true,
"appPath": "public/modules",
"dependencies": {
"bootstrap": "^3.3.7",
"angular-resource": "~1.4.7",
"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",
"v-button": "^1.1.1",
"angular-input-stars": "https://github.com/tellform/angular-input-stars.git#master",
"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",
"ng-translate": "*",
"deep-diff": "^0.3.4",
"jsep": "0.3.1",
"ngclipboard": "^1.1.1",
"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"
},
"overrides": {
"BOWER-PACKAGE": {
"main": "**/*.min.js"
}
}
}

View File

@ -1,27 +0,0 @@
{
"tags": {
"allowUnknownTags": true,
"dictionaries": ["jsdoc","closure"]
},
"source": {
"includePattern": ".+\\.js(doc)?$",
"excludePattern": "(^|\\/|\\\\)_",
"exclude": [ "test/*", "examples/*", "node_modules/*" ]
},
"plugins": ["plugins/markdown"],
"templates": {
"cleverLinks": false,
"monospaceLinks": false,
"default": {
"outputSourceFiles": true
},
"systemName" : "DocStrap",
"footer" : "",
"copyright" : "DocStrap Copyright © 2012-2013 The contributors to the JSDoc3 and DocStrap projects.",
"navType" : "vertical",
"theme" : "flatly",
"linenums" : true,
"collapseSymbols" : false,
"inverseNav" : true
}
}

View File

@ -1,119 +0,0 @@
'use strict';
/**
* Module dependencies.
*/
var _ = require('lodash'),
gruntFile = require('grunt').file,
bowerFiles = require('main-bower-files'),
path = require('path'),
fs = require('fs');
var exists = require('path-exists').sync;
var minBowerFiles = function(type){
return bowerFiles(type).map( function(path, index, arr) {
var newPath = path.replace(/.([^.]+)$/g, '.min.$1');
return exists( newPath ) ? newPath : path;
});
};
/**
* Load app configurations
*/
var exports = _.extend(
require('./env/all'),
require('./env/' + process.env.NODE_ENV) || {}
);
//Load keys from api_keys.js if file exists
if( fs.existsSync('./config/env/api_keys.js') ){
module.exports = _.extend(
exports,
require('./env/api_keys')
);
} else {
module.exports = exports;
}
/**
* Get files by glob patterns
*/
module.exports.getGlobbedFiles = function(globPatterns, removeRoot, addRoot) {
var files = gruntFile.expand(globPatterns);
if (removeRoot) {
files = files.map(function(file) {
if(addRoot) return file.replace(removeRoot, addRoot);
return file.replace(removeRoot, '');
});
}
return files;
};
module.exports.removeRootDir = function(files, removeRoot, addRoot) {
return files.map(function(file) {
if (addRoot) return file.replace(path.join(process.cwd(), removeRoot), addRoot);
return file.replace(path.join(process.cwd(), removeRoot), '');
});
};
/**
* Get the app's bower dependencies
*/
module.exports.getBowerJSAssets = function() {
return this.removeRootDir(minBowerFiles('**/**.js'), 'public/', 'static/');
};
module.exports.getBowerCSSAssets = function() {
return this.removeRootDir(minBowerFiles('**/**.css'), 'public/', 'static/');
};
module.exports.getBowerOtherAssets = function() {
return this.removeRootDir(minBowerFiles('**/!(*.js|*.css|*.less)'), 'public/', 'static/');
};
/**
* Helper Function for getJavascriptAssets and getFormJavaScriptAssets
*/
module.exports._getAssets = function(includeTests, isFormJS){
var unit_tests, js_assets;
if(isFormJS) {
js_assets = this.assets.form_js;
unit_tests = this.assets.form_unit_tests;
} else {
js_assets = this.assets.js;
unit_tests = this.assets.unit_tests;
}
var output = this.getGlobbedFiles(js_assets, 'public/', 'static/');
// To include tests
if (includeTests) {
output = _.union(output, this.getGlobbedFiles(unit_tests));
}
return output;
};
/**
* Get the modules JavaScript files
*/
module.exports.getJavaScriptAssets = function(includeTests) {
return this._getAssets(includeTests, false);
};
/**
* Get the modules Form JavaScript files
*/
module.exports.getFormJavaScriptAssets = function(includeTests) {
return this._getAssets(includeTests, true);
};
/**
* Get the modules CSS files
*/
module.exports.getCSSAssets = function() {
var output = this.getGlobbedFiles(this.assets.css, 'public/', 'static/');
return output;
};

149
config/env/all.js vendored
View File

@ -1,149 +0,0 @@
'use strict';
module.exports = {
app: {
google_analytics_id: process.env.GOOGLE_ANALYTICS_ID || '',
title: process.env.APP_NAME || 'TellForm',
description: process.env.APP_DESC || 'Opensource form builder alternative to TypeForm',
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: ''
}
},
admin:{
email: process.env.ADMIN_EMAIL || 'admin@admin.com',
username: process.env.ADMIN_USERNAME || 'root',
password: process.env.ADMIN_PASSWORD || 'root',
},
redisUrl: process.env.REDIS_URL || 'redis://127.0.0.1:6379',
port: process.env.PORT || 3000,
socketPort: process.env.SOCKET_PORT || 20523,
socketPortExternallyVisible: (process.env.SOCKET_PORT_EXTERN_VISIBLE === 'TRUE'),
templateEngine: 'pug',
signupDisabled: (process.env.SIGNUP_DISABLED === 'TRUE'),
enableClusterMode: (process.env.ENABLE_CLUSTER_MODE === 'TRUE'),
baseUrl: process.env.BASE_URL || 'localhost:3000',
tempUserCollection: 'temporary_users',
mailer: {
from: process.env.MAILER_FROM || 'testing@'+process.env.SPARKPOST_SANDBOX_DOMAIN || 'no-reply@tellform.com',
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 || 465,
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 || ''
}
}
},
subdomainsDisabled: (process.env.SUBDOMAINS_DISABLED === 'TRUE'),
//Sentry DSN Client Key
DSN: process.env.RAVEN_DSN || '',
// The secret should be set to a non-guessable string that
// is used to compute a session hash
sessionSecret: process.env.SESSION_SECRET || 'CHANGE_ME_PLEASE',
// The name of the MongoDB collection to store sessions in
sessionCollection: 'sessions',
// The session cookie settings
sessionCookie: {
path: '/',
httpOnly: true,
// If secure is set to true then it will cause the cookie to be set
// only when SSL-enabled (HTTPS) is used, and otherwise it won't
// set a cookie. 'true' is recommended yet it requires the above
// mentioned pre-requisite.
secure: false,
// Only set the maxAge to null if the cookie shouldn't be expired
// at all. The cookie will expunge when the browser is closed.
maxAge: 24 * 60 * 60 * 1000 // 24 hours
// To set the cookie in a specific domain uncomment the following
// setting:
},
// The session cookie name
sessionName: 'connect.sid',
log: {
// Can specify one of 'combined', 'common', 'dev', 'short', 'tiny'
format: 'combined',
// Stream defaults to process.stdout
// Uncomment to enable logging to a log on the file system
fileLogger: {
directoryPath: process.cwd(),
fileName: 'app.log',
maxsize: 10485760,
maxFiles: 2,
json: false
}
},
assets: {
css: [
'public/modules/**/css/*.css'
],
//Order matters here as some directives in form_modules override those in modules
js: [
'public/config.js',
'public/application.js',
'public/dist/populate_template_cache.js',
'public/modules/*/*.js',
'public/modules/*/*/*.js',
'public/modules/*/*/*/*.js',
'public/modules/*/*/*/*/*.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',
'public/form-application.js',
'public/dist/form_populate_template_cache.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: [
'public/modules/**/*.html',
'public/form_modules/forms/base/**/*.html'
],
unit_tests: [
'public/lib/angular-mocks/angular-mocks.js',
'public/modules/*/tests/unit/**/*.js'
],
e2e_tests: [
'public/modules/*/tests/e2e/**.js'
],
form_unit_tests: [
'public/lib/angular-mocks/angular-mocks.js',
'public/form_modules/*/tests/unit/**/*.js'
],
form_e2e_tests: [
'public/form_modules/*/tests/e2e/**.js'
]
}
};

View File

@ -1,37 +0,0 @@
'use strict';
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: ''
}
},
log: {
// Can specify one of 'combined', 'common', 'dev', 'short', 'tiny'
format: 'dev',
// Stream defaults to process.stdout
// Uncomment to enable logging to a log on the file system
},
mailer: {
from: process.env.MAILER_FROM || 'no-reply@tellform.com',
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 || 465,
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 || ''
}
}
}
};

View File

@ -1,35 +0,0 @@
'use strict';
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',
},
port: process.env.PORT || 5000,
socketUrl: process.env.SOCKET_URL || 'ws.tellform.com',
socketPortExternallyVisible: (process.env.SOCKET_PORT_EXTERN_VISIBLE === 'TRUE' || !process.env.SOCKET_PORT_EXTERN_VISIBLE), //socketPortExternallyVisible set to true in production config by default
socketPort: process.env.SOCKET_PORT || 20523,
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
fileLogger: {
directoryPath: process.cwd(),
fileName: 'app.log',
maxsize: 10485760,
maxFiles: 2,
json: false
}
},
sessionCookie: {
secure: false,
maxAge: 24 * 60 * 60 * 1000, // 24 hours
domain: process.env.BASE_URL || '.tellform.com'
},
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']
}
};

60
config/env/secure.js vendored
View File

@ -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 || ''
}
}
}
};

46
config/env/test.js vendored
View File

@ -1,46 +0,0 @@
'use strict';
module.exports = {
baseUrl: '127.0.0.1:3001',
db: {
uri: 'mongodb://localhost/mean-test',
options: {
user: '',
pass: ''
}
},
port: 3001,
log: {
// Can specify one of 'combined', 'common', 'dev', 'short', 'tiny'
format: 'dev',
// Stream defaults to process.stdout
// Uncomment to enable logging to a log on the file system
options: {
//stream: 'access.log'
}
},
app: {
title: 'TellForm Test'
},
sessionCookie: {
maxAge: 24 * 60 * 60 * 1000 // 24 hours
},
mailer: {
from: process.env.MAILER_FROM || '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 || ''
}
}
}
};

View File

@ -1,371 +0,0 @@
'use strict';
/**
* Module dependencies.
*/
var fs = require('fs'),
https = require('https'),
express = require('express'),
morgan = require('morgan'),
logger = require('./logger'),
bodyParser = require('body-parser'),
session = require('express-session'),
compression = require('compression'),
methodOverride = require('method-override'),
cookieParser = require('cookie-parser'),
helmet = require('helmet'),
passport = require('passport'),
raven = require('raven'),
MongoStore = require('connect-mongo')(session),
config = require('./config'),
consolidate = require('consolidate'),
path = require('path'),
client = new raven.Client(config.DSN),
i18n = require('i18n');
var mongoose = require('mongoose');
/**
* Configure Socket.io
*/
var configureSocketIO = function (app, db) {
// Load the Socket.io configuration
var server = require('./socket.io')(app, db);
// Return server object
return server;
};
var supportedLanguages = ['en', 'de', 'fr', 'it', 'es'];
function containsAnySupportedLanguages(preferredLanguages){
for (var i = 0; i < preferredLanguages.length; i++) {
var currIndex = supportedLanguages.indexOf(preferredLanguages[i]);
if (currIndex > -1) {
return supportedLanguages[currIndex];
}
}
return null;
}
module.exports = function(db) {
// Initialize express app
var app = express();
var url = require('url');
// Globbing model files
config.getGlobbedFiles('./app/models/**/*.js').forEach(function(modelPath) {
require(path.resolve(modelPath));
});
// Setting application local variables
app.locals.google_analytics_id = config.app.google_analytics_id;
app.locals.title = config.app.title;
app.locals.signupDisabled = config.signupDisabled;
app.locals.description = config.app.description;
app.locals.keywords = config.app.keywords;
app.locals.subdomainsDisabled = config.subdomainsDisabled;
if(config.socketPortExternallyVisible){
app.locals.socketPort = config.socketPort;
} else {
app.locals.socketPort = '';
}
if(config.socketUrl){
app.locals.socketUrl = config.socketUrl;
}
app.locals.bowerJSFiles = config.getBowerJSAssets();
app.locals.bowerCssFiles = config.getBowerCSSAssets();
app.locals.bowerOtherFiles = config.getBowerOtherAssets();
app.locals.jsFiles = config.getJavaScriptAssets();
app.locals.formJSFiles = config.getFormJavaScriptAssets();
app.locals.cssFiles = config.getCSSAssets();
app.use(function (req, res, next) {
var urlPath;
if(!config.subdomainsDisabled) {
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);
}
// continue if no subdomains
if (!subdomains.length) {
return next();
}
urlPath = url.parse(req.url).path.split('/');
if (urlPath.indexOf('static') > -1) {
urlPath.splice(1, 1);
req.root = req.protocol + '://' + config.baseUrl + urlPath.join('/');
return next();
}
if (urlPath.indexOf('users') > -1 && urlPath.indexOf('me') > -1) {
return next();
}
if (subdomains.indexOf('stage') > -1 || subdomains.indexOf('admin') > -1) {
return next();
}
if (subdomains.indexOf('api') > -1) {
// rebuild url
subdomainPath += 'api' + req.url;
// TODO: check path and query strings are preserved
// reassign url
req.url = subdomainPath;
return next();
}
User.findOne({username: req.subdomains.reverse()[0]}).exec(function (err, user) {
if (err) {
req.subdomains = null;
// Error page
return res.status(404).render('404', {
error: 'Page Does Not Exist'
});
}
if (user === null) {
// Error page
return res.status(404).render('404', {
error: 'Page Does Not Exist'
});
}
// rebuild url
subdomainPath += subdomains.join('/') + req.url;
// TODO: check path and query strings are preserved
// reassign url
req.url = subdomainPath;
// Q.E.D.
return next();
});
} else {
urlPath = url.parse(req.url).path.split('/');
if (urlPath.indexOf('static') > -1 && urlPath.indexOf('view') === urlPath.indexOf('static')-1) {
urlPath.splice(1, 1);
req.url = urlPath.join('/');
}
return next();
}
});
//Setup Prerender.io
app.use(require('prerender-node').set('prerenderToken', process.env.PRERENDER_TOKEN));
// Passing the request url to environment locals
app.use(function(req, res, next) {
if(config.baseUrl === ''){
config.baseUrl = req.protocol + '://' + req.headers.host;
}
res.locals.url = req.protocol + '://' + req.headers.host + req.url;
next();
});
// Should be placed before express.static
app.use(compression({
// only compress files for the following content types
filter: function(req, res) {
return (/json|text|javascript|css/).test(res.getHeader('Content-Type'));
},
// zlib option for compression level
level: 9
}));
//Setup i18n
i18n.configure({
locales: supportedLanguages,
directory: __dirname + '/locales',
defaultLocale: 'en',
cookie: 'userLang'
});
app.use(i18n.init);
app.use(function(req, res, next) {
// express helper for natively supported engines
res.locals.__ = res.__ = function() {
return i18n.__.apply(req, arguments);
};
next();
});
// Set template engine as defined in the config files
app.engine('server.view.pug', consolidate.pug);
// Set views path and view engine
app.set('view engine', 'server.view.pug');
app.set('views', './app/views');
// Enable logger (morgan)
app.use(morgan(logger.getLogFormat(), logger.getMorganOptions()));
// Environment dependent middleware
if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test') {
// Disable views cache
app.set('view cache', false);
} else if (process.env.NODE_ENV === 'production') {
app.locals.cache = 'memory';
app.set('view cache', true);
}
// Request body parsing middleware should be above methodOverride
app.use(bodyParser.urlencoded({
extended: true,
limit: '100mb'
}));
app.use(bodyParser.json({ limit: '100mb' }));
app.use(methodOverride());
// Use helmet to secure Express headers
app.use(helmet.frameguard());
app.use(helmet.xssFilter());
app.use(helmet.noSniff());
app.use(helmet.ieNoOpen());
app.use(helmet.dnsPrefetchControl());
app.use(helmet.hidePoweredBy());
// 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());
// Express MongoDB session storage
app.use(session({
saveUninitialized: true,
resave: true,
secret: config.sessionSecret,
store: new MongoStore({
mongooseConnection: db.connection,
collection: config.sessionCollection
}),
cookie: config.sessionCookie,
name: config.sessionName
}));
// use passport session
app.use(passport.initialize());
app.use(passport.session());
//Visitor Language Detection
app.use(function(req, res, next) {
var acceptLanguage = req.headers['accept-language'];
var languages, supportedLanguage;
if(acceptLanguage){
languages = acceptLanguage.match(/[a-z]{2}(?!-)/g) || [];
supportedLanguage = containsAnySupportedLanguages(languages);
}
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 });
}
next();
});
// Globbing routing files
config.getGlobbedFiles('./app/routes/**/*.js').forEach(function(routePath) {
require(path.resolve(routePath))(app);
});
// Add headers for Sentry
app.use(function (req, res, next) {
// Website you wish to allow to connect
res.setHeader('Access-Control-Allow-Origin', 'https://sentry.io');
// Request methods you wish to allow
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
// Request headers you wish to allow
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
// Set to true if you need the website to include cookies in the requests sent
// to the API (e.g. in case you use sessions)
res.setHeader('Access-Control-Allow-Credentials', true);
// Pass to next layer of middleware
next();
});
// Sentry (Raven) middleware
app.use(raven.middleware.express.requestHandler(config.DSN));
// Should come before any other error middleware
app.use(raven.middleware.express.errorHandler(config.DSN));
// Assume 'not found' in the error msgs is a 404. this is somewhat silly, but valid, you can do whatever you like, set properties, use instanceof etc.
app.use(function(err, req, res, next) {
// If the error object doesn't exists
if (!err) {
return next();
}
// Log it
client.captureError(err);
// Error page
res.status(500).render('500', {
__: i18n.__,
error: err.stack
});
});
// Assume 404 since no middleware responded
app.use(function(req, res) {
client.captureError(new Error('Page Not Found'));
res.status(404).render('404', {
url: req.originalUrl,
error: 'Not Found',
__: i18n.__
});
});
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
return app;
};

View File

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

View File

@ -1,21 +0,0 @@
{
"404_HEADER": "404 - Seite nicht gefunden",
"500_HEADER": "500 - Interner Serverfehler",
"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_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_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",
"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_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."
}

View File

@ -1,22 +0,0 @@
{
"500_HEADER": "500 - Internal Server Error",
"404_HEADER": "404 - Page not found",
"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_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_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",
"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_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.",
"RESET_PASSWORD_REQUEST_EMAIL_PARAGRAPH_1": "Here is a special link that will allow you to reset your password. Please note it will expire in one hour for your protection:",
"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"
}

View File

@ -1,21 +0,0 @@
{
"404_HEADER": "404 - Página no encontrada",
"500_HEADER": "500 - Error interno del servidor",
"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_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_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",
"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_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á".
}

View File

@ -1,21 +0,0 @@
{
"404_HEADER": "404 - Page non trouvée",
"500_HEADER": "500 - Erreur interne du serveur",
"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_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_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",
"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_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é.",
"RESET_PASSWORD_REQUEST_EMAIL_PARAGRAPH_1": "Voici un lien spécial qui vous permettra de réinitialiser votre mot de passe. Veuillez noter qu'il expirera dans une heure pour votre protection :",
"RESET_PASSWORD_REQUEST_EMAIL_LINK_TEXT": "Réinitialiser votre mot de passe",
"RESET_PASSWORD_REQUEST_EMAIL_PARAGRAPH_2": "Si vous ne l'avez pas demandé, veuillez ignorer cet e-mail et votre mot de passe restera inchangé."
}

View File

@ -1,21 +0,0 @@
{
"404_HEADER": "404 - Pagina non trovata",
"500_HEADER": "500 - Errore Server Interno",
"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_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_TEXT": "Verifica il tuo account facendo clic sul seguente collegamento o copiandolo e incollandolo nel tuo browser: $ {URL}",
"EMAIL_SIGNATURE": "- Il team TellForm",
"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_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."
}

View File

@ -1,22 +0,0 @@
{
"500_HEADER": "500 - Internt Serverfel",
"404_HEADER": "404 - Sidan hittades inte",
"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_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_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",
"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_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.",
"RESET_PASSWORD_REQUEST_EMAIL_PARAGRAPH_1": "Här är en speciell länk som kommer tillåta dig att återställa ditt lösenord. Vänligen notera att det kommer utgå om en timma för din säkerhet:",
"RESET_PASSWORD_REQUEST_EMAIL_LINK_TEXT": "Återställ Ditt Lösenord",
"RESET_PASSWORD_REQUEST_EMAIL_PARAGRAPH_2": "Om du inte begärde detta, vänligen ignorera detta meddelande och ditt lösenord kommer att förbli oförändrat.",
"RESET_PASSWORD_CONFIRMATION_EMAIL_BODY_1": "RESET_PASSWORD_CONFIRMATION_EMAIL_BODY_1"
}

View File

@ -1,137 +0,0 @@
'use strict';
var _ = require('lodash'),
config = require('./config'),
chalk = require('chalk'),
fs = require('fs'),
winston = require('winston');
// list of valid formats for the logging
var validFormats = ['combined', 'common', 'dev', 'short', 'tiny'];
// Instantiating the default winston application logger with the Console
// transport
var logger = new winston.Logger({
transports: [
new winston.transports.Console({
level: 'info',
colorize: true,
showLevel: true,
handleExceptions: true,
humanReadableUnhandledException: true
})
],
exitOnError: false
});
// A stream object with a write function that will call the built-in winston
// logger.info() function.
// Useful for integrating with stream-related mechanism like Morgan's stream
// option to log all HTTP requests to a file
logger.stream = {
write: function(msg) {
logger.info(msg);
}
};
/**
* Instantiate a winston's File transport for disk file logging
*
*/
logger.setupFileLogger = function setupFileLogger() {
var fileLoggerTransport = this.getLogOptions();
if (!fileLoggerTransport) {
return false;
}
try {
// Check first if the configured path is writable and only then
// instantiate the file logging transport
if (fs.openSync(fileLoggerTransport.filename, 'a+')) {
logger.add(winston.transports.File, fileLoggerTransport);
}
return true;
} catch (err) {
if (process.env.NODE_ENV !== 'test') {
console.log();
console.log(chalk.red('An error has occured during the creation of the File transport logger.'));
console.log(chalk.red(err));
console.log();
}
return false;
}
};
/**
* The options to use with winston logger
*
* Returns a Winston object for logging with the File transport
*/
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')) {
console.log('unable to find logging file configuration');
return false;
}
var logPath = configFileLogger.directoryPath + '/' + configFileLogger.fileName;
return {
level: 'debug',
colorize: false,
filename: logPath,
timestamp: true,
maxsize: configFileLogger.maxsize ? configFileLogger.maxsize : 10485760,
maxFiles: configFileLogger.maxFiles ? configFileLogger.maxFiles : 2,
json: (_.has(configFileLogger, 'json')) ? configFileLogger.json : false,
eol: '\n',
tailable: true,
showLevel: true,
handleExceptions: true,
humanReadableUnhandledException: true
};
};
/**
* The options to use with morgan logger
*
* Returns a log.options object with a writable stream based on winston
* file logging transport (if available)
*/
logger.getMorganOptions = function getMorganOptions() {
return {
stream: logger.stream
};
};
/**
* The format to use with the logger
*
* Returns the log.format option set in the current environment configuration
*/
logger.getLogFormat = function getLogFormat() {
var format = config.log && config.log.format ? config.log.format.toString() : 'combined';
if (!_.includes(validFormats, format)) {
if (process.env.NODE_ENV !== 'test') {
console.log();
console.log(chalk.yellow('Warning: An invalid format was provided. The logger will use the default format of "' + format + '"'));
console.log();
}
}
return format;
};
logger.setupFileLogger();
module.exports = logger;

View File

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

View File

@ -1,50 +0,0 @@
'use strict';
var passport = require('passport');
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 (!user)
return res.status(401).send(info.message || '');
req.login(user, function(loginErr) {
if (loginErr) return res.sendStatus(500);
req.user = user;
return next();
});
})(req, res, next);
} else {
return res.sendStatus(401);
}
};
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');
};

View File

@ -1,38 +0,0 @@
'use strict';
// Load the module dependencies
var config = require('./config'),
path = require('path'),
http = require('http'),
socketio = require('socket.io');
// Define the Socket.io configuration method
module.exports = function (app, db) {
var server = http.createServer(app);
var io;
// make it possible to only expose one domain
if (process.env.SOCKET_PORT != process.env.PORT) {
io = socketio(config.socketPort, { transports: ['websocket', 'polling'] });
} else {
io = socketio(server, { transports: ['websocket', 'polling'] });
}
if(config.enableClusterMode){
var redis = require('socket.io-redis');
if( process.env.REDIS_DB_PORT_6379_TCP_ADDR ){
io.adapter(redis({ host: process.env.REDIS_DB_PORT_6379_TCP_ADDR || '127.0.0.1' , port: process.env.REDIS_DB_PORT_6379_TCP_PORT || 6379 }));
} else {
io.adapter(redis( config.redisUrl ));
}
}
// Add an event listener to the 'connection' event
io.on('connection', function (socket) {
config.getGlobbedFiles('./app/sockets/**.js').forEach(function (socketConfiguration) {
require(path.resolve(socketConfiguration))(io, socket);
});
});
return server;
};

View File

@ -1,12 +0,0 @@
'use strict';
/**
* Module dependencies.
*/
var passport = require('passport'),
AnonymousStrategy = require('passport-anonymous').Strategy;
module.exports = function() {
// Use local strategy
passport.use(new AnonymousStrategy());
};

View File

@ -1,25 +0,0 @@
'use strict';
var passport = require('passport');
var LocalAPIKeyStrategy = require('passport-localapikey-update').Strategy;
var User = require('mongoose').model('User');
module.exports = function() {
passport.use(new LocalAPIKeyStrategy({
passReqToCallback : true
}, function(req, apiKey, done) {
return User.findOne({
'apiKey': apiKey
}, function(err, user) {
if (err)
return done(err);
if (!user)
return done(null, false, {
message: 'Unknown API Key'
});
return done(null, user);
});
}));
};

View File

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

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

View File

@ -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

View File

@ -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)

39
docker-compose.yml Normal file
View File

@ -0,0 +1,39 @@
# WARNING!! This is to build from source
# to use a version that is prebuilt copy one of the entries from the examples folder!
version: "3"
services:
redis:
image: redis
ohmyform:
build: .
environment:
CREATE_ADMIN: "TRUE"
MAILER_URI: smtp://mail:1025
DATABASE_DRIVER: postgres
DATABASE_URL: postgresql://root:root@db:5432/ohmyform
LOGIN_NOTE: "login with root:root!"
SECRET_KEY: 12345
links:
- db
- redis
- mail
ports:
- "5200:3000"
depends_on:
- db
- redis
mail:
image: mailhog/mailhog
ports:
- "5051:8025"
db:
image: postgres:10-alpine
volumes:
- ./pg_data:/var/lib/postgresql/data
environment:
POSTGRES_USER: root
POSTGRES_PASSWORD: root
POSTGRES_DB: ohmyform

View File

@ -1,32 +0,0 @@
## TODO: add a compose examples directory with various deployment options.
version: "3"
services:
redis:
image: redis
mongo:
image: mongo
volumes:
- "./data/mongo:/data"
tellform:
image: ohmyform/ohmyform
environment:
CREATE_ADMIN: "TRUE"
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
links:
- mongo
- redis
- mail
ports:
- "5000:5000"
depends_on:
- mongo
- redis
mail:
image: mailhog/mailhog
ports:
- "5050:8025"

View File

@ -43,7 +43,7 @@ http {
{% endif %}
location / {
proxy_pass http://tellform:20523;
proxy_pass http://ohmyform:20523;
proxy_read_timeout 90;
proxy_set_header Host $host;
@ -59,7 +59,7 @@ http {
{% if TLS and not TLS_ERROR %}
proxy_set_header X-Forwarded-Proto https;
{% endif %}
}
}
{% if TLS_FLAVOR == 'letsencrypt' %}
location ^~ /.well-known/acme-challenge/ {
@ -94,9 +94,9 @@ http {
root /usr/share/nginx/html;
index index.html index.htm;
location / {
proxy_pass http://tellform:5000;
location / {
proxy_pass http://ohmyform:5000;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
@ -106,7 +106,7 @@ http {
proxy_set_header X-Forwarded-Proto https;
{% endif %}
}
{% if TLS_FLAVOR == 'letsencrypt' %}
location ^~ /.well-known/acme-challenge/ {
proxy_pass http://127.0.0.1:8008;

View File

@ -12,7 +12,7 @@ command = [
"-m", "{}@{}".format(os.environ["POSTMASTER"], os.environ["DOMAIN"]),
"certonly", "--standalone",
"--server", "https://acme-v02.api.letsencrypt.org/directory",
"--cert-name", "tellform",
"--cert-name", "ohmyform",
"--preferred-challenges", "http", "--http-01-port", "8008",
"--keep-until-expiring",
"--rsa-key-size", "4096",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

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