Compare commits

..

188 Commits

Author SHA1 Message Date
04d9b0bf68 Creating a simpler API endpoint for embedded parsing 2016-09-12 21:13:06 -07:00
6957ab7ac1 Door log filtering, user case-insensitive and status-insensitive sorting, new user scope
Presumably changed by meznak or blhack, not myself
2016-09-12 20:56:31 -07:00
96a748ba0b Adding note to MAC registration 2016-02-11 20:29:13 -07:00
2cd1966250 Board member changes to registration forms and reports 2016-02-11 20:27:28 -07:00
f19e822d88 Merge branch 'master' of github.com:zyphlar/Open-Source-Access-Control-Web-Interface 2015-10-27 09:46:45 -07:00
6e13ec34ef Updating paypal csv import to new type 2015-10-27 09:43:27 -07:00
Will Bradley
63a9b1b2f0 Update and rename README.rdoc to README.md 2015-06-30 12:07:14 -07:00
5a8375581f It appears that the PayPal CSV has magically changed formats to no longer include a preceding space in the column names, which gets turned into a preceding underscore.
The first 6 lines of the header do need to be removed, however.
2015-06-30 11:49:32 -07:00
3097803d05 Removing "looks like you haven't paid" alert 2015-01-04 21:05:48 -07:00
40102775ea Fixing mac index unauth 2014-11-20 13:22:39 -07:00
d522d0a43b Fix 404 error on macs/index unauthenticated 2014-11-20 13:18:58 -07:00
c976a6c216 Adding payment index 2014-11-20 13:13:38 -07:00
d1c5dba667 Adding indexes and speeding up doorlog query 2014-11-20 12:56:28 -07:00
98b9db5fe1 Merge branch 'master' of github.com:zyphlar/Open-Source-Access-Control-Web-Interface 2014-11-20 12:38:19 -07:00
cd84e33e56 Switching back to group mail 2014-10-21 20:27:34 -07:00
a6f6e2e141 Merge branch 'master' of github.com:heatsynclabs/Open-Source-Access-Control-Web-Interface 2014-10-15 13:44:52 -07:00
6334928779 Making door alert / webpage status work as expected 2014-10-07 19:55:34 -07:00
85ec806b22 Emailing board instead of discussion list 2014-09-26 21:24:45 -07:00
b42236d228 HTMLifying alert text properly 2014-09-11 21:03:46 -07:00
e0e43ccdbd Adjusting email 2014-09-04 19:40:00 -07:00
3ce0d3c45a Adding door alert emails (test mode) and adjusting door log graph view 2014-09-04 19:24:33 -07:00
3cb1d4e730 Merge pull request #2 from zyphlar/master
Updating hsl repo
2014-07-06 17:01:02 -07:00
1d6699ae9f Increasing visible detail of mac log 2014-06-24 12:20:17 -07:00
3a028ca7fc Updating with https 2014-04-21 11:12:12 -07:00
b23de1c1a2 Styling the members-without-cards report 2014-04-21 11:03:59 -07:00
409227f2d5 Adjusting user summary styling 2014-04-21 10:58:22 -07:00
6198ad419c Adjusting display of most active card 2014-04-16 16:07:16 -07:00
57a5a00352 Adding graph to door log (open/closed) 2014-04-14 18:09:02 -07:00
2f4872218e Making contracts awesome 2014-04-12 15:05:57 -07:00
daeb7713e0 Removing force_ssl for /space_api and /macs 2014-04-11 13:15:42 -07:00
0cd63b085b Merge branch 'master' of github.com:zyphlar/Open-Source-Access-Control-Web-Interface 2014-04-10 23:24:39 -07:00
a95c1220bf Forcing ssl 2014-04-10 23:24:47 -07:00
7e8c8e1bb6 Merge branch 'master' of github.com:zyphlar/Open-Source-Access-Control-Web-Interface 2014-04-10 23:21:40 -07:00
11f837cf7e HTTPS 2014-04-10 23:20:50 -07:00
18a52b4fca Merge branch 'master' of github.com:zyphlar/Open-Source-Access-Control-Web-Interface 2014-04-10 21:05:29 -07:00
b3279299cf Adjusting payment graph 2014-04-10 21:05:14 -07:00
cb0f203f82 Updating final user views 2014-04-10 21:01:42 -07:00
bedd80d555 Updating user index with new layout 2014-04-10 20:47:25 -07:00
8f069cee4d Removing faker 2014-04-10 20:13:21 -07:00
16c75b2d0f Fixing issue #19 for realsies (oriented by tracking) 2014-04-10 20:10:04 -07:00
13e72fef23 Fixing issue #19 and also styling homepage 2014-04-10 19:45:29 -07:00
83442c4bff Merge branch 'master' of github.com:zyphlar/Open-Source-Access-Control-Web-Interface 2014-04-10 19:19:48 -07:00
74d3fc45dd Adding faker 2014-04-10 19:19:28 -07:00
d4286e4ddf Improving logging of macs 2014-03-12 20:46:24 -07:00
3cd6c59df0 More contract tweaks 2014-03-02 21:48:49 -07:00
d8311fa7cc Merge branch 'master' of github.com:zyphlar/Open-Source-Access-Control-Web-Interface 2014-03-02 19:42:10 -07:00
0cfa83a9f7 Display changes for contract 2014-03-02 19:41:57 -07:00
691089dd1c Merge branch 'master' of github.com:zyphlar/Open-Source-Access-Control-Web-Interface 2014-03-02 19:39:21 -07:00
e8ef430733 Working around temporary lack of last names in users 2014-03-02 19:39:04 -07:00
20594d651c Gemfile updates, schema update 2014-03-02 19:17:09 -07:00
cac59e9de5 Finishing contracts, changing from s3.yml to .env 2014-03-02 19:06:39 -07:00
c74da562bc Adding contracts 2014-02-27 04:13:53 -07:00
095b6d3965 Adding interlock authentication 2014-02-23 05:55:00 -07:00
c5556a0d50 Adding bio to summary 2014-02-14 04:04:28 -07:00
03a0588073 Creating sweet user summary page 2014-02-14 03:48:03 -07:00
17aa2b971c Changing pirate icon 2014-02-14 02:14:22 -07:00
d943adc458 Creating user summary page 2014-02-14 02:12:13 -07:00
8425aaecd2 Adding pictures to resources 2014-02-14 01:09:16 -07:00
8a31655fd3 Merge branch 'master' of github.com:zyphlar/Open-Source-Access-Control-Web-Interface 2014-02-14 00:02:53 -07:00
3d97e92eb7 Adding resource category edit abilities 2014-02-14 00:02:11 -07:00
3f5a7012bd Removing required postal code
Was causing issues with resetting passwords
2014-02-12 20:11:21 -07:00
fb47188af2 Upgrading to ruby-version from rvmrc 2014-02-10 21:07:44 -07:00
bff2be7cc9 Updating readme with arp-scan doc 2014-02-09 05:20:08 -07:00
ee7e79a433 Allowing people to view/edit 2014-02-09 05:13:35 -07:00
2c4cd4a020 Mooooore resources 2014-02-09 05:01:52 -07:00
a2cbcac798 More resource tweaks 2014-02-09 04:32:45 -07:00
23dcb0715c More resource adding 2014-02-09 03:42:17 -07:00
74e60d4ec9 Adding bootstrap 2014-02-09 02:10:12 -07:00
e61895d20f Chmod 2014-02-09 02:09:03 -07:00
58926cf3ab Updating resources 2014-02-09 02:08:08 -07:00
e16e35e098 Massive chmod/chown 2014-02-09 00:56:48 -07:00
1d0e8721e1 Adding resources (imported from old toolshare) 2014-02-09 00:54:03 -07:00
56450cf319 Adding postal code to users 2014-02-08 21:06:07 -07:00
92c3a6d2be Adding db seed, fixing migration issues 2014-02-08 19:13:45 -07:00
73833dd2bf Updating door log key
Adding reboot logging
2014-01-31 18:41:52 -07:00
c6bb164f73 Fixing MAC form and index 2014-01-24 04:27:21 -07:00
aa29f30f30 Allowing users to see member activity 2014-01-23 17:50:00 -07:00
a2a9d082d8 Adding json formats to space api access 2014-01-21 19:38:11 -07:00
8060f76cca Adjusting icon and adding meta tag for icon 2013-12-29 22:28:54 -07:00
f7d26ab69e Adding iOS bookmark icon 2013-12-29 22:20:00 -07:00
6157280655 Changing door access tablet interface 2013-12-29 21:56:47 -07:00
cbadcc5c73 Fixing user-emailing abilities 2013-12-13 03:34:52 -07:00
ef590793f2 Card upload would fail due to being logged out halfway thru; changed arduino auth mechanism 2013-12-03 01:42:08 -07:00
d807aad668 Merge pull request #1 from zyphlar/master
Getting the HSL copy up to date
2013-12-02 01:26:33 -08:00
41a8431be5 Adding direct door access commands and interface 2013-12-02 02:03:40 -07:00
d2434be109 Moving spaceapi endpoint and OAC door status checking here 2013-12-01 19:32:08 -07:00
9dc8645c32 Update README.rdoc 2013-10-26 03:21:48 -07:00
02b997b838 Adding mac history graph 2013-10-26 03:22:03 -07:00
d88abe7d32 Whups, integer error in text email causing signup failures 2013-10-22 01:38:57 -07:00
0d1894d6e8 Updating payment and front page status alerts to work properly 2013-10-10 20:58:19 -07:00
008ceaed9e Updating emailer 2013-10-06 05:51:41 -07:00
15836b4f4e Adding zombie report 2013-10-06 05:17:24 -07:00
57e6ac1fc5 Adding user email feature 2013-10-06 04:09:56 -07:00
04bab84293 Adding new member report 2013-10-05 19:53:31 -07:00
d61289abaf Adding analytics, adjusting door log issues 2013-10-04 02:59:41 -07:00
75da7ffb6a Wording on graph 2013-10-02 05:01:30 -07:00
0eeaddabd7 Adjusting wording and stat counts 2013-10-02 04:57:46 -07:00
370c82e075 Wording 2013-10-02 04:46:35 -07:00
7f7cd6e9ff Adjusting wording, styling, and graph visibility 2013-10-02 04:45:07 -07:00
f11d5ebe1e Adding individual statistics/data-dumps 2013-10-02 04:31:10 -07:00
4da2ec9463 Tweaking main page stats to show member counts 2013-09-28 20:17:38 -07:00
7cd33ff030 Also cancan issue in mac controller 2013-09-28 19:36:23 -07:00
7bfd45a163 Added more_info notice and fixed payments cancan bug 2013-09-28 19:34:08 -07:00
42679aa410 Updating settings & fixing auth of objects through other objects cancan bug? 2013-09-28 19:31:28 -07:00
9e08a0d036 Updated stats and privacy 2013-09-28 18:55:22 -07:00
2c204a8f9d Adding wysiwyg and settings 2013-09-28 04:59:19 -07:00
f03ffcd356 Adding settings 2013-09-28 04:24:24 -07:00
b267da7b37 Style 2013-09-28 04:18:52 -07:00
ec84d8f5e0 Button style 2013-09-28 04:13:21 -07:00
e002907da7 Fixed graph 2013-09-28 04:12:43 -07:00
5acb3cd272 Updating payment graph 2013-09-28 03:33:36 -07:00
16e5e8d584 Adding payment graph 2013-09-28 02:42:30 -07:00
b5383eaafb Readme update 2013-09-28 02:42:14 -07:00
d1985dae12 Adding payment stats 2013-09-28 01:30:52 -07:00
4631489eba Changing card stats to month and adding runner up 2013-09-27 01:19:58 -07:00
e61ac5d32e Adding most active mac and runner up stats 2013-09-27 00:56:52 -07:00
3ddfb3442b Tabs with spaces 2013-09-25 21:45:12 -07:00
530f4834ca Changing user exit reason to fulltext 2013-09-24 23:14:50 -07:00
3c02724488 Fixing string comparison on member level 2013-09-23 23:55:16 -07:00
ec139a97a8 Separating certs into columns 2013-09-23 20:24:38 -07:00
c5e02a51be Making it possible for users to easily register existing MACs 2013-09-23 18:51:22 -07:00
cad46e45c5 Adding users without cards report, fixing mac registration for existing macs 2013-09-23 18:34:18 -07:00
377170d47c Adjusting sql for macs for postgres compatibility 2013-09-22 01:08:05 -07:00
df0e91c4ee Migrations and tweaks to facilitate sqlite-postgres switch 2013-09-22 00:50:41 -07:00
84525ca65f Updating email address 2013-09-21 02:38:03 -07:00
faaeb2b4a2 Allowing nil social media urls 2013-08-29 16:28:57 -07:00
c61b8ac8d4 Adding recent users report 2013-08-29 02:50:05 -07:00
8820dc4986 Adding activity and form fields for user profiles 2013-08-29 02:27:48 -07:00
37aba522cf Profile tweaks 2013-08-29 01:42:26 -07:00
d1ba469767 Adjusting profile display 2013-08-29 01:38:40 -07:00
f4868a32ae Social icons 2013-08-29 01:22:06 -07:00
880488c73e Adding social and exit reasons 2013-08-29 01:23:21 -07:00
2231d124a6 Style tweak and stats 2013-08-28 08:40:36 -07:00
accec4edb1 Merge branch 'csv'
Conflicts:
	app/models/user_certification.rb
	app/views/user_certifications/index.html.erb
	app/views/user_certifications/show.html.erb
2013-08-28 08:21:28 -07:00
25c0d1e1cb Finalized payments and CSVs and inactivity 2013-08-28 08:19:01 -07:00
e8e024c042 Adding CSV show 2013-08-28 05:14:10 -07:00
10a1e4eb84 Switching to RVM, View tweaks 2013-08-28 05:15:50 -07:00
211d79cbcd Adding csv import 2013-08-28 04:25:54 -07:00
69a57bc63b Merge branch 'ipn' of github.com:zyphlar/Open-Source-Access-Control-Web-Interface into ipn 2013-08-28 03:19:09 -07:00
c662be6dc0 IPN improvements 2013-08-28 03:18:47 -07:00
ca7808a525 Merge branch 'ipn' of github.com:zyphlar/Open-Source-Access-Control-Web-Interface into ipn 2013-08-27 23:06:43 -07:00
9fb2507aa1 Updating coin 2013-08-27 23:06:16 -07:00
0be2834a5d Minor status tweak 2013-08-27 00:39:43 -07:00
ef46bf6a98 Adjusting ranking method 2013-08-27 00:09:54 -07:00
45bac6cae4 Merge branch 'ipn' of github.com:zyphlar/Open-Source-Access-Control-Web-Interface into ipn 2013-08-26 23:40:40 -07:00
4dba2b8c3d Adding status symbols 2013-08-26 23:39:54 -07:00
18bc427eb3 Adding quick-add buttons to user profiles 2013-08-26 23:01:43 -07:00
810ff6b034 De-duplicating cert view 2013-08-26 22:32:08 -07:00
ae031838dc Merge branch 'ipn' of github.com:zyphlar/Open-Source-Access-Control-Web-Interface into ipn 2013-08-26 22:15:54 -07:00
59b6e3c838 Updating nav 2013-08-26 22:12:45 -07:00
98be42e9f9 Fixing nil errors 2013-08-26 22:11:04 -07:00
b5d9514914 Merge branch 'ipn' of github.com:zyphlar/Open-Source-Access-Control-Web-Interface into ipn 2013-08-26 22:09:37 -07:00
125ad76a1c Hacks to allow 1.8.7 2013-08-26 22:07:41 -07:00
4067477cd4 Updating icons, fixing user cert nil error 2013-08-26 22:05:53 -07:00
9e4b79a353 Merge branch 'ipn' of github.com:zyphlar/Open-Source-Access-Control-Web-Interface into ipn 2013-08-26 15:39:18 -07:00
f8f11e3d7e Adjust IPN security 2013-08-26 15:34:10 -07:00
805148ee40 Merge branch 'ipn' of github.com:zyphlar/Open-Source-Access-Control-Web-Interface into ipn 2013-08-24 03:44:00 -07:00
f111769b20 Remove debug, update schema 2013-08-24 03:43:43 -07:00
eb782f11d5 Adding IPN validation 2013-08-24 03:40:34 -07:00
a1ef8a5fd5 User cert tweaks (handling nulls) 2013-08-24 02:23:12 -07:00
ec4cf4dea9 Adding IPNs 2013-08-24 02:18:37 -07:00
75e4907a9c Usability tweaks, creating merges, fixing issues 2013-05-23 23:25:09 -07:00
20a007cac5 Instructors can delete their own certs; hidden users are hidden in drop-downs; admins can see last user login; prepping for postgres 2013-05-03 00:16:02 -07:00
719b9447ab Merge branch 'pamela' of github.com:zyphlar/Open-Source-Access-Control-Web-Interface into pamela 2013-02-18 23:29:44 -07:00
8a7fe29f6d Changing lapsed member math to be more accurate & give more info 2013-02-18 23:29:06 -07:00
e630c81298 Resolving nil error for access cards / logs 2013-02-14 23:00:01 -07:00
cefd4b3878 Added nice front page stuff, fixing sqlite issues, adding user payments to profiles 2013-02-14 00:29:22 -07:00
ed75ea0e90 Adding payments 2013-02-12 01:58:17 -07:00
3fb774d057 Adding note to macs json feed 2013-02-09 05:05:06 -07:00
0ced399651 Adjusted profile page to make avatar more clear 2013-02-09 04:21:42 -07:00
02920837e2 Updated new user email to include survey and link to user 2013-02-09 04:11:58 -07:00
63913c0be3 Forgot migration from previous commit. Also added links to home page 2013-02-09 04:01:21 -07:00
ac66cb0cbe Added "how did you hear about us" to user. 2013-02-09 03:47:07 -07:00
1f63709887 Reporting door access logs on each card's page; also card access stats for last 7 days 2013-02-09 03:32:26 -07:00
3f3eb1ed65 Allowed admins to see hidden users, added "no orientation" message to main page and hid new people from non-oriented people 2013-02-09 02:51:35 -07:00
f1b752a4c4 Updating homepage to display cooler door/mac stats 2013-02-09 02:40:38 -07:00
2d0735e914 Replaced "authok" with "ok" on OAC-Ethernet API, changing to match 2013-02-09 02:08:43 -07:00
d156edd683 Updating how macs are stored/viewed 2013-02-09 02:08:29 -07:00
1b64a6b931 Changing email to 2013-02-01 05:03:18 -07:00
1239d6682b Added mailer to notify on new user 2013-02-01 04:44:05 -07:00
50171effad Allowing JSON 2013-02-01 03:58:26 -07:00
43e2cdba78 Finished mac filtering, display, permissions, etc 2013-02-01 03:37:30 -07:00
f3498ddcac Added mac logs, improved mac editing/viewing 2013-02-01 00:06:13 -07:00
04764af983 Got macs working 2013-01-31 22:39:33 -07:00
998558cd30 Figured out mac saving issue 2013-01-31 21:25:54 -07:00
048ce52111 Renaming pamela to macs 2013-01-31 20:48:27 -07:00
a7e999614c Adding pamela but I'm dumb and messed up routing 2013-01-31 20:33:40 -07:00
6673573e36 Updated front page stats to fix dupes 2013-01-31 19:15:35 -07:00
464 changed files with 41248 additions and 444 deletions

8
.gitignore vendored
View File

@ -9,13 +9,19 @@
# Ignore the default SQLite database.
/db/*.sqlite3
/db/*.sqlite3.*
# Ignore all logfiles and tempfiles.
/log/*.log
/tmp
tmp/
# Ignore compiled assets
/public/assets
# Ignore config file
# Ignore config and database files (passwords)
/config/config.yml
/config/s3.yml
/config/database.yml
/config/initializers/secret_token.rb
.env

1
.ruby-gemset Normal file
View File

@ -0,0 +1 @@
members-hsl

1
.ruby-version Normal file
View File

@ -0,0 +1 @@
ruby-1.9.3-p385

16
Gemfile
View File

@ -1,11 +1,16 @@
source 'https://rubygems.org'
gem 'rails', '3.2.3'
ruby '1.9.3'
gem 'rails', '3.2.8'
gem 'dotenv-rails'
# Bundle edge Rails instead:
# gem 'rails', :git => 'git://github.com/rails/rails.git'
gem 'sqlite3'
gem 'pg'
gem 'taps'
gem 'json'
@ -40,7 +45,12 @@ gem 'bcrypt-ruby', '~> 3.0.0'
# gem 'capistrano'
# To use debugger
# gem 'ruby-debug'
#gem 'debugger'
#gem "paperclip", "~> 3.0"
gem "paperclip", "~> 3.0"
gem "aws-sdk"
gem 'gravtastic'
gem 'passenger'
gem "rails-settings-cached", "0.2.4"

View File

@ -1,131 +1,177 @@
GEM
remote: https://rubygems.org/
specs:
actionmailer (3.2.3)
actionpack (= 3.2.3)
actionmailer (3.2.8)
actionpack (= 3.2.8)
mail (~> 2.4.4)
actionpack (3.2.3)
activemodel (= 3.2.3)
activesupport (= 3.2.3)
actionpack (3.2.8)
activemodel (= 3.2.8)
activesupport (= 3.2.8)
builder (~> 3.0.0)
erubis (~> 2.7.0)
journey (~> 1.0.1)
journey (~> 1.0.4)
rack (~> 1.4.0)
rack-cache (~> 1.2)
rack-test (~> 0.6.1)
sprockets (~> 2.1.2)
activemodel (3.2.3)
activesupport (= 3.2.3)
sprockets (~> 2.1.3)
activemodel (3.2.8)
activesupport (= 3.2.8)
builder (~> 3.0.0)
activerecord (3.2.3)
activemodel (= 3.2.3)
activesupport (= 3.2.3)
activerecord (3.2.8)
activemodel (= 3.2.8)
activesupport (= 3.2.8)
arel (~> 3.0.2)
tzinfo (~> 0.3.29)
activeresource (3.2.3)
activemodel (= 3.2.3)
activesupport (= 3.2.3)
activesupport (3.2.3)
activeresource (3.2.8)
activemodel (= 3.2.8)
activesupport (= 3.2.8)
activesupport (3.2.8)
i18n (~> 0.6)
multi_json (~> 1.0)
arel (3.0.2)
aws-sdk (1.33.0)
json (~> 1.4)
nokogiri (>= 1.4.4)
uuidtools (~> 2.1)
bcrypt-ruby (3.0.1)
builder (3.0.0)
cancan (1.6.8)
builder (3.0.4)
cancan (1.6.10)
climate_control (0.0.3)
activesupport (>= 3.0)
cocaine (0.5.3)
climate_control (>= 0.0.3, < 1.0)
coffee-rails (3.2.2)
coffee-script (>= 2.2.0)
railties (~> 3.2.0)
coffee-script (2.2.0)
coffee-script-source
execjs
coffee-script-source (1.3.3)
devise (2.1.1)
coffee-script-source (1.6.3)
daemon_controller (1.1.5)
devise (2.2.7)
bcrypt-ruby (~> 3.0)
orm_adapter (~> 0.1)
railties (~> 3.1)
warden (~> 1.2.1)
dotenv (0.10.0)
dotenv-rails (0.10.0)
dotenv (= 0.10.0)
erubis (2.7.0)
execjs (1.4.0)
multi_json (~> 1.0)
execjs (2.0.2)
gravtastic (3.2.6)
hike (1.2.1)
i18n (0.6.0)
hike (1.2.3)
i18n (0.6.5)
journey (1.0.4)
jquery-rails (2.1.1)
railties (>= 3.1.0, < 5.0)
thor (~> 0.14)
json (1.7.5)
libv8 (3.3.10.4)
jquery-rails (3.0.4)
railties (>= 3.0, < 5.0)
thor (>= 0.14, < 2.0)
json (1.8.1)
libv8 (3.16.14.3)
mail (2.4.4)
i18n (>= 0.4.0)
mime-types (~> 1.16)
treetop (~> 1.4.8)
mime-types (1.19)
multi_json (1.3.6)
orm_adapter (0.1.0)
mime-types (1.25)
mini_portile (0.5.2)
multi_json (1.8.2)
nokogiri (1.6.1)
mini_portile (~> 0.5.0)
orm_adapter (0.4.0)
paperclip (3.5.4)
activemodel (>= 3.0.0)
activesupport (>= 3.0.0)
cocaine (~> 0.5.3)
mime-types
passenger (4.0.19)
daemon_controller (>= 1.1.0)
rack
rake (>= 0.8.1)
pg (0.17.0)
polyglot (0.3.3)
rack (1.4.1)
rack (1.4.5)
rack-cache (1.2)
rack (>= 0.4)
rack-ssl (1.3.2)
rack-ssl (1.3.3)
rack
rack-test (0.6.1)
rack-test (0.6.2)
rack (>= 1.0)
rails (3.2.3)
actionmailer (= 3.2.3)
actionpack (= 3.2.3)
activerecord (= 3.2.3)
activeresource (= 3.2.3)
activesupport (= 3.2.3)
rails (3.2.8)
actionmailer (= 3.2.8)
actionpack (= 3.2.8)
activerecord (= 3.2.8)
activeresource (= 3.2.8)
activesupport (= 3.2.8)
bundler (~> 1.0)
railties (= 3.2.3)
railties (3.2.3)
actionpack (= 3.2.3)
activesupport (= 3.2.3)
railties (= 3.2.8)
rails-settings-cached (0.2.4)
rails (>= 3.0.0)
railties (3.2.8)
actionpack (= 3.2.8)
activesupport (= 3.2.8)
rack-ssl (~> 1.3.2)
rake (>= 0.8.7)
rdoc (~> 3.4)
thor (~> 0.14.6)
rake (0.9.2.2)
rdoc (3.12)
thor (>= 0.14.6, < 2.0)
rake (10.1.0)
rdoc (3.12.2)
json (~> 1.4)
sass (3.2.1)
sass-rails (3.2.5)
ref (1.0.5)
rest-client (1.6.7)
mime-types (>= 1.16)
sass (3.2.11)
sass-rails (3.2.6)
railties (~> 3.2.0)
sass (>= 3.1.10)
tilt (~> 1.3)
sequel (3.20.0)
sinatra (1.0)
rack (>= 1.0)
sprockets (2.1.3)
hike (~> 1.2)
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
sqlite3 (1.3.6)
therubyracer (0.10.1)
libv8 (~> 3.3.10)
thor (0.14.6)
tilt (1.3.3)
treetop (1.4.10)
sqlite3 (1.3.8)
taps (0.3.24)
rack (>= 1.0.1)
rest-client (>= 1.4.0, < 1.7.0)
sequel (~> 3.20.0)
sinatra (~> 1.0.0)
therubyracer (0.12.0)
libv8 (~> 3.16.14.0)
ref
thor (0.18.1)
tilt (1.4.1)
treetop (1.4.15)
polyglot
polyglot (>= 0.3.1)
tzinfo (0.3.33)
uglifier (1.2.7)
tzinfo (0.3.38)
uglifier (2.2.1)
execjs (>= 0.3.0)
multi_json (~> 1.3)
warden (1.2.1)
multi_json (~> 1.0, >= 1.0.2)
uuidtools (2.1.4)
warden (1.2.3)
rack (>= 1.0)
PLATFORMS
ruby
DEPENDENCIES
aws-sdk
bcrypt-ruby (~> 3.0.0)
cancan
coffee-rails (~> 3.2.1)
devise
dotenv-rails
gravtastic
jquery-rails
json
rails (= 3.2.3)
paperclip (~> 3.0)
passenger
pg
rails (= 3.2.8)
rails-settings-cached (= 0.2.4)
sass-rails (~> 3.2.3)
sqlite3
taps
therubyracer
uglifier (>= 1.0.3)

25
README.md Normal file
View File

@ -0,0 +1,25 @@
Open Access Control Web Interface
==
Web software for managing a database of members in a collaborative grassroots workshop, and also controlling Arclight of 23b Hackerspace's Arduino access control system via Ethernet ( see: https://github.com/zyphlar/Open_Access_Control_Ethernet )
https://github.com/zyphlar/Open-Source-Access-Control-Web-Interface
Copyright Will Bradley, 2012-2014
Distributed under a Creative Commons Attribution 3.0 license http://creativecommons.org/licenses/by/3.0/
![screenshot](https://cloud.githubusercontent.com/assets/48434/8439253/a9a810e6-1f1f-11e5-8b5c-3f0b22f14a9f.png)
Contributions welcome! Simply send a pull request via Github.
To use:
* Install Imagemagick (for Paperclip / image uploads)
* Install arp-scan (for LAN Mac address scanning)
* Load into a Rails 3 environment
* Copy config/config.yml.example to config/config.yml and edit appropriately
* Copy config/database.yml.example to config/database.yml and edit appropriately
* Copy env.example to .env and edit appropriately for your Amazon S3 account OR adjust the resource.rb and contract.rb model settings to use different storage for picture attachments (via Paperclip)
* Copy config/initializers/secret_token.rb.example to config/config/initializers/secret_token.rb and edit appropriately
* See/edit db/seeds.rb for the initial admin account info.
* Run bundle install, rake db:migrate, rake db:seed, etc.

View File

@ -1,15 +0,0 @@
== Open Access Control Web Interface
Web software for managing a database of members in a collaborative grassroots workshop,
and also controlling Arclight of 23b Hackerspace's Arduino access control system
via Ethernet ( see: https://github.com/zyphlar/Open_Access_Control_Ethernet )
https://github.com/zyphlar/Open-Source-Access-Control-Web-Interface
Copyright Will Bradley, 2012-2013
Distributed under a Creative Commons Attribution 3.0 license http://creativecommons.org/licenses/by/3.0/
To use:
* Load into a Rails 3 environment
* Rename config/config.yml.example to config/config.yml and edit appropriately
* Use the Rails console to create a new User and set user.admin = true
* Run bundle install, rake db:migrate, etc.

0
app/assets/images/logo.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

0
app/assets/images/nil.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 95 B

After

Width:  |  Height:  |  Size: 95 B

0
app/assets/javascripts/application.js Normal file → Executable file
View File

0
app/assets/javascripts/certifications.js.coffee Normal file → Executable file
View File

0
app/assets/javascripts/door_logs.js.coffee Normal file → Executable file
View File

0
app/assets/javascripts/home.js.coffee Normal file → Executable file
View File

View File

@ -0,0 +1,3 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

View File

@ -0,0 +1,3 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

View File

@ -0,0 +1,3 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

View File

@ -0,0 +1,3 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

0
app/assets/javascripts/user_certifications.js.coffee Normal file → Executable file
View File

0
app/assets/javascripts/users.js.coffee Normal file → Executable file
View File

44
app/assets/stylesheets/application.css Normal file → Executable file
View File

@ -11,3 +11,47 @@
*= require_self
*= require_tree .
*/
.caption { display: inline-block; background-color: #eee; border: 1px solid #333; border-radius: 5px; margin-bottom: 1em; }
.btn {
display: inline-block;
padding: 4px 10px 4px;
margin-bottom: 0;
font-size: 13px;
line-height: 18px;
color: #333;
text-align: center;
text-decoration: none;
text-shadow: 0 1px 1px rgba(255,255,255,.75);
vertical-align: middle;
background-color: #f5f5f5;
background-image: -moz-linear-gradient(top, #fff, #e6e6e6);
background-image: -ms-linear-gradient(top, #fff, #e6e6e6);
background-image: -webkit-gradient(linear,0 0,0 100%,from( #fff),to( #e6e6e6));
background-image: -webkit-linear-gradient(top, #fff, #e6e6e6);
background-image: -o-linear-gradient(top, #fff, #e6e6e6);
background-image: linear-gradient(top, #fff, #e6e6e6);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0);
border-color: #e6e6e6 #e6e6e6 #bfbfbf;
border-color: rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);
filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
border: 1px solid #ccc;
border-bottom-color: #bbb;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);
-moz-box-shadow: inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);
box-shadow: inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);
cursor: pointer;
filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
}
.member-status-symbol, .social-icon {
vertical-align: middle;
}
a.social-link:hover {
background: transparent;
}
.lined-table td {
border-bottom: 1px dashed black;
}

0
app/assets/stylesheets/certifications.css.scss Normal file → Executable file
View File

0
app/assets/stylesheets/door_logs.css.scss Normal file → Executable file
View File

1
app/assets/stylesheets/home.css.scss Normal file → Executable file
View File

@ -1,3 +1,4 @@
// Place all the styles related to the home controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
table, td { vertical-align: top }

View File

@ -0,0 +1,3 @@
// Place all the styles related to the Ipn controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

View File

@ -0,0 +1,3 @@
// Place all the styles related to the MacLogs controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

View File

@ -0,0 +1,4 @@
// Place all the styles related to the pamela controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
.hidden { color: #ccc; }

View File

@ -0,0 +1,3 @@
// Place all the styles related to the Payments controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

11
app/assets/stylesheets/scaffolds.css.scss Normal file → Executable file
View File

@ -19,17 +19,6 @@ pre {
font-size: 11px;
}
a {
color: #000;
&:visited {
color: #666;
}
&:hover {
color: #fff;
background-color: #000;
}
}
div {
&.field, &.actions {
margin-bottom: 10px;

0
app/assets/stylesheets/user_certifications.css.scss Normal file → Executable file
View File

10
app/assets/stylesheets/users.css.scss Normal file → Executable file
View File

@ -6,8 +6,16 @@
.hoverinfo { cursor: progress; }
.payment_links { background-color: #ddd; padding: 1em; border-radius: 1em;
display: inline-block; float: right; }
display: inline-block; float: right; max-width: 30%; min-width: 10em;}
.payment-highlighted {
background-color: orange !important;
}
.avatar { height: 2em; width: 2em; }
.avatar-large {
vertical-align: top;
}
textarea { height: 10em; }

34
app/controllers/application_controller.rb Normal file → Executable file
View File

@ -1,15 +1,49 @@
class ApplicationController < ActionController::Base
protect_from_forgery
force_ssl if: :ssl_forced?
def ssl_forced?
# Non-production environments and read-only stuff like the space API and MACs should not require SSL. (APIs hate following 301s).
if Rails.env.development? || Rails.env.test? || ["space_api","macs"].include?(params[:controller])
return false
else
return true
end
end
rescue_from CanCan::AccessDenied do |exception|
if !current_user.nil? && current_user.orientation.blank? then
flash[:alert] = "Sorry, you probably need to complete New Member Orientation before having access to this page. <br/>Please check your email and schedule a New Member Orientation with a volunteer."
else
flash[:alert] = "Nothing to see here!"
end
Rails.logger.warn "----------\r\nWARNING: AccessDenied Exception: #{exception.inspect} User: #{current_user.inspect}\r\n----------"
redirect_to root_url
end
@payment_methods = [[nil],["PayPal"],["Dwolla"],["Bill Pay"],["Check"],["Cash"],["Other"]]
@payment_instructions = {nil => nil, :paypal => "Set up a monthly recurring payment to hslfinances@gmail.com", :dwolla => "Set up a monthly recurring payment to hslfinances@gmail.com", :billpay => "Have your bank send a monthly check to HeatSync Labs Treasurer, 140 W Main St, Mesa AZ 85201", :check => "Mail to HeatSync Labs Treasurer, 140 W Main St, Mesa AZ 85201 OR put in the drop safe at the Lab with a deposit slip firmly attached each month.", :cash => "Put in the drop safe at the Lab with a deposit slip firmly attached each month.", :other => "Hmm... talk to a Treasurer!"}
# Check authorization of a user / sign them in manually
def check_auth(email,password)
resource = User.find_by_email(email)
if resource && resource.valid_password?(password)
resource.remember_me = true
sign_in :user, resource
return true
else
return false
end
end
end
# Add a "fit" function to sanitize inputs for mac history
class Fixnum
def fit(range)
self > range.max ? range.max : (self < range.min ? range.min : self)
end
end

59
app/controllers/cards_controller.rb Normal file → Executable file
View File

@ -1,6 +1,6 @@
class CardsController < ApplicationController
load_and_authorize_resource
before_filter :authenticate_user!
load_and_authorize_resource except: :authorize
before_filter :authenticate_user!, except: :authorize
# GET /cards
# GET /cards.json
@ -9,6 +9,20 @@ class CardsController < ApplicationController
#authorize! :read, @cards
@cards = @cards.sort_by{|e| e[:id]}
if can? :read, DoorLog then
most_active_count = 0
runner_up_count = 0
@most_active_card = nil
@runner_up_card = nil
@cards.each do |card|
card_num_R = card.card_number.to_i(16)%32767
card[:accesses_this_week] = DoorLog.where("key = ? AND data = ? AND created_at > ?", 'G', card_num_R, DateTime.now - 1.month).order("created_at DESC").group_by { |d| d.created_at.beginning_of_day }.count
end
@most_active_cards = @cards.sort{|a,b| b[:accesses_this_week] <=> a[:accesses_this_week]}
@most_active_card = @most_active_cards[0]
@runner_up_card = @most_active_cards[1]
end
respond_to do |format|
format.html # index.html.erb
format.json { render :json => @cards }
@ -18,8 +32,10 @@ class CardsController < ApplicationController
# GET /cards/1
# GET /cards/1.json
def show
#@card = Card.find(params[:id])
if can? :read, DoorLog then
card_num_R = @card.card_number.to_i(16)%32767
@door_logs = DoorLog.where('key = ? AND data = ?', "G", card_num_R).order("created_at DESC")
end
respond_to do |format|
format.html # show.html.erb
format.json { render :json => @card }
@ -95,6 +111,41 @@ class CardsController < ApplicationController
end
end
def authorize
# Stop unless signed in already, OR if the supplied user/pass params are good.
unless current_user || check_auth(params['user'],params['pass'])
@auth = "bad_user_or_pass"
else
# Stop unless the user can access the door system
unless can? :authorize, Card
@auth = "bad_user_permissions"
Rails.logger.warn "----------\r\nWARNING: CARD AUTH ATTEMPT DENIED. USER #{current_user.inspect}\r\n----------"
else
begin
@card = Card.find(:first, :conditions => ["lower(card_number) = ?", params[:id].downcase])
@auth = @card.inspect
if @card && @card.user
@auth = @card.user.has_certification?(params[:device])
else
@auth = false
end
rescue
@auth = false
end
end
end
if @card && @card.user
username = @card.user.name
else
username = nil
end
render json: [@auth, username]
end
# DELETE /cards/1
# DELETE /cards/1.json
def destroy

2
app/controllers/certifications_controller.rb Normal file → Executable file
View File

@ -1,6 +1,6 @@
class CertificationsController < ApplicationController
load_and_authorize_resource :certification
load_and_authorize_resource :user, :through => :certification
#load_and_authorize_resource :user, :through => :certification
before_filter :authenticate_user!
# GET /certifications

View File

@ -0,0 +1,87 @@
class ContractsController < ApplicationController
load_and_authorize_resource :contract
before_filter :authenticate_user!, :load_users
layout 'resources'
def index
if params[:user_id].present?
@contracts = Contract.where(user_id: params[:user_id])
end
respond_to do |format|
format.html
format.json { render :json => @contracts }
end
end
def show
end
def new
end
def edit
end
def create
# if @contract.first_name.blank? && @contract.last_name.blank? && @contract.cosigner.blank? # assume autodetect of filename
# begin
# name_split = params[:contract][:document].original_filename.sub(".jpg","").split
# if name_split.count == 4 # we have one name
# @contract.first_name = name_split[0]
# @contract.last_name = name_split[1]
# # 2 is the hyphen
# @contract.signed_at = Date.parse(name_split[3])
# elsif name_split.count == 7 && name_split[2] == "by" # we have two names
# @contract.first_name = name_split[0]
# @contract.last_name = name_split[1]
# # 2 is "by"
# @contract.cosigner = "#{name_split[3]} #{name_split[4]}"
# # 5 is the hyphen
# @contract.signed_at = Date.parse(name_split[6])
# else
# Rails.logger.info "Couldn't determine name from filename array: #{name_split.inspect}"
# end
# rescue Exception => e
# end
# end
@contract.created_by = current_user
respond_to do |format|
if @contract.save
format.html { redirect_to @contract, :notice => 'Contract was successfully created.' }
format.json { render :json => @contract, :status => :created, :location => @contract }
else
format.html { render :action => "new" }
format.json { render :json => @contract.errors, :status => :unprocessable_entity }
end
end
end
def find_for_user
end
def update
respond_to do |format|
if @contract.update_attributes(params[:contract])
format.html { redirect_to @contract, :notice => 'Contract was successfully updated.' }
format.json { head :no_content }
else
format.html { render :action => "edit" }
format.json { render :json => @contract.errors, :status => :unprocessable_entity }
end
end
end
def destroy
@contract.destroy
respond_to do |format|
format.html { redirect_to contracts_url }
format.json { head :no_content }
end
end
def load_users
@users = User.accessible_by(current_ability).sort_by(&:name)
end
end

32
app/controllers/door_logs_controller.rb Normal file → Executable file
View File

@ -5,7 +5,36 @@ class DoorLogsController < ApplicationController
# GET /door_logs
# GET /door_logs.json
def index
@door_logs = DoorLog.find(:all, :order => "created_at DESC", :limit => 500)
# @door_logs = DoorLog.find(:all, :order => "created_at DESC", :limit => 1000)
@door_logs = DoorLog.where("key NOT LIKE 'alarm%' AND key != 'armed' AND key != 'activated'").order("created_at DESC").limit(1000)
begin
@start_date = DateTime.parse(params[:start])
@end_date = DateTime.parse(params[:end])
rescue
@start_date = DateTime.now - 2.weeks
@end_date = DateTime.now
end
@door_logs_by_hour = DoorLog.where("created_at > ? AND created_at < ? AND (key = ? OR key = ?)", @start_date, @end_date,"door_1_locked","door_2_locked").order("created_at ASC").group_by(&:key)
@door_log_graph = [
@door_logs_by_hour["door_1_locked"].map{|d| [d.created_at.to_time.to_i*1000, 1^d.data.to_i]}, # use XOR to invert 1 into 0 and vice versa
@door_logs_by_hour["door_2_locked"].map{|d| [d.created_at.to_time.to_i*1000, 1^d.data.to_i]}
]
# @door_logs_by_hour.each do |door_log|
# # Add one computer for activate, subtract one for deactivate
# if door_log.data == 1
#
# elsif door_log.data == 0
# mac_running_balance -= 1
# end
# @door_log_graph << [time.to_time.to_i*1000,mac_running_balance]
# end
respond_to do |format|
format.html # index.html.erb
@ -26,6 +55,7 @@ class DoorLogsController < ApplicationController
# GET /door_logs/auto_download
def auto_download
@results = DoorLog.download_from_door
@status = DoorLog.download_status # for space_api
respond_to do |format|
format.html # show.html.erb

62
app/controllers/home_controller.rb Normal file → Executable file
View File

@ -1,18 +1,54 @@
class HomeController < ApplicationController
layout 'resources'
def index
@num_certs = UserCertification.count
@recent_certs = UserCertification.where("created_at > ?", DateTime.now - 7.days).count
@num_users = User.count
@recent_users = User.where("created_at > ?", DateTime.now - 7.days).count
@num_door_opens = DoorLog.where("key = 'G'").count
@recent_door_opens = DoorLog.where("key = 'G' AND created_at > ?", DateTime.now - 7.days).count
@num_door_denieds = DoorLog.where("key = 'D'").count
@recent_door_denieds = DoorLog.where("key = 'D' AND created_at > ?", DateTime.now - 7.days).count
respond_to do |format|
format.html # index.html.erb
def index
# Alerts
if user_signed_in? && current_user.orientation.blank? then
flash[:alert] = "There's a lot more to see here, but our records show you haven't completed the new member orientation yet. If that's incorrect, please contact a volunteer."
end
end
# if user_signed_in? && current_user.member_status.between?(2,100) then
# flash[:alert] = "<!--
# Member: <%= current_user.member.inspect
# Level: <%= current_user.member_level.inspect
# -->
# Looks like we haven't acknowledged a recent payment for you yet. This could be because we're slow, but if in doubt please see your profile for payment instructions, consider updating your membership level to something accurate, or contact us.<br/>Thanks for supporting us!"
# end
# Fun Stats
@featured_resource = Resource.where("picture_file_name IS NOT NULL").sample
@num_certs = UserCertification.count
@recent_certs = UserCertification.where("created_at > ?", DateTime.now - 7.days).count
@num_users = User.count
@recent_users = User.where("created_at > ?", DateTime.now - 7.days).count
# Payments: member levels are multipled by 10 to indicate current payment; 25 x 10 = 250
@num_paid_users = User.all.select{|u| u.member_status >= 250 }.count
@num_plus_users = User.all.select{|u| u.member_status == 1000 }.count
@num_basic_users = User.all.select{|u| u.member_status == 500 }.count
@num_associate_users = User.all.select{|u| u.member_status == 250 }.count
@num_delinquent_users = User.all.select{|u| !u.payment_status }.count
if can? :read, User then
@recent_user_names = User.where("member_level > 10").accessible_by(current_ability).order('created_at desc').limit(5)
end
@num_door_opens = DoorLog.where("key = 'G'").count
@today_door_opens = DoorLog.where("key = 'G' AND created_at > ?", DateTime.now - 1.day).count
@recent_door_opens = DoorLog.where("key = 'G' AND created_at > ?", DateTime.now - 7.days).count
@num_door_denieds = DoorLog.where("key = 'D'").count
@recent_door_denieds = DoorLog.where("key = 'D' AND created_at > ?", DateTime.now - 1.month).count
@num_logins = User.sum('sign_in_count')
@recent_logins = User.where('current_sign_in_at > ?',Date.today - 7.days).count
@num_macs = Mac.count
@recent_macs = Mac.where("since > ?", DateTime.now - 1.day).count
respond_to do |format|
format.html # index.html.erb
end
end
def more_info
respond_to do |format|
format.html # more_info.html.erb
end
end
end

View File

@ -0,0 +1,54 @@
class IpnsController < ApplicationController
load_and_authorize_resource :ipn, :except => [:new, :create]
before_filter :authenticate_user!, :except => [:new, :create]
protect_from_forgery :except => [:create]
def index
@ipns = Ipn.all
end
def show
end
def new
end
def create
@ipn = Ipn.new_from_dynamic_params(params)
@ipn.data = params.to_json
@ipn.save
render :nothing => true
#unless @ipn.validate!
# Rails.logger.error "Unable to validate IPN: #{@ipn.inspect}"
#end
end
def import
@ipn = Ipn.new_from_dynamic_params(params)
@ipn.data = params.to_json
@ipn.save
redirect_to ipn_path(@ipn)
#unless @ipn.validate!
# Rails.logger.error "Unable to validate IPN: #{@ipn.inspect}"
#end
end
def validate
if @ipn.validate!
redirect_to ipns_url, :notice => 'Valid!'
else
redirect_to ipns_url, :notice => 'INVALID'
end
end
def link
result = @ipn.link_payment
if result.first
redirect_to ipns_url, :notice => 'Payment was successfully linked.'
else
redirect_to ipns_url, :notice => result.last
end
end
end

View File

@ -0,0 +1,13 @@
class MacLogsController < ApplicationController
load_and_authorize_resource :mac_log
before_filter :authenticate_user!
def index
@mac_logs = MacLog.desc.limit(1000)
@macs = {}
@mac_logs.each do |log|
@macs.merge!({log.mac => Mac.find_by_mac(log.mac)})
end
end
end

View File

@ -0,0 +1,362 @@
class MacsController < ApplicationController
load_and_authorize_resource :mac, :except => [:index, :create, :history]
#load_and_authorize_resource :user, :through => :mac, :except => [:index, :show, :scan, :import]
before_filter :arp_lookup, :only => :new
#require "active_record"
require "optparse"
#require "rubygems"
def index
recent_mac_logs_ungrouped = MacLog.last(1000)
if recent_mac_logs_ungrouped.present?
@mac_time_start_date = recent_mac_logs_ungrouped.first.created_at
recent_mac_logs = recent_mac_logs_ungrouped.group_by(&:mac)
@mac_times = {}
# Go thru each mac
recent_mac_logs.each do |mac_log|
last_active = nil
# And the entries for each mac (mac_log.first is the string, mac_log.last is the array)
mac_log.last.each do |entry|
# Find an activate followed immediately by a deactivate
if entry.action == "activate"
last_active = entry
else
if last_active && entry.action == "deactivate"
# Calculate the time difference between the two and append to this mac's total time
this_entry = @mac_times[entry.mac]
if this_entry
this_time = this_entry[:time]
else
this_time = 0
end
@mac_times[entry.mac] = {:mac => entry, :time => (entry.created_at - last_active.created_at) + this_time}
else
# No pair found; discard.
last_active = nil
end
end
end
end
@mac_times_sorted = @mac_times.sort{|a,b| b.last[:time] <=> a.last[:time] }
@most_active_mac = nil
@runner_up_mac = nil
@mac_times_sorted.each do |mac_time|
unless @most_active_mac
this_mac = Mac.find_by_mac(mac_time.first)
unless this_mac.hidden
@most_active_mac = this_mac
@most_active = mac_time
end
else
unless @runner_up_mac
this_mac = Mac.find_by_mac(mac_time.first)
unless this_mac.hidden
@runner_up_mac = this_mac
@runner_up = mac_time
end
end
end
end
end
#@active_macs = Mac.where(:active => true, :hidden => false)
#@active_macs += Mac.where(:active => true, :hidden => nil)
# De-dupe users for the public
if can? :update, Mac then
@active_macs = Mac.where("macs.active = ? AND (macs.hidden IS NULL OR macs.hidden = ?)", true, false).includes(:user).order("users.name ASC")
elsif user_signed_in? then
@active_macs = Mac.where("macs.active = ? AND (macs.hidden IS NULL OR macs.hidden = ?)", true, false).includes(:user).order("users.name ASC").group("users.name")
else
@active_macs = Mac.select("mac, note, user_id").where("macs.active = ? AND (macs.hidden IS NULL OR macs.hidden = ?)", true, false).joins(:user).order("users.name ASC").group("users.name, mac, note, user_id")
end
@hidden_macs = Mac.where("macs.active = ? AND macs.hidden = ?", true, true).order("note ASC")
@all_macs = Mac.find(:all, :order => "LOWER(mac)")
respond_to do |format|
format.html
format.json {
@filtered_macs = Mac.select("macs.mac, users.name, macs.note").where("macs.active = ? AND (macs.hidden IS NULL OR macs.hidden = ?)", true, false).joins(:user)
render :json => @filtered_macs
}
end
end
def history
authorize! :read_details, Mac
begin
@start_date = DateTime.parse(params[:start])
@end_date = DateTime.parse(params[:end])
rescue
@start_date = DateTime.now - 2.weeks
@end_date = DateTime.now
end
@mac_logs_by_hour = MacLog.where("created_at > ? AND created_at < ?", @start_date, @end_date).group_by{|m| m.created_at.beginning_of_hour}
@mac_log_graph = []
mac_running_balance = 0
lowest_balance = 0
@mac_logs_by_hour.each do |time, mac_log|
mac_log.each do |entry|
# Add one computer for activate, subtract one for deactivate
if entry.action == "activate"
mac_running_balance += 1
elsif entry.action == "deactivate"
mac_running_balance -= 1
end
# Keep track of the lowest number in the graph
if mac_running_balance < lowest_balance
lowest_balance = mac_running_balance
end
end
@mac_log_graph << [time.to_time.to_i*1000,mac_running_balance]
end
if lowest_balance != 0
# Subtract a negative balance to raise everything
@mac_log_graph = @mac_log_graph.map{ |time,balance| [time, balance - lowest_balance] }
end
respond_to do |format|
format.html
format.json { render :json => @mac_log_graph }
end
end
# GET /macs/1
# GET /macs/1.json
def show
@mac = Mac.find(params[:id])
@mac_logs = MacLog.where(:mac => @mac.mac)
respond_to do |format|
format.html # show.html.erb
format.json { render :json => @macs }
end
end
# GET /macs/new
# GET /macs/new.json
def new
@mac = Mac.new
if can? :manage, Mac then
@users = User.accessible_by(current_ability).sort_by(&:name)
else
@users = [current_user]
end
respond_to do |format|
format.html # new.html.erb
format.json { render :json => @mac }
end
end
# GET /macs/1/edit
def edit
@mac = Mac.find(params[:id])
if can? :manage, Mac then
@users = User.accessible_by(current_ability).sort_by(&:name)
else
@users = [current_user]
end
end
# POST /macs
def create
@mac = Mac.new(params[:mac])
@existing_mac = Mac.find_by_mac(@mac.mac)
if can? :manage, Mac then
@users = User.accessible_by(current_ability).sort_by(&:name)
else
@users = [current_user]
end
if @existing_mac.present?
if @existing_mac.user_id.nil?
redirect_to edit_mac_path(@existing_mac), :notice => 'This MAC already exists, edit it here:'
else
@mac.errors.add(:user,"for this MAC is already set to #{@existing_mac.user.name} -- please contact them or an admin if this is incorrect.")
render :action => "new"
end
else
respond_to do |format|
if @mac.save
format.html { redirect_to macs_path, :notice => 'MAC was successfully created.' }
format.json { render :json => @mac, :status => :created, :location => @mac }
else
format.html { render :action => "new" }
format.json { render :json => @mac.errors, :status => :unprocessable_entity }
end
end
end
end
# PUT /macs/1
# PUT /macs/1.json
def update
#Log who updated this
@mac = Mac.find(params[:id])
@mac.assign_attributes(params[:mac])
#@mac.user_id = params[:mac][:user_id]
authorize! :update, @mac
if can? :manage, Mac then
@users = User.accessible_by(current_ability).sort_by(&:name)
else
@users = [current_user]
end
respond_to do |format|
if @mac.save
format.html { redirect_to macs_path, :notice => 'MAC was successfully updated.' }
format.json { head :no_content }
else
format.html { render :action => "edit" }
format.json { render :json => @mac.errors, :status => :unprocessable_entity }
end
end
end
def arp_lookup
@ip = request.env['REMOTE_ADDR']
@arp = /([0-9A-F]{2}[:-]){5}([0-9A-F]{2})/i.match(%x(arp -a | grep #{@ip}))
end
def scan
Rails.logger.info "starting scan..."
# Command line arguments
options = {};
OptionParser.new { |opts|
opts.banner = "Usage: pamela-scanner.rb --interface=en0"
options[:verbose] = true
opts.on("v", "--verbose", "Run verbosely") { |verbose|
options[:verbose] = verbose
}
options[:interface] = "eth0"
opts.on("i", "--interface=interface", "Network Interface") { |interface|
options[:interface] = interface
}
options[:max_age] = 20
opts.on("a", "--max-age=minutes", "Minutes to keep expired macs active") { |max_age|
options[:max_age] = max_age.to_i
}
options[:db_host] = "configure_me"
opts.on("r", "--db-host=host", "Database Host") { |host|
options[:db_host] = host
}
options[:db_name] = "configure_me"
opts.on("n", "--db-name=name", "Database Name") { |name|
options[:db_name] = name
}
options[:db_user] = "configure_me"
opts.on("u", "--db-user=user", "Database User") { |user|
options[:db_user] = user
}
options[:db_password] = "configure_me"
opts.on("p", "--db-password=password", "Database Password") { |password|
options[:db_password] = password
}
}.parse!
# Open the database
#ActiveRecord::Base::establish_connection(
# :adapter => "mysql2",
# :host => options[:db_host],
# :database => options[:db_name],
# :username => options[:db_user],
# :password => options[:db_password])
#class Mac < ActiveRecord::Base
#end
#class MacLog < ActiveRecord::Base
#end
# Scan the network for mac addresses
macs = {};
command = sprintf("arp-scan -R --interface=%s --localnet", options[:interface])
if options[:verbose]
Rails.logger.info "Running [#{command}]"
end
IO.popen(command) { |stdin|
result = stdin.read()
result.lines.each { |line|
Rails.logger.info "Reading stdin: "+line.inspect
next if line !~ /^([\d\.]+)\s+([[:xdigit:]:]+)\s/;
macs[($2).downcase] = ($1).downcase;
}
Rails.logger.info "STDIN:"+result.lines.count.inspect
@macs = macs.dup # make a copy for output in the view
Rails.logger.info "MACS:"+@macs.inspect
}
# Scan the existing macs and update each record as necessary
Mac.find(:all).each { |entry|
mac = entry.mac.downcase
ip = entry.ip
if macs.has_key?(mac) # if our scan shows this mac
if ! entry.active || ! entry.since
Rails.logger.info "Activating #{mac} at #{ip}" if options[:verbose]
entry.since = Time.now
MacLog.new(:mac => mac, :ip => ip, :action => "activate").save
end
entry.active = 1
entry.ip = ip
entry.refreshed = Time.now
entry.save
macs.delete(mac)
next
end
# Entry is no longer current
if entry.active
ageMinutes = ((Time.now - entry.refreshed)/60).to_i
next if ageMinutes < options[:max_age]
Rails.logger.info "Deactivating #{mac}, #{ageMinutes} minutes old" if options[:verbose]
entry.active = 0
entry.save
MacLog.new(:mac => mac, :ip => ip, :action => "deactivate").save
end
}
# Add entries for any macs not already in the db
macs.each { |mac, ip|
Rails.logger.info "Activating new entry #{mac} at #{ip}" if options[:verbose]
Mac.new(:mac => mac, :ip => ip, :active => 1, :since => Time.now, :refreshed => Time.now).save
Rails.logger.info MacLog.new(:mac => mac, :ip => ip, :action => "activate").save
}
@log = MacLog.all
end #def scan
def import
require 'csv'
csv_text = File.read('mac_log.csv')
csv = CSV.parse(csv_text)
@output = []
csv.each do |row|
@output += [row[1], Mac.create({:mac => row[0], :note => row[1], :hidden => row[2]}) ]
end
end
end

View File

@ -0,0 +1,143 @@
class PaymentsController < ApplicationController
load_and_authorize_resource :payment
#load_and_authorize_resource :user, :through => :payment
before_filter :authenticate_user!
# Load users and certs based on current ability
before_filter do
@users = User.where(:hidden => false).where("member_level > 10").accessible_by(current_ability).sort_by(&:name_with_payee_and_member_level)
end
before_filter :only => [:create, :update] do
@payment.created_by = current_user.id
end
# GET /payments
# GET /payments.json
def index
@payments = @payments.order("date DESC")
@graph = { :members => chart("members"),
:total => chart("total"),
:basic => chart("basic"),
:associate => chart("associate")}
respond_to do |format|
format.html # index.html.erb
format.json { render :json => @payments }
end
end
# Private method for index charts
def chart name
chart_name = name || "total"
if chart_name == "total"
chart_type = [25, 50, 100]
elsif chart_name == "members"
chart_type = [25, 50, 100]
elsif chart_name == "basic"
chart_type = [50]
elsif chart_name == "associate"
chart_type = [25]
else
chart_type = []
end
payment_months = @payments.sort_by(&:date).group_by{ |p| p.date.beginning_of_month }
@payments_by_month = []
payment_months.each do |month|
# Calculate sum of amounts for each month and store at end of month array
@payments_by_month << [month.first.to_time.to_i*1000, month.last.sum{|p|
amount = amount_or_level(p)
if chart_type.include?(amount)
if chart_name == "members"
1 # Output 1 to count members
else
amount # Output dollars to count amount
end
else
0
end
}]
end
return @payments_by_month
end
def amount_or_level p
if p.amount
return p.amount.to_i
else
if p.user
Rails.logger.info p.user.member_level
return p.user.member_level.to_i
else
Rails.logger.info p.inspect
Rails.logger.info p.user.inspect
return 0
end
end
end
# GET /payments/1
# GET /payments/1.json
def show
respond_to do |format|
format.html # show.html.erb
format.json { render :json => @payment }
end
end
# GET /payments/new
# GET /payments/new.json
def new
respond_to do |format|
format.html # new.html.erb
format.json { render :json => @payment }
end
end
# GET /payments/1/edit
def edit
end
# POST /payments
# POST /payments.json
def create
Rails.logger.warn "payment:"
Rails.logger.warn @payment.inspect
respond_to do |format|
if @payment.save
format.html { redirect_to payments_url, :notice => 'Payment was successfully created.' }
format.json { render :json => @payment, :status => :created, :location => @payment }
else
format.html { render :action => "new" }
format.json { render :json => @payment.errors, :status => :unprocessable_entity }
end
end
end
# PUT /payments/1
# PUT /payments/1.json
def update
respond_to do |format|
if @payment.update_attributes(params[:payment])
format.html { redirect_to payments_url, :notice => 'Payment was successfully updated.' }
format.json { head :no_content }
else
format.html { render :action => "edit" }
format.json { render :json => @payment.errors, :status => :unprocessable_entity }
end
end
end
# DELETE /payments/1
# DELETE /payments/1.json
def destroy
@payment.destroy
respond_to do |format|
format.html { redirect_to payments_url }
format.json { head :no_content }
end
end
end

View File

@ -0,0 +1,28 @@
class PaypalCsvsController < ApplicationController
load_and_authorize_resource :paypal_csv
before_filter :authenticate_user!
def index
end
def show
end
def new
end
def create
PaypalCsv.batch_import_from_csv(params[:file].path)
redirect_to paypal_csvs_path, :notice => 'Paypal CSV batch was successfully loaded.'
end
def link
result = @paypal_csv.link_payment
if result.first
redirect_to paypal_csvs_url, :notice => 'Payment was successfully linked.'
else
redirect_to paypal_csvs_url, :notice => result.last
end
end
end

0
app/controllers/registrations_controller.rb Normal file → Executable file
View File

View File

@ -0,0 +1,46 @@
class ResourceCategoriesController < ApplicationController
load_and_authorize_resource
layout 'resources'
def create
authorize! :create, @resource_category
respond_to do |format|
if @resource_category.save
format.html { redirect_to resource_categories_path, :notice => "Category was successfully created." }
format.json { head :no_content }
else
format.html { render :action => "new" }
format.json { render :json => @resource_category.errors, :status => :unprocessable_entity }
end
end
end
def update
authorize! :update, @resource_category
respond_to do |format|
if @resource_category.update_attributes(params[:resource_category])
format.html { redirect_to resource_categories_path, :notice => "Category was successfully updated." }
format.json { head :no_content }
else
format.html { render :action => "edit" }
format.json { render :json => @resource_category.errors, :status => :unprocessable_entity }
end
end
end
def destroy
respond_to do |format|
if @resource_category.destroy
format.html { redirect_to resource_categories_path, :notice => "Category was deleted." }
format.json { head :ok }
else
format.html { redirect_to resource_categories_path, :notice => "Category could not be deleted. #{@resource_category.errors.full_messages.first}." }
format.json { render :json => @resource_category.errors, :status => :unprocessable_entity }
end
end
end
end

View File

@ -0,0 +1,63 @@
class ResourcesController < ApplicationController
load_and_authorize_resource
before_filter :load_users
layout 'resources'
def index
@featured_resource = @resources.where("picture_file_name IS NOT NULL").sample
end
def new
# don't get too excited... for some reason this gets set to the current_user
@resource.user_id = nil
end
def create
@resource.modified_by = current_user.id # log who modified this last
authorize! :create, @resource
respond_to do |format|
if @resource.save
format.html { redirect_to resource_path(@resource), :notice => "Resource was successfully created." }
format.json { head :no_content }
else
format.html { render :action => "new" }
format.json { render :json => @resource.errors, :status => :unprocessable_entity }
end
end
end
def update
@resource.modified_by = current_user.id # log who modified this last
@resource.assign_attributes(params[:resource])
authorize! :update, @resource
respond_to do |format|
if @resource.update_attributes(params[:resource])
format.html { redirect_to resource_path(@resource), :notice => "Resource was successfully updated." }
format.json { head :no_content }
else
format.html { render :action => "edit" }
format.json { render :json => @resource.errors, :status => :unprocessable_entity }
end
end
end
def destroy
@resource.destroy
respond_to do |format|
format.html { redirect_to resources_path, :notice => "Resource was deleted." }
format.json { head :ok }
end
end
def load_users
if can? :assign_user, Resource then
@users = User.accessible_by(current_ability).sort_by(&:name)
else
@users = [current_user]
end
end
end

View File

@ -0,0 +1,32 @@
class SettingsController < ApplicationController
authorize_resource
def index
@settings = Setting.all
@@default_settings.each do |key, value|
if @settings[key].blank?
@settings[key] = value
end
end
end
def edit
value = Setting[params[:id].to_sym]
if value.present?
@setting = {}
@setting[:var] = params[:id]
@setting[:value] = value
elsif @@default_settings[params[:id].to_sym].present?
@setting = {}
@setting[:var] = params[:id]
@setting[:value] = @@default_settings[params[:id].to_sym]
end
end
def update
Setting[params[:id]] = params[:value]
redirect_to settings_path
end
end

View File

@ -0,0 +1,131 @@
class SpaceApiController < ApplicationController
# Individually remove authorizing stuff since there is no SpaceApi model
authorize_resource :except => [:index, :simple, :access, :access_post, :alert_if_not_status]
# User auth here happens via params, instead of form.
before_filter :authenticate_user!, :except => [:index, :simple, :access, :access_post, :alert_if_not_status]
def index
@json = JSON.parse(Setting.space_api_json_template)
door_status = DoorLog.show_status # Expect {:unlocked => boolean, :door_1_locked => boolean, :door_2_locked => boolean}
@json["open"] = door_status[:unlocked]
if( door_status[:unlocked] )
@json["status"] = "doors_open=both"
elsif( !door_status[:door_1_locked] )
@json["status"] = "doors_open=door1"
elsif( !door_status[:door_2_locked] )
@json["status"] = "doors_open=door2"
else
@json["status"] = "doors_open=none"
end
respond_to do |format|
format.html
format.json {
response.headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate"
render :json => @json
}
end
end
def simple
door_status = DoorLog.show_status # Expect {:unlocked => boolean, :door_1_locked => boolean, :door_2_locked => boolean}
render :json => door_status
end
def access
@status = DoorLog.show_status
# Nothing, just render form
unless user_signed_in?
@output = "Invalid email or password. Please login with your Members DB email and password below."
else
unless can? :access_doors_remotely, :door_access
@output = "Sorry, your account isn't able to control doors remotely."
else
@output = "Ready to control doors. Send POST params to this URL as per the HTML form."
end
end
# Render the form again (or result)
respond_to do |format|
format.html
format.json {
response.headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate"
render :json => @output
}
end
end
def access_post
@output = ""
#if params['cmd'] == "check-login" then
# if users[params['user']] && users[params['user']]['pass'].to_s == (Digest::SHA2.new(bitlen=512) << params['pass']).to_s then
# @output += '{ "login": "okay" }'
# else
# @output += '{ "login": "fail" }'
# end
#
# exit
#end
# Stop unless signed in already, OR if the supplied user/pass params are good.
unless current_user || check_auth(params['user'],params['pass'])
@output += "Invalid email or password."
else
# Stop unless the user can access the door system
unless can? :access_doors_remotely, :door_access
@output += "Sorry, your account isn't able to control doors remotely. Ask an admin if this is incorrect."
Rails.logger.warn "----------\r\nWARNING: DOOR ACCESS ATTEMPT DENIED. USER #{current_user.inspect}\r\n----------"
else
# Stop unless we've got a command to run
unless params['cmd']
@output += "No command specified."
else
# Log the access
Rails.logger.info "Door access: user #{current_user.inspect}"
DoorLog.create!({:key => "rem_"+DoorLog.parse_command(params['cmd'])[:url_param], :data => current_user.id})
# Execute the access
@output += DoorLog.execute_command(params['cmd'])
end
end
end
# Render the form again (or result)
respond_to do |format|
format.html {
render :access
}
format.json {
response.headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate"
render :json => @output
}
end
end
# Expect status to be "open" or "closed"
def alert_if_not_status
@expected_status = params['status']
@status = DoorLog.show_status
if !["open","closed"].include?(@expected_status)
@output = "USAGE: Specify an expected status (/alert_if_not/open or /alert_if_not/closed). Alert emails will be sent if status doesn't match."
elsif @expected_status.to_s == "open" && @status[:unlocked] == true
@output = "Unlocked Status is OK."
elsif @expected_status.to_s == "closed" && @status[:unlocked] == false
@output = "Unlocked Status is OK."
else
@output = "Unlocked Status is NOT OK. Alerting."
@output += " - Mail result: "
@output += DoorMailer.alert(@status).deliver.inspect
end
response.headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate"
render :json => {response: @output, status: @status}
end
end

View File

@ -0,0 +1,68 @@
class StatisticsController < ApplicationController
before_filter :load_and_authorize_user
def index
end
def door_log
# Get own user's door data
cards = @user.cards
card_hash = {}
cards.each{|c| card_hash[c.card_number.to_i(16)%32767] = c.card_number}
card_num_Rs = cards.map{|c| c.card_number.to_i(16)%32767}
@door_logs = DoorLog.where("data = ?", card_num_Rs).order("created_at ASC")
@door_logs.map{|l|
l.data = card_hash[l.data.to_i].to_i(16)
l.key = DoorLog.key_legend[l.key]
}
@door_log_graph = []
@door_logs.where("key = 'G'").group_by{|l| l.created_at.beginning_of_day}.each{|l| @door_log_graph << [l.first.to_time.to_i*1000,l.last.size]}
respond_to do |format|
format.html
format.json { render :json => @door_logs }
end
end
def mac_log
macs = @user.macs.where(:hidden => false).map{|m| m.mac}
@mac_logs = MacLog.where(:mac => macs)
@mac_log_graph = {}
macs.each do |mac|
mac_log = MacLog.where(:mac => mac)
mac_times = []
last_active = nil
mac_log.each do |entry|
# Find an activate followed immediately by a deactivate
if entry.action == "activate"
last_active = entry
else
if last_active && entry.action == "deactivate"
# Calculate the time difference between the two and append to this mac's total time
mac_times << [entry.created_at, ((entry.created_at - last_active.created_at)/60/60)]
else
# No pair found; discard.
last_active = nil
end
end
end
mac_log_graph = []
mac_times.group_by{|m| m.first.beginning_of_day}.each{|m| mac_log_graph << [m.first.to_time.to_i*1000,m.last.map{|n| n.last}.sum.round(2)]}
# Store each mac in the hash with its graph
@mac_log_graph[mac] = mac_log_graph
end
#@mac_log_graph = mac_log_grouped.map{|g| [g.first.to_time.to_i*1000, g.last.size] }
respond_to do |format|
format.html
format.json { render :json => @mac_logs }
end
end
def load_and_authorize_user
@user = current_user
authorize! :read, @user
end
end

6
app/controllers/user_certifications_controller.rb Normal file → Executable file
View File

@ -1,12 +1,12 @@
class UserCertificationsController < ApplicationController
load_and_authorize_resource :user_certification
load_and_authorize_resource :user, :through => :user_certification
load_and_authorize_resource :certification, :through => :user_certification
#load_and_authorize_resource :user, :through => :user_certification
#load_and_authorize_resource :certification, :through => :user_certification
before_filter :authenticate_user!
# Load users and certs based on current ability
before_filter :only => [:new, :edit, :create, :update] do
@users = User.accessible_by(current_ability).sort_by(&:name)
@users = User.where(:hidden => [false,nil]).accessible_by(current_ability).sort_by(&:name)
@certifications = Certification.accessible_by(current_ability).sort_by(&:name)
end

156
app/controllers/users_controller.rb Normal file → Executable file
View File

@ -1,47 +1,115 @@
class UsersController < ApplicationController
load_and_authorize_resource
before_filter :authenticate_user!
layout 'resources'
def sort_by_cert(certs,id)
result = 0
certs.each do |c|
if c.id == id
result = 1
end
end
return result
end
# GET /users
# GET /users.json
def index
case params[:sort]
when "name"
@users = @users.sort_by(&:name)
when "certifications"
@users = @users.sort_by{ |u| [-u.certifications.count,u.name] }
when "orientation"
@users = @users.sort_by{ |u| [-u.orientation.to_i,u.name] }
when "waiver"
@users = @users.sort_by{ |u| [-u.waiver.to_i,u.name] }
when "member"
@users = @users.sort_by{ |u| [-u.member.to_i,-u.member_level.to_i,u.name] }
when "card"
@users = @users.sort_by{ |u| [-u.cards.count,u.name] }
when "instructor"
@users = @users.sort{ |a,b| [b.instructor.to_s,a.name] <=> [a.instructor.to_s,b.name] }
when "admin"
@users = @users.sort{ |a,b| [b.admin.to_s,a.name] <=> [a.admin.to_s,b.name] }
else
@users = @users.sort_by(&:name)
end
unless params[:full] # by default, show summary
@users = @users.active.sort_by{|u| [-u.member_level, u.name.downcase] }
#@users.paying + @users.volunteer
#.joins(:payments).where("payments.date > ? OR ", (DateTime.now - 60.days)).uniq
respond_to do |format|
format.html { render 'summary', layout: 'resources' }
format.json { render :json => @users }
end
else # show full
case params[:sort]
when "name"
@users = @users.sort_by{ |u| u.name.downcase }
when "cert"
@users = @users.sort_by{ |u| [-sort_by_cert(u.certifications,params[:cert].to_i),u.name.downcase] }
when "orientation"
@users = @users.sort_by{ |u| [-u.orientation.to_i,u.name.downcase] }
when "waiver"
@users = @users.sort_by{ |u| [-u.contract_date.to_i,u.name.downcase] }
when "member"
@users = @users.sort_by{ |u| [-u.member_status.to_i,u.name.downcase] }
when "card"
@users = @users.sort_by{ |u| [-u.cards.count,u.name.downcase] }
when "instructor"
@users = @users.sort{ |a,b| [b.instructor.to_s,a.name] <=> [a.instructor.to_s,b.name] }
when "admin"
@users = @users.sort{ |a,b| [b.admin.to_s,a.name] <=> [a.admin.to_s,b.name] }
else
@users = @users.sort_by{ |u| u.name.downcase }
end
respond_to do |format|
format.html # index.html.erb
format.json { render :json => @users }
respond_to do |format|
format.html # index.html.erb
format.json { render :json => @users }
end
end
end
# 'Active' users who haven't paid recently
def inactive
@users = @users.all.select{|u| u if u.payment_status == false }.sort_by{ |u| -u.delinquency }
end
# Recent user activity
def activity
@zombie_members = User.where('sign_in_count = 0').where('member_level > 1')
@user_logins = User.where(:current_sign_in_at => 2.months.ago..Time.now).where('sign_in_count > 1')
@new_users = User.where(:created_at => 3.months.ago..Date.today)
@cardless_users = User.includes('cards').where(['users.member_level >= ?','50']).where('cards.id IS NULL')
end
# New members (for emailing out)
def new_member_report
@new_users = User.where(:created_at => 3.months.ago..Date.today).where(:hidden => false).where(['member_level >= ?','1'])
end
# GET /users/1
# GET /users/1.json
def show
@payments = Payment.where(:user_id => @user.id).order('date desc').limit(10)
respond_to do |format|
format.html # show.html.erb
format.json { render :json => @user }
end
end
def compose_email
@user = User.find(params[:user_id])
end
def send_email
@user = User.find(params[:user_id])
@subject = params[:subject]
@body = params[:body]
if @user.send_email(current_user,@subject,@body)
redirect_to user_path(@user), :notice => "Email sent successfully."
else
flash[:alert] = "Error sending email."
render :compose_email
end
end
# GET /user_summary/1
def user_summary
respond_to do |format|
format.html { render :partial => "user_summary" } # show.html.erb
format.json { render :json => @user }
end
end
# GET /users/new
# GET /users/new.json
def new
@ -58,9 +126,12 @@ class UsersController < ApplicationController
# POST /users
# POST /users.json
def create
# update oriented_by only if orientation has been set
@user.oriented_by_id = current_user.id unless @user.orientation.blank?
respond_to do |format|
if @user.save
format.html { redirect_to users_url, :notice => 'User was successfully created.' }
format.html { redirect_to @user, :notice => 'User was successfully created.' }
format.json { render :json => @user, :status => :created, :location => @user }
else
format.html { render :action => "new" }
@ -72,9 +143,13 @@ class UsersController < ApplicationController
# PUT /users/1
# PUT /users/1.json
def update
# update oriented_by only if it's blank but the (new) orientation isn't blank
# gotta test the params because they don't get applied til below.
@user.oriented_by_id = current_user.id if @user.oriented_by.blank? && (!params[:user]["orientation(1i)"].blank?)
respond_to do |format|
if @user.update_attributes(params[:user])
format.html { redirect_to users_url, :notice => 'User was successfully updated.' }
format.html { redirect_to @user, :notice => 'User was successfully updated.' }
format.json { head :no_content }
else
format.html { render :action => "edit" }
@ -83,6 +158,37 @@ class UsersController < ApplicationController
end
end
# GET /users/merge
def merge_view
@users = @users.sort_by(&:name)
respond_to do |format|
format.html # merge_view.html.erb
end
end
# POST /users/merge
def merge_action
@user_to_keep = User.find(params[:user][:to_keep])
Rails.logger.info "USER TO KEEP:"
Rails.logger.info @user_to_keep.inspect
@user_to_merge = User.find(params[:user][:to_merge])
Rails.logger.info "USER TO MERGE:"
Rails.logger.info @user_to_merge.inspect
@user_to_keep.absorb_user(@user_to_merge)
Rails.logger.info "RESULT:"
Rails.logger.info @user_to_keep.inspect
Rails.logger.info @user_to_keep.cards.inspect
Rails.logger.info @user_to_keep.user_certifications.inspect
Rails.logger.info @user_to_keep.payments.inspect
respond_to do |format|
format.html { redirect_to @user_to_keep, :notice => 'Users successfully merged.' }
end
end
# DELETE /users/1
# DELETE /users/1.json
def destroy

27
app/helpers/application_helper.rb Normal file → Executable file
View File

@ -1,4 +1,31 @@
module ApplicationHelper
@payment_methods = [[nil],["PayPal"],["Dwolla"],["Bill Pay"],["Check"],["Cash"],["Other"]]
@payment_instructions = {nil => nil, :paypal => "Set up a monthly recurring payment to hslfinances@gmail.com", :dwolla => "Set up a monthly recurring payment to hslfinances@gmail.com", :billpay => "Have your bank send a monthly check to HeatSync Labs Treasurer, 140 W Main St, Mesa AZ 85201", :check => "Mail to HeatSync Labs Treasurer, 140 W Main St, Mesa AZ 85201 OR put in the drop safe at the Lab with a deposit slip firmly attached each month.", :cash => "Put in the drop safe at the Lab with a deposit slip firmly attached each month.", :other => "Hmm... talk to a Treasurer!"}
def sort_link(title, column, options = {})
condition = options[:unless] if options.has_key?(:unless)
sort_dir = params[:dir] == 'up' ? 'down' : 'up'
link_to_unless condition, title, request.parameters.merge( {:sort => column, :dir => sort_dir} )
end
def li_link_to(name = nil, options = nil, html_options = nil, &block)
html_options, options, name = options, name, block if block_given?
options ||= {}
html_options = convert_options_to_data_attributes(options, html_options)
url = url_for(options)
html_options['href'] ||= url
if current_page?(url)
content_tag(:li, class: "active") do
content_tag(:a, name || url, html_options, &block)
end
else
content_tag(:li) do
content_tag(:a, name || url, html_options, &block)
end
end
end
end

0
app/helpers/certifications_helper.rb Normal file → Executable file
View File

0
app/helpers/door_logs_helper.rb Normal file → Executable file
View File

0
app/helpers/home_helper.rb Normal file → Executable file
View File

2
app/helpers/ipn_helper.rb Executable file
View File

@ -0,0 +1,2 @@
module IpnHelper
end

2
app/helpers/mac_logs_helper.rb Executable file
View File

@ -0,0 +1,2 @@
module MacLogsHelper
end

2
app/helpers/pamela_helper.rb Executable file
View File

@ -0,0 +1,2 @@
module PamelaHelper
end

2
app/helpers/payments_helper.rb Executable file
View File

@ -0,0 +1,2 @@
module PaymentsHelper
end

0
app/helpers/user_certifications_helper.rb Normal file → Executable file
View File

0
app/helpers/users_helper.rb Normal file → Executable file
View File

0
app/mailers/.gitkeep Normal file → Executable file
View File

12
app/mailers/door_mailer.rb Executable file
View File

@ -0,0 +1,12 @@
class DoorMailer < ActionMailer::Base
default :from => "no-reply@heatsynclabs.org"
def alert(status)
@url = "http://members.heatsynclabs.org"
@status = status
mail(:to => 'heatsynclabs@googlegroups.com',
:subject => "HSL Doors")
end
end

20
app/mailers/user_mailer.rb Executable file
View File

@ -0,0 +1,20 @@
class UserMailer < ActionMailer::Base
default :from => "no-reply@heatsynclabs.org"
def new_user_email(user)
@user = user
@url = "http://members.heatsynclabs.org"
mail(:to => 'member-notifications@heatsynclabs.org',
:subject => "New HSL Member: "+user.name)
end
def email(to_user,from_user,subject,body)
@url = "http://members.heatsynclabs.org"
@body = body
@from_user = from_user
mail(:to => to_user.email,
:subject => "HSL Message: "+subject)
end
end

0
app/models/.gitkeep Normal file → Executable file
View File

63
app/models/ability.rb Normal file → Executable file
View File

@ -2,36 +2,67 @@ class Ability
include CanCan::Ability
def initialize(user)
can :read, Mac # Anonymous can read mac
can :scan, Mac # Need anonymous so CRON can scan
can :read, Resource
can :read, ResourceCategory
if !user.nil?
# By default, users can only see their own stuff
can :read, Card, :user_id => user.id
can :read, Certification
can :read, User, :id => user.id #TODO: why can users update themselves?
can :read_details, Mac
can [:update], Mac, :user_id => nil
can [:create,:update], Mac, :user_id => user.id
can [:create,:update,:destroy], Resource, :user_id => user.id
can :read, Payment, :user_id => user.id
can :read, UserCertification, :user_id => user.id
can :read, User, :id => user.id #TODO: why can users update themselves? Maybe because Devise doesn't check users/edit?
can :compose_email, User
can :send_email, User
if user.card_access_enabled
can :access_doors_remotely, :door_access
can :authorize, Card # used for interlock card/certification auth
end
# Instructors can manage certs and see users
if user.instructor?
can :manage, Certification
can [:create,:read], User, :hidden => [nil,false]
can [:create,:read], UserCertification
can [:update,:destroy], UserCertification, :created_by => user.id
end
# Users can see others' stuff if they've been oriented
unless user.orientation.blank?
can [:read,:new_member_report,:activity], User, :hidden => [nil,false]
can :read, UserCertification
can [:create,:update], Resource, :user_id => [nil,user.id]
can [:create,:update,:destroy], ResourceCategory
end
# Accountants can manage payments
if user.accountant?
can :manage, Payment
can :manage, Ipn
can :manage, PaypalCsv
end
# Admins can manage all
if user.admin?
can :manage, :all
end
# Instructors can manage certs and see users
if user.instructor?
can :manage, Certification
can [:create,:read], User, :hidden => [nil,false]
can :manage, UserCertification
end
# Users can see others' stuff if they've been oriented
unless user.orientation.blank?
can :read, User, :hidden => [nil,false]
can :read, UserCertification
end
# Prevent all destruction for now
cannot :destroy, User
cannot :destroy, Card
# Prevent most destruction for now
#cannot :destroy, User
#cannot :destroy, Card
cannot :destroy, Certification
cannot :destroy, UserCertification
cannot :destroy, Mac
cannot :destroy, MacLog
#cannot :destroy, UserCertification
cannot :destroy, DoorLog
# no exception for destroying payments
end
# Define abilities for the passed in user here. For example:
#

53
app/models/card.rb Normal file → Executable file
View File

@ -2,7 +2,8 @@ class Card < ActiveRecord::Base
require 'open-uri'
attr_accessible :id, :user_id, :name, :card_number, :card_permissions
validates_uniqueness_of :id,:card_number
validates_presence_of :user_id, :card_number, :card_permissions
validates_uniqueness_of :id, :card_number
belongs_to :user
def upload_to_door
@ -10,21 +11,14 @@ class Card < ActiveRecord::Base
door_access_url = APP_CONFIG['door_access_url']
door_access_password = APP_CONFIG['door_access_password']
# connect to door access system
source = open("#{door_access_url}?e=#{door_access_password}").read
results = source.scan(/authok/)
if(results.size > 0) then
#only continue if we've got an OK login
cardid = self.id.to_s.rjust(3, '0') #TODO: provide ability for
cardid = self.id.to_s.rjust(3, '0')
cardperm = self.card_permissions.to_s.rjust(3, '0')
cardnum = self.card_number.rjust(8, '0')
source = open("#{door_access_url}?m#{cardid}&p#{cardperm}&t#{cardnum}").read
# login and send the command all in one go (auto-logout is a feature of the arduino when used this way)
source = open("#{door_access_url}?m#{cardid}&p#{cardperm}&t#{cardnum}&e=#{door_access_password}").read
results = source.scan(/cur/)
#logout
open("#{door_access_url}?e=0000")
if(results.size > 0) then
#only return true if we got some kind of decent response
return true
@ -32,10 +26,6 @@ class Card < ActiveRecord::Base
# We didn't get a decent response.
return false
end
else
# We didn't get an OK login.
return false
end
end
def self.upload_all_to_door
@ -46,30 +36,21 @@ class Card < ActiveRecord::Base
door_access_url = APP_CONFIG['door_access_url']
door_access_password = APP_CONFIG['door_access_password']
source = open("#{door_access_url}?e=#{door_access_password}").read
results = source.scan(/authok/)
if(results.size > 0) then
@cards.each do |u|
#only continue if we've got an OK login
cardid = u.id.to_s.rjust(3, '0')
cardperm = u.card_permissions.to_s.rjust(3, '0')
cardnum = u.card_number.rjust(8, '0')
@cards.each do |u|
cardid = u.id.to_s.rjust(3, '0')
cardperm = u.card_permissions.to_s.rjust(3, '0')
cardnum = u.card_number.rjust(8, '0')
source = open("#{door_access_url}?m#{cardid}&p#{cardperm}&t#{cardnum}").read
results = source.scan(/cur/)
# login and send the command all in one go (auto-logout is a feature of the arduino when used this way)
source = open("#{door_access_url}?m#{cardid}&p#{cardperm}&t#{cardnum}&e=#{door_access_password}").read
results = source.scan(/cur/)
if(results.size > 0) then
#only return true if we got some kind of decent response
@end_results.push([cardid,"OK"])
else
@end_results.push([cardid,"FAIL"])
end
if(results.size > 0) then
#only return true if we got some kind of decent response
@end_results.push([cardid,"OK"])
else
@end_results.push([cardid,"FAIL"])
end
#logout
open("#{door_access_url}?e=0000")
else
@end_results.push([cardid,"FAIL"])
end
return @end_results

4
app/models/certification.rb Normal file → Executable file
View File

@ -1,5 +1,7 @@
class Certification < ActiveRecord::Base
attr_accessible :description, :name
attr_accessible :description, :name, :slug
has_many :user_certifications
has_many :users, :through => :user_certifications
validates_presence_of :name, :slug
end

24
app/models/contract.rb Normal file
View File

@ -0,0 +1,24 @@
class Contract < ActiveRecord::Base
belongs_to :user
belongs_to :created_by, :foreign_key => "created_by_id", :class_name => "User"
attr_accessible :user_id, :first_name, :last_name, :cosigner,
:signed_at, :document, :document_file_name, :document_content_type,
:document_file_size, :document_updated_at
# :created_by not accessible for security purposes
validates_presence_of :first_name, :signed_at #, :last_name
has_attached_file :document,
{ :styles =>
{
:medium => "300x300>",
:large => "900x900>"
},
:storage => :s3,
:s3_protocol => :https,
:s3_credentials => { :access_key_id => ENV['S3_KEY'],
:secret_access_key => ENV['S3_SECRET'] },
:path => ":attachment/:id/:style.:extension",
:bucket => ENV['S3_BUCKET']
}
end

126
app/models/door_log.rb Normal file → Executable file
View File

@ -2,6 +2,126 @@ class DoorLog < ActiveRecord::Base
attr_accessible :data, :key
require 'open-uri'
def self.execute_command(command)
output = ""
# load config values
door_access_url = APP_CONFIG['door_access_url']
door_access_password = APP_CONFIG['door_access_password']
#login
source = open("#{door_access_url}?e=#{door_access_password}").read
results = source.scan(/ok/)
#only continue if we've got an OK login
if(results.size > 0) then
# Parse the command and result
parsing = parse_command(command)
output += parsing[:output]
url_param = parsing[:url_param]
# Execute the command
open("#{door_access_url}?#{url_param}")
self.download_status # Update the status cache
else
output += 'Failed to connect to door system.'
end
#logout
open("#{door_access_url}?e=0000")
return output
end
def self.parse_command(command)
output = ""
url_param = ""
# @commands = [
# ["Unlock all doors","unlock"],
# ["Unlock Front door","unlock-front"],
# ["Unlock Rear door","unlock-rear"],
# ["Lock all doors","lock"],
# ["Lock Front door","lock-front"],
# ["Lock Rear door","lock-rear"],
# ["Open Front door","open-front"],
# ["Open Rear door","open-rear"],
# ["Arm alarm","arm"],
# ["Disarm alarm","disarm"]
# ]
case command
when "open-front"
output += "Front door opened."
url_param = "o1"
when "open-rear"
output += "Rear door opened."
url_param = "o2"
when "u", "unlock"
output += "Doors unlocked, remember to re-lock them."
url_param = "u"
when "u1", "unlock-front"
output += "Front Door unlocked, remember to re-lock it."
url_param = "u=1"
when "u2", "unlock-rear"
output += "Rear Door unlocked, remember to re-lock it."
url_param = "u=2"
when "lock", "l"
output += "Doors locked."
url_param = "l"
when "lock-front", "l1"
output += "Front Door locked."
url_param = "l=1"
when "lock-rear", "l2"
output += "Rear Door locked."
url_param = "l=2"
when "arm"
output += "Armed."
url_param = "2"
when "disarm"
output += "Disarmed."
url_param = "1"
else
output += "Fail. Don't be a naughty user!"
url_param = "9" # Using 9 because it's just status, no harm done
end
return {:output => output, :url_param => url_param}
end
def self.show_status
door_logs = DoorLog.order('created_at DESC').where(key: ["door_1_locked","door_2_locked"]).limit(2)
door_1_locked = parse_locked_status(door_logs, "door_1_locked")
door_2_locked = parse_locked_status(door_logs, "door_2_locked")
# Doors are unlocked if 1 OR 2 are NOT locked
status = {:unlocked => (!door_1_locked || !door_2_locked), :door_1_locked => door_1_locked, :door_2_locked => door_2_locked }
end
def self.parse_locked_status(door_logs, door_key)
door_logs_selected = door_logs.select{|s| s.key == door_key }
if door_logs_selected.present?
door_data = door_logs_selected.first.data
if door_data == 0 # 0 = unlocked
return false
else
return true # 1 = locked
end
end
end
def self.download_status
# load config values
door_access_url = APP_CONFIG['door_access_url']
door_access_password = APP_CONFIG['door_access_password']
# query for status
source = open("#{door_access_url}?9").read
# expect {"armed"=>255, "activated"=>255, "alarm_3"=>1, "alarm_2"=>1, "door_1_locked"=>1, "door_2_locked"=>1}
# See https://github.com/heatsynclabs/Open_Access_Control_Ethernet for more info
@status = JSON.parse(source)
@status.each do |key,value|
DoorLog.create!({:key => key, :data => value})
end
end
def self.download_from_door
# load config values
door_access_url = APP_CONFIG['door_access_url']
@ -9,7 +129,7 @@ class DoorLog < ActiveRecord::Base
# connect to door access system
source = open("#{door_access_url}?e=#{door_access_password}").read
results = source.scan(/authok/)
results = source.scan(/ok/)
if(results.size > 0) then
@end_results = Array.new
@ -41,4 +161,8 @@ class DoorLog < ActiveRecord::Base
end
end
def self.key_legend
{'G' => "Granted", "R" => "Read", "D" => "Denied",
'g' => "granted", "r" => "read", "d" => "denied"}
end
end

95
app/models/ipn.rb Executable file
View File

@ -0,0 +1,95 @@
require 'net/http'
class Ipn < ActiveRecord::Base
attr_accessible :data
belongs_to :payment
after_create :create_payment
def date_parsed
begin
Date.strptime(self.payment_date, "%H:%M:%S %b %e, %Y %Z")
rescue
Date.new
end
end
def self.new_from_dynamic_params(params)
ipn = Ipn.new()
ipn.attributes.each do |c|
unless params[c.first.to_sym].nil?
ipn[c.first.to_sym] = params[c.first.to_sym]
end
end
return ipn
end
# Post back to Paypal to make sure it's valid
def validate!
uri = URI.parse('https://www.paypal.com/cgi-bin/webscr?cmd=_notify-validate')
http = Net::HTTP.new(uri.host, uri.port)
http.open_timeout = 60
http.read_timeout = 60
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
http.use_ssl = true
response = http.post(uri.request_uri, self.data,
'Content-Length' => "#{self.data.size}",
'User-Agent' => "Ruby on Rails"
).body
unless ["VERIFIED", "INVALID"].include?(response)
Rails.logger.error "Faulty paypal result: #{response}"
return false
end
unless response == "VERIFIED"
Rails.logger.error "Invalid IPN: #{response}"
Rails.logger.error "Data: #{self.data}"
return false
end
return true
end
def link_payment
create_payment
end
private
def create_payment
# find user by email, then by payee
user = User.where("lower(email) = ?", self._from_email_address.downcase).first
user = User.where("lower(payee) = ?", self._from_email_address.downcase).first if user.nil? && self._from_email_address.present?
# Only create payments if the IPN matches a member
if user.present?
# And is a payment (not a cancellation, etc)
payment_types = ["subscr_payment","send_money"]
if payment_types.include?(self.txn_type)
# And a member level
if User.member_levels[self.payment_gross.to_i].present?
payment = Payment.new
payment.date = Date.strptime(self.payment_date, "%H:%M:%S %b %e, %Y %Z")
payment.user_id = user.id
payment.amount = self.payment_gross
if payment.save
self.payment_id = payment.id
self.save!
else
return [false, "Unable to link payment. Payment error: #{payment.errors.full_messages.first}"]
end
else
return [false, "Unable to link payment. Couldn't find membership level '#{self.payment_gross.to_i}'."]
end
else
return [false, "Unable to link payment. Transaction is a '#{self.txn_type}' instead of '#{payment_types.inspect}'."]
end
else
return [false, "Unable to link payment. Couldn't find user/payee '#{self.payer_email}'."]
end
return [true]
end
end

6
app/models/mac.rb Executable file
View File

@ -0,0 +1,6 @@
class Mac < ActiveRecord::Base
belongs_to :user
attr_accessible :active, :ip, :mac, :refreshed, :since, :hidden, :note, :user_id
validates_uniqueness_of :mac, :case_sensitive => false
end

5
app/models/mac_log.rb Executable file
View File

@ -0,0 +1,5 @@
class MacLog < ActiveRecord::Base
attr_accessible :action, :ip, :mac
scope :desc, order("mac_logs.created_at DESC")
end

17
app/models/payment.rb Executable file
View File

@ -0,0 +1,17 @@
class Payment < ActiveRecord::Base
belongs_to :user
has_one :ipn
has_one :paypal_csv
attr_accessible :date, :user_id, :created_by, :amount
validates_presence_of :user_id, :date, :amount # not created_by
validates_uniqueness_of :date, :scope => :user_id, :message => ' of payment already exists for this user.'
def human_date
if date.year < DateTime.now.year
date.strftime("%b %e, %y")
else
date.strftime("%b %e")
end
end
end

80
app/models/paypal_csv.rb Executable file
View File

@ -0,0 +1,80 @@
require 'csv'
class PaypalCsv < ActiveRecord::Base
attr_accessible :data, :_address_status, :_counterparty_status, :_currency, :_fee, :_from_email_address, :_gross, :_item_id, :_item_title, :_name, :_net, :_status, :_time, :_time_zone, :_to_email_address, :_transaction_id, :_type, :date, :string
belongs_to :payment
after_create :create_payment
def date_parsed
begin
Date.strptime(self._date, "%m/%d/%Y")
rescue
Date.new
end
end
def self.batch_import_from_csv(filename)
csv = CSV.table(filename)
csv.each do |row|
paypal_csv = PaypalCsv.new()
logger.fatal row.inspect
paypal_csv.attributes.each do |c|
# Try finding the column without a prepended _ first (compatibility with new CSV format)
unless row[c.first[1..-1].to_sym].nil?
paypal_csv[c.first.to_sym] = row[c.first[1..-1].to_sym]
end
# If there's an exact match, it takes precedence
unless row[c.first.to_sym].nil?
paypal_csv[c.first.to_sym] = row[c.first.to_sym]
end
end
paypal_csv.data = row.to_json
paypal_csv.save
end
return true
end
def link_payment
create_payment
end
private
def create_payment
logger.fatal self.inspect
# find user by email, then by payee
user = User.where("lower(email) = ?", self._from_email_address.downcase).first
user = User.where("lower(payee) = ?", self._from_email_address.downcase).first if user.nil? && self._from_email_address.present?
# Only create payments if the CSV matches a member
if user.present?
# And is a payment (not a cancellation, etc)
payment_types = ["Subscription Payment Processed","Recurring Payment Received","Payment Received"]
if payment_types.include?(self._type)
# And a member level
if User.member_levels[self._gross.to_i].present?
payment = Payment.new
payment.date = Date.strptime(self._date, "%m/%d/%Y") #7/6/2013 for Jul 06
payment.user_id = user.id
payment.amount = self._gross
if payment.save
self.payment_id = payment.id
self.save!
else
return [false, "Unable to link payment. Payment error: #{payment.errors.full_messages.first}"]
end
else
return [false, "Unable to link payment. Couldn't find membership level '#{self._gross.to_i}'."]
end
else
return [false, "Unable to link payment. Transaction is a '#{self._type}' instead of '#{payment_types.inspect}'."]
end
else
return [false, "Unable to link payment. Couldn't find user/payee '#{self._from_email_address}'."]
end
return [true]
end
end

31
app/models/resource.rb Executable file
View File

@ -0,0 +1,31 @@
class Resource < ActiveRecord::Base
attr_accessible :supercategory, :user_id, :resource_category_id, :name, :serial, :specs, :status, :donatable,
:picture, :picture_file_name, :picture_content_type, :picture_file_size, :picture_updated_at,
:picture2, :picture2_file_name, :picture2_content_type, :picture2_file_size, :picture2_updated_at,
:picture3, :picture3_file_name, :picture3_content_type, :picture3_file_size, :picture3_updated_at,
:picture4, :picture4_file_name, :picture4_content_type, :picture4_file_size, :picture4_updated_at,
:notes, :estimated_value, :disposed_at, :modified_by
belongs_to :owner, :class_name => "ToolshareUser" #TODO: remove owner
belongs_to :user
belongs_to :resource_category
PICTURE_OPTIONS = { :styles => { :medium => "300x300>",
:thumb => "100x100>",
:tiny => "50x50>"},
:storage => :s3,
:s3_protocol => :https,
:s3_credentials => { :access_key_id => ENV['S3_KEY'],
:secret_access_key => ENV['S3_SECRET'] },
:path => ":attachment/:id/:style.:extension",
:bucket => ENV['S3_BUCKET'] }
has_attached_file :picture, PICTURE_OPTIONS
has_attached_file :picture2, PICTURE_OPTIONS
has_attached_file :picture3, PICTURE_OPTIONS
has_attached_file :picture4, PICTURE_OPTIONS
def resource_category_name
resource_category.name if resource_category
end
end

13
app/models/resource_category.rb Executable file
View File

@ -0,0 +1,13 @@
class ResourceCategory < ActiveRecord::Base
has_many :resources
attr_accessible :name
before_destroy :has_resources?
private
def has_resources?
errors.add(:base, "Cannot delete category with associated resources") unless resources.count == 0
errors.blank? #return false, to not destroy the element, otherwise, it will delete.
end
end

3
app/models/setting.rb Executable file
View File

@ -0,0 +1,3 @@
class Setting < RailsSettings::CachedSettings
attr_accessible :var
end

4
app/models/toolshare_user.rb Executable file
View File

@ -0,0 +1,4 @@
class ToolshareUser < ActiveRecord::Base
has_many :resources, :foreign_key => "owner_id"
attr_accessible :name, :email
end

243
app/models/user.rb Normal file → Executable file
View File

@ -1,6 +1,6 @@
class User < ActiveRecord::Base
include Gravtastic
gravtastic :size => 120, :default => ""
gravtastic :size => 150, :default => ""
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable,
@ -9,40 +9,223 @@ class User < ActiveRecord::Base
:recoverable, :rememberable, :trackable, :validatable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me, :name, :admin, :instructor, :member, :emergency_name, :emergency_phone, :current_skills, :desired_skills, :waiver, :emergency_email, :phone, :payment_method, :orientation, :member_level, :certifications, :hidden #TODO: make admin/instructor/member/etc not accessible
attr_accessible :email, :password, :password_confirmation, :remember_me, :name, :admin, :instructor, :member, :emergency_name, :emergency_phone, :current_skills, :desired_skills, :waiver, :emergency_email, :phone, :payment_method, :orientation, :member_level, :certifications, :hidden, :marketing_source, :payee, :accountant, :exit_reason, :twitter_url, :facebook_url, :github_url, :website_url, :email_visible, :phone_visible, :postal_code #TODO: make admin/instructor/member/etc not accessible
belongs_to :oriented_by, :foreign_key => "oriented_by_id", :class_name => "User"
has_many :cards
has_many :user_certifications
has_many :certifications, :through => :user_certifications
has_many :contracts
has_many :payments
has_many :macs
has_many :resources
scope :volunteer, -> { where('member_level >= 10 AND member_level < 25') }
scope :active, -> { where('member_level >= 10') }
scope :paying, -> { joins(:payments).where("payments.date > ?", (DateTime.now - 90.days)).uniq }
validates_format_of [:twitter_url, :facebook_url, :github_url, :website_url], :with => URI::regexp(%w(http https)), :allow_blank => true
# disable # validates_presence_of :postal_code
after_create :send_new_user_email
def absorb_user(user_to_absorb)
# copy all attributes except email, password, name, and anything that isn't blank on the destination
user_to_absorb.attributes.each_pair {|k,v|
unless (v.nil? || k == :id || k == :email || k == :password || k == :name || k == :password_confirmation || k == :hidden || k == 'hidden' || k == :encrypted_password || !self.attributes[k].blank? )
Rails.logger.info "Updating "+k.to_s+" from "+self[k].to_s
self[k] = v
Rails.logger.info "Updated "+k.to_s+" to "+self[k].to_s
end
}
self.save!
user_to_absorb.cards.each {|card|
Rails.logger.info "CARD BEFORE: "+card.inspect
card.user_id = self.id
card.save!
Rails.logger.info "CARD AFTER: "+card.inspect
}
user_to_absorb.user_certifications.each {|user_cert|
Rails.logger.info "CERT BEFORE: "+user_cert.inspect
user_cert.user_id = self.id
user_cert.save!
Rails.logger.info "CERT AFTER: "+user_cert.inspect
}
user_to_absorb.payments.each {|payment|
Rails.logger.info "PAYMENT BEFORE: "+payment.inspect
payment.user_id = self.id
payment.amount = 0 if payment.amount.nil? # Bypass validation on amount
payment.save!
Rails.logger.info "PAYMENT AFTER: "+payment.inspect
}
user_to_absorb.destroy
end
def card_access_enabled
# If the user has at least one card with permission level 1, they have access
self.cards.where(:card_permissions => 1).count > 0
end
def name_with_email_and_visibility
if hidden then
"#{name} (#{email}) HIDDEN"
else
"#{name} (#{email})"
end
end
def name_with_payee_and_member_level
if payee.blank? then
"#{name} - #{member_level_string}"
else
"#{payee} for #{name} - #{member_level_string}"
end
end
def member_level_string
case self.member_level.to_i
when 0
"None"
when 1
"Unable"
when 10..24
"Volunteer"
when 25..49
"Associate ($25)"
when 50..99
"Basic ($50)"
when 100..999
"Plus ($100)"
end
end
def self.member_levels
{25 => "Associate", 50 => "Basic", 75 => "Basic", 100 => "Plus"}
end
def payment_status
results = payment_status_calculation
return results[:paid]
end
def member_status
output = ""
if self.member_level.to_i >= 1 then
output = "<span class='hoverinfo' title='Inactive'>&#9676;</span>"
end
unless self.member.nil? then
# 1 = inactive, show an X
if self.member >= 10 then
output = "<span class='hoverinfo' title='Volunteer'>&#9684;</span>"
# 25 or higher is paying, show a check
end
if self.member >= 25 then
output = "<span class='hoverinfo' title='25'>&#9681;</span>"
end
if self.member >= 50 then
output = "<span class='hoverinfo' title='50'>&#9685;</span>"
end
if self.member >= 100 then
output = "<span class='hoverinfo' title='100'>&#9679;</span>"
end
if self.member < self.member_level.to_i then
output = "<span class='hoverinfo' title='Lapsed'>&#x2717;</span>"
end
end
return output
results = member_status_calculation
return results[:rank]
end
def member_status_symbol
results = member_status_calculation
return "<img src='/#{results[:icon]}#{results[:flair]}-coin.png' title='#{results[:message]}' class='member-status-symbol' />"
end
def last_payment_date
self.payments.maximum(:date)
end
def delinquency
if self.payments.count > 0
paydate = self.payments.maximum(:date)
(Date.today - paydate).to_i
else
(Date.today - self.created_at.to_date).to_i
end
end
def send_email(from_user,subject,body)
Rails.logger.info UserMailer.email(self,from_user,subject,body).deliver
end
def has_certification?(cert_slug)
if self.certifications.find_by_slug(cert_slug)
true
else
false
end
end
def contract_date
self.contracts.first.signed_at unless self.contracts.blank?
end
private
def send_new_user_email
Rails.logger.info UserMailer.new_user_email(self).deliver
end
def member_status_calculation
# Begin output buffer
message = ""
icon = ""
flair = ""
rank = 0
# First status item is level
case self.member_level.to_i
when 0..9
if self.payments.count > 0 then
message = "Former Member (#{(DateTime.now - self.payments.maximum(:date)).to_i/30} months ago)"
icon = :timeout
rank = 1
else
message = "Not a Member"
icon = :no
rank = 0
end
when 10..24
message = "Volunteer"
icon = :heart
rank = 101
when 25..49
message = member_level_string
icon = :copper
rank = 250
when 50..99
message = member_level_string
icon = :silver
rank = 500
when 100..999
message = member_level_string
icon = :gold
rank = 1000
end
payment_results = payment_status_calculation
flair = payment_results[:flair]
rank = rank/10 unless payment_results[:paid]
message = payment_results[:message] unless payment_results[:message].blank?
return {:message => message, :icon => icon, :flair => flair, :rank => rank}
end
def payment_status_calculation
flair = ""
message = ""
paid = true
# Second status item is payment status
case self.member_level.to_i
when 25..999
# There are payments
if self.payments.count > 0 then
# They're on time
if self.payments.maximum(:date) > (DateTime.now - 60.days)
flair = "-paid"
paid = true
else
message = "Last Payment #{(DateTime.now - self.payments.maximum(:date)).to_i/30} months ago"
paid = false
end
else
message = "No Payments Recorded"
paid = false
end
end
return {:message => message, :paid => paid, :flair => flair}
end
end

9
app/models/user_certification.rb Normal file → Executable file
View File

@ -1,8 +1,17 @@
class UserCertification < ActiveRecord::Base
attr_accessible :certification_id, :user_id
validates_presence_of :certification_id, :user_id
validates_uniqueness_of :certification_id, :scope => :user_id, :message => 'already exists for this user.' # Makes sure users don't get certified twice
belongs_to :user
belongs_to :certification
def user_name
if user.blank?
return "n/a (user ##{user_id} missing)"
else
return self.user.name
end
end
end

3
app/views/cards/_form.html.erb Normal file → Executable file
View File

@ -11,9 +11,10 @@
</div>
<% end %>
<% @card.user_id = params[:user] if params[:user].present? %>
<div class="field">
<%= f.label :user %><br />
<%= collection_select(:card, :user_id, User.all.sort_by(&:name), :id, :name) %>
<%= collection_select(:card, :user_id, User.all.sort_by(&:name), :id, :name, :include_blank => true) %>
</div>
<div class="field">
<%= f.label :name, "Card Note" %><br />

0
app/views/cards/edit.html.erb Normal file → Executable file
View File

29
app/views/cards/index.html.erb Normal file → Executable file
View File

@ -1,8 +1,20 @@
<h1>Access Cards</h1>
<%= link_to 'New Card', new_card_path if can? :create, Card %>
<%= link_to 'Upload all cards', upload_all_path if can? :upload_all, Card %>
<table>
<%= link_to 'New Card', new_card_path, :class => "btn" if can? :create, Card %>
<%= link_to 'Upload all cards', upload_all_path, :class => "btn" if can? :upload_all, Card %>
<%= link_to 'Door Logs', door_logs_path, :class => "btn" if can? :read, DoorLog %>
<%= link_to 'Space API', space_api_path, :class => "btn" %>
<%= link_to 'Remote Door Access', space_api_access_path, :class => "btn" if can? :access_doors_remotely, :door_access %>
<p>
<b>Most Active Card Last Month:</b> <%= @most_active_card.user.name unless @most_active_card.user.blank? %> (<%= @most_active_card.accesses_this_week unless @most_active_card.blank? %> days)
</p>
<p>
<% unless @runner_up_card.blank? || @runner_up_card.user.blank? %>
<b>Runner Up:</b> <%= @runner_up_card.user.name %> (<%= @runner_up_card.accesses_this_week %> days)
<% end %>
</p>
<table class="lined-table">
<col />
<col />
<col class="col_highlight" />
@ -14,6 +26,7 @@
<th>DB ID</th>
<th>Card #</th>
<th>Access?</th>
<th>Days Accessed Last Month</th>
<th></th>
<th></th>
<th></th>
@ -22,11 +35,19 @@
<% if !@cards.blank? %>
<% @cards.each do |card| %>
<tr>
<td><%= card.user.name %></td>
<td>
<% if card.user.nil? %>
n/a
<% else %>
<%= raw(card.user.member_status_symbol) %>
<%= link_to card.user.name , card %>
<% end %>
</td>
<td><%= card.name %></td>
<td><%= card.id %></td>
<td><%= card.card_number %></td>
<td><%= if card.card_permissions == 1 then "Access" end %></td>
<td><%= card.accesses_this_week unless card.accesses_this_week < 1 %></td>
<td><%= link_to 'Upload', upload_path(card) if can? :upload, card %></td>
<td><%= link_to 'Edit', edit_card_path(card) if can? :update, card %></td>
<td><%= link_to 'Destroy', card, :confirm => 'Are you sure? WARNING: THIS DOES NOT REMOVE THE CARD FROM THE DOOR SYSTEM! DISABLE AND UPLOAD IT FIRST.', :method => :delete if can? :destroy, card %></td>

0
app/views/cards/new.html.erb Normal file → Executable file
View File

15
app/views/cards/show.html.erb Normal file → Executable file
View File

@ -1,6 +1,6 @@
<p>
<b>User:</b>
<%= @card.user.name unless @card.user.blank? %>
<%= link_to @card.user.name, @card.user unless @card.user.blank? %>
</p>
<p>
@ -20,9 +20,20 @@
<p>
<b>Card Permissions:</b>
<%= @card.card_permissions %>
<%= if @card.card_permissions == 1 then "Enabled" else "Disabled" end %>
</p>
<% if can? :read, DoorLog %>
<p>
<b>Access attempts:</b>
<ul>
<% @door_logs.each do |log| %>
<li><%= log.created_at %></li>
<% end %>
</ul>
</p>
<% end %>
<%= link_to 'Upload to Door', upload_path(@card) if can? :upload, @card %>
<% if can? :update, @card then %><%= link_to 'Edit', edit_card_path(@card) %> |<% end %>
<%= link_to 'Back', cards_path %>

0
app/views/cards/upload.html.erb Normal file → Executable file
View File

0
app/views/cards/upload_all.html.erb Normal file → Executable file
View File

4
app/views/certifications/_form.html.erb Normal file → Executable file
View File

@ -15,6 +15,10 @@
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :slug, "Slug (lowercase, single-word identifier)" %><br />
<%= f.text_field :slug %>
</div>
<div class="field">
<%= f.label :description %><br />
<%= f.text_area :description %>

0
app/views/certifications/edit.html.erb Normal file → Executable file
View File

1
app/views/certifications/index.html.erb Normal file → Executable file
View File

@ -5,6 +5,7 @@
<ul>
<% @certifications.each do |certification| %>
<li><%= link_to certification.name, certification %>
(<%= certification.slug %>)
<% if can? :update, certification %> | <%= link_to 'Edit', edit_certification_path(certification) %><% end %>
<% if can? :destroy, certification %> | <%= link_to 'Destroy', certification, :confirm => 'Are you sure?', :method => :delete %><% end %>
</li>

0
app/views/certifications/new.html.erb Normal file → Executable file
View File

16
app/views/certifications/show.html.erb Normal file → Executable file
View File

@ -3,18 +3,20 @@
<%= @certification.name %>
</p>
<p>
<b>Slug (lowercase, single-word identifier):</b>
<%= @certification.slug %>
</p>
<p>
<b>Description:</b>
<%= simple_format @certification.description %>
</p>
<b>Certified Users:</b>
<ul>
<% @certification_users.each do |user| %>
<li><%= link_to user.name, user %></li>
<% end %>
<% if @certification_users.blank? then %><li>n/a</li><% end %>
</ul>
<p>
<b>Certified Users:</b>
<%= link_to "Click Here", user_certifications_path %>
</p>
<% if can? :update, @certification %><%= link_to 'Edit', edit_certification_path(@certification) %> |<% end %>
<%= link_to 'Back', certifications_path %>

View File

@ -0,0 +1,51 @@
<%= form_for(@contract, html: {class: "col-sm-6"}) do |f| %>
<% if @contract.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@contract.errors.count, "error") %> prohibited this contract from being saved:</h2>
<ul>
<% @contract.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="form-group">
<%= f.label :first_name %><br />
<%= f.text_field :first_name, class: "form-control" %>
</div>
<div class="form-group">
<%= f.label :last_name %><br />
<%= f.text_field :last_name, class: "form-control" %>
</div>
<div class="form-group">
<%= f.label :cosigner %><br />
<%= f.text_field :cosigner, class: "form-control" %>
</div>
<div class="form-group">
<%= f.label :user_id, "User" %><br />
<%= collection_select(:contract, :user_id, @users, :id, :name, :include_blank => true) %>
</div>
<div class="form-group">
<%= f.label :signed_at %><br />
<%= f.date_select :signed_at %>
</div>
<div class="form-group">
<%= f.label :document %><br />
<%= link_to "View Existing Document", @contract.document.url, class: "btn btn-default" unless @contract.document.blank? %>
<p>
<%= link_to "Upload New Document", "#", class: "btn btn-default", onclick: "$('#document_upload').removeClass('hidden'); $(this).addClass('hidden')" unless @contract.document.blank? %>
<div id="document_upload" class="<%= "hidden" unless @contract.document.blank? %>">
<%= f.file_field :document, class: "form-control", style: "width: 100%; height: 100px; background-color: #f9f9f9;" %>
</div>
</p>
</div>
<div class="form-group">
<%= f.submit nil, class: "btn btn-primary" %>
</div>
<% end %>

View File

@ -0,0 +1,4 @@
<h1>Edit Contract
<%= link_to 'Back', contracts_path, class: "btn btn-default" %>
</h1>
<%= render 'form' %>

View File

@ -0,0 +1,42 @@
<div class="row">
<h1 class="col-md-8">Contracts
<%= link_to 'Add Contract', new_contract_path, :class => "btn btn-success" if can? :create, Contract %>
</h1>
</div>
<table class="table">
<tr>
<th>Scan?</th>
<th>Name</th>
<th>User</th>
<th>Date</th>
<th></th>
</tr>
<% @contracts.sort_by{|r| (r.last_name) || "" }.each do |contract| %>
<tr>
<td>
<% unless contract.document.blank? %>
<span class="glyphicon glyphicon-ok"></span>
<% end %>
</td>
<td>
<%= contract.last_name %>,
<%= contract.first_name %>
<%= "and #{contract.cosigner}" unless contract.cosigner.blank? %>
</td>
<td>
<%= link_to contract.user.name, contract.user if contract.user %>
</td>
<td>
<%= contract.signed_at.to_date.to_s(:long) %>
</td>
<td>
<%= link_to "View", contract, class: "btn btn-primary" %>
<%= link_to "Edit", edit_contract_path(contract), class: "btn btn-default" %>
</td>
</tr>
<% end %>
</table>
<br />

View File

@ -0,0 +1,80 @@
<h1>New Contract
<%= link_to 'Back', contracts_path, class: "btn btn-default" %>
</h1>
<div class="col-xs-12">
<%= render 'form' %>
</div>
<div class="col-xs-12">
<div class="alert alert-info">Note: if you name your scans in one of the below formats, this form will try to fill itself out automatically when you select the file. (The spaces, hyphen, and jpg or pdf extensions are mandatory for this to work.) <em>Double-check the names, users, and dates, though!!</em><br/>
<strong>first last - date.[jpg|pdf]</strong><br/>
<strong>first last by cosigner name - date.[jpg|pdf]</strong><br/>
</div>
</div>
<script type="text/javascript">
function capitalizeIfIsntCapitalized(str){
if(str[0].toLowerCase() == str[0] ){
return str[0].toUpperCase() + str.slice(1);
}
else{
return str;
}
}
$(function() {
$("#contract_document").change(function (){
doc = document.getElementById("contract_document")
if(doc.files && doc.files.length > 0) {
name_split = doc.files[0].name.replace(".jpg","").replace(".pdf","").split(" ");
$("#contract_user_alert").remove(); // clear any existing alerts
$("#contract_user_spinner").remove(); // clear spinner
if(name_split.length == 4){ // we have one name
$("#contract_first_name").val(capitalizeIfIsntCapitalized(name_split[0]));
$("#contract_last_name").val(capitalizeIfIsntCapitalized(name_split[1]));
// 2 is the hyphen
signed_at = new Date(name_split[3]);
$("#contract_signed_at_1i").val(signed_at.getUTCFullYear()).change();
$("#contract_signed_at_2i").val(signed_at.getUTCMonth()+1).change();
$("#contract_signed_at_3i").val(signed_at.getUTCDate()).change();
}
else if(name_split.length == 7 && name_split[2] == "by"){ // we have two names
$("#contract_first_name").val(capitalizeIfIsntCapitalized(name_split[0]));
$("#contract_last_name").val(capitalizeIfIsntCapitalized(name_split[1]));
// 2 is "by"
$("#contract_cosigner").val(capitalizeIfIsntCapitalized(name_split[3])+" "+capitalizeIfIsntCapitalized(name_split[4]));
// 5 is the hyphen
signed_at = new Date(name_split[6]);
$("#contract_signed_at_1i").val(signed_at.getUTCFullYear());
$("#contract_signed_at_2i").val(signed_at.getUTCMonth()+1);
$("#contract_signed_at_3i").val(signed_at.getUTCDate());
}
// Try and select the relevant user if exists
if( $("#contract_first_name").val().length > 0 && $("#contract_last_name").val().length > 0 ) {
user_id = $('#contract_user_id option').filter(function(){
return $(this).text() == $("#contract_first_name").val() + " " + $("#contract_last_name").val();
}).prop("selected", "true").val();
// Start a spinner before AJAX request
$("#contract_user_id").after("<i id='contract_user_spinner' class='icon-spin icon-refresh'></i>");
// If we found a user, check how many contracts that user already has
$.get("/users/"+user_id+"/contracts.json",function(data){
$("#contract_user_spinner").remove(); // clear spinner
if(data.length > 0){
other_contract_links = $.map(data,function(item){
output = "<a href='/contracts/"+item["id"]+"'>"+item["id"]+"</a> &nbsp;&nbsp; ";
return output;
});
$("#contract_user_id").after("<span id='contract_user_alert' class='label label-danger'>Warning: this user already has "+data.length+" other contract(s). Check the existing one(s) to avoid duplication: "+other_contract_links+"</span>");
}
});
}
}
});
});
</script>

View File

@ -0,0 +1,41 @@
<div class="row">
<h1 class="col-md-8">
Contract
<%= link_to 'Back', contracts_path, :class => "btn btn-default" %>
<%= link_to 'New', new_contract_path, :class => "btn btn-success" %>
<%= link_to 'Edit', edit_contract_path(@contract), :class => "btn btn-primary" %>
<%= link_to 'Delete', contract_path(@contract), {:confirm => 'Are you sure you want to delete this forever?', :method => :delete, :class => "btn btn-danger"} if can? :destroy, @contract %>
</h1>
</div>
<h2>
<%= @contract.first_name %>
<%= @contract.last_name %>
<%= "and #{@contract.cosigner}" unless @contract.cosigner.blank? %>
<%= link_to "(#{@contract.user.name})", @contract.user if @contract.user %>
<small>
signed
<%= @contract.signed_at.to_date.to_s(:long) %>
</small>
</h2>
<% unless @contract.created_by.blank? %>
<p>
<em>Created by <%= @contract.created_by.name %></em>
</p>
<% end %>
<% if @contract.document.blank? %>
<p>No document uploaded</p>
<% else %>
<p><%= link_to "Download Contract", @contract.document.url %>
<div class="col-xs-12">
<% contract_url = (@contract.document.exists?(:large) ? @contract.document.url(:large) : @contract.document.url) %>
<iframe src="<%= contract_url %>" width="100%" height="600"></iframe>
</div>
</p>
<% end %>
<br />

0
app/views/devise/confirmations/new.html.erb Normal file → Executable file
View File

View File

View File

0
app/views/devise/mailer/unlock_instructions.html.erb Normal file → Executable file
View File

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