open_porch 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. data/Gemfile +41 -0
  2. data/Gemfile.lock +130 -0
  3. data/README.md +170 -0
  4. data/Rakefile +19 -0
  5. data/VERSION +1 -0
  6. data/app/controllers/admin/areas/base_controller.rb +11 -0
  7. data/app/controllers/admin/areas/issues_controller.rb +67 -0
  8. data/app/controllers/admin/areas/memberships_controller.rb +7 -0
  9. data/app/controllers/admin/areas/posts_controller.rb +44 -0
  10. data/app/controllers/admin/areas_controller.rb +89 -0
  11. data/app/controllers/admin/base_controller.rb +16 -0
  12. data/app/controllers/admin/user_activity_controller.rb +29 -0
  13. data/app/controllers/admin/users_controller.rb +57 -0
  14. data/app/controllers/application_controller.rb +15 -0
  15. data/app/controllers/areas/base_controller.rb +25 -0
  16. data/app/controllers/areas/issues_controller.rb +35 -0
  17. data/app/controllers/areas/posts_controller.rb +28 -0
  18. data/app/controllers/areas_controller.rb +30 -0
  19. data/app/controllers/passwords_controller.rb +42 -0
  20. data/app/controllers/registrations_controller.rb +42 -0
  21. data/app/controllers/sessions_controller.rb +33 -0
  22. data/app/controllers/users_controller.rb +77 -0
  23. data/app/helpers/open_porch_helper.rb +24 -0
  24. data/app/mailers/user_mailer.rb +35 -0
  25. data/app/models/address.rb +57 -0
  26. data/app/models/area.rb +171 -0
  27. data/app/models/area_activity.rb +24 -0
  28. data/app/models/email_message.rb +104 -0
  29. data/app/models/issue.rb +66 -0
  30. data/app/models/issue_number.rb +22 -0
  31. data/app/models/membership.rb +27 -0
  32. data/app/models/post.rb +64 -0
  33. data/app/models/session_user.rb +69 -0
  34. data/app/models/user.rb +140 -0
  35. data/app/models/user_activity.rb +31 -0
  36. data/app/models/user_authority_check.rb +14 -0
  37. data/app/views/admin/areas/_form.html.haml +8 -0
  38. data/app/views/admin/areas/_nav.html.haml +12 -0
  39. data/app/views/admin/areas/edit.html.haml +22 -0
  40. data/app/views/admin/areas/edit_borders.html.haml +44 -0
  41. data/app/views/admin/areas/index.html.haml +61 -0
  42. data/app/views/admin/areas/issues/_post.html.haml +15 -0
  43. data/app/views/admin/areas/issues/add_posts.js.rjs +3 -0
  44. data/app/views/admin/areas/issues/edit.html.haml +37 -0
  45. data/app/views/admin/areas/issues/index.html.haml +31 -0
  46. data/app/views/admin/areas/issues/remove_posts.js.rjs +3 -0
  47. data/app/views/admin/areas/issues/show.html.haml +13 -0
  48. data/app/views/admin/areas/memberships/index.html.haml +17 -0
  49. data/app/views/admin/areas/new.html.haml +6 -0
  50. data/app/views/admin/areas/new.js.haml +4 -0
  51. data/app/views/admin/areas/posts/_edit.html.haml +6 -0
  52. data/app/views/admin/areas/posts/_post_status.html.haml +1 -0
  53. data/app/views/admin/areas/posts/destroy.js.rjs +1 -0
  54. data/app/views/admin/areas/posts/edit.js.rjs +1 -0
  55. data/app/views/admin/areas/posts/show.js.rjs +1 -0
  56. data/app/views/admin/areas/posts/toggle_reviewed_by.js.rjs +1 -0
  57. data/app/views/admin/areas/posts/update.js.rjs +5 -0
  58. data/app/views/admin/areas/show.html.haml +5 -0
  59. data/app/views/admin/user_activity/show.html.haml +5 -0
  60. data/app/views/admin/users/_form.html.haml +21 -0
  61. data/app/views/admin/users/edit.html.haml +5 -0
  62. data/app/views/admin/users/index.html.haml +31 -0
  63. data/app/views/admin/users/new.html.haml +5 -0
  64. data/app/views/areas/issues/index.html.haml +31 -0
  65. data/app/views/areas/issues/show.html.haml +22 -0
  66. data/app/views/areas/posts/_post.html.haml +8 -0
  67. data/app/views/areas/posts/_posts_search_form.html.haml +8 -0
  68. data/app/views/areas/posts/index.html.haml +27 -0
  69. data/app/views/areas/posts/new.html.haml +10 -0
  70. data/app/views/areas/show.html.haml +47 -0
  71. data/app/views/layouts/_account_nav.html.haml +18 -0
  72. data/app/views/layouts/_flash_message.html.haml +4 -0
  73. data/app/views/layouts/_footer.html.haml +9 -0
  74. data/app/views/layouts/_head.html.haml +16 -0
  75. data/app/views/layouts/admin/_nav.html.haml +13 -0
  76. data/app/views/layouts/admin.html.haml +15 -0
  77. data/app/views/layouts/application.html.haml +14 -0
  78. data/app/views/layouts/area_editor.html.haml +11 -0
  79. data/app/views/passwords/edit.html.haml +9 -0
  80. data/app/views/passwords/new.html.haml +13 -0
  81. data/app/views/registrations/_address_form.html.haml +7 -0
  82. data/app/views/registrations/create.html.haml +49 -0
  83. data/app/views/registrations/index.html.haml +30 -0
  84. data/app/views/registrations/new.html.haml +17 -0
  85. data/app/views/sessions/new.html.haml +18 -0
  86. data/app/views/stylesheets/common.sass +239 -0
  87. data/app/views/stylesheets/content.sass +193 -0
  88. data/app/views/stylesheets/reset.sass +46 -0
  89. data/app/views/stylesheets/structure.sass +11 -0
  90. data/app/views/stylesheets/typography.sass +57 -0
  91. data/app/views/user_mailer/email_verification.html.erb +5 -0
  92. data/app/views/user_mailer/email_verification.text.erb +7 -0
  93. data/app/views/user_mailer/new_issue.html.erb +32 -0
  94. data/app/views/user_mailer/new_issue.text.erb +26 -0
  95. data/app/views/user_mailer/password_reset.html.erb +7 -0
  96. data/app/views/user_mailer/password_reset.text.erb +6 -0
  97. data/app/views/users/_form.html.haml +5 -0
  98. data/app/views/users/edit.html.haml +11 -0
  99. data/app/views/users/new.html.haml +41 -0
  100. data/app/views/users/show.html.haml +30 -0
  101. data/bin/open_porch_engine +135 -0
  102. data/config/application.rb +43 -0
  103. data/config/boot.rb +13 -0
  104. data/config/database_example.yml +59 -0
  105. data/config/environment.rb +5 -0
  106. data/config/environments/development.rb +26 -0
  107. data/config/environments/production.rb +49 -0
  108. data/config/environments/test.rb +35 -0
  109. data/config/initializers/backtrace_silencers.rb +7 -0
  110. data/config/initializers/email_regex.rb +38 -0
  111. data/config/initializers/geokit_config.rb +61 -0
  112. data/config/initializers/inflections.rb +20 -0
  113. data/config/initializers/meta_search.rb +7 -0
  114. data/config/initializers/mime_types.rb +5 -0
  115. data/config/initializers/open_porch.rb +41 -0
  116. data/config/initializers/sass.rb +1 -0
  117. data/config/initializers/secret_token.rb +7 -0
  118. data/config/initializers/session_store.rb +8 -0
  119. data/config/initializers/states_provinces.rb +2 -0
  120. data/config/initializers/will_paginate.rb +2 -0
  121. data/config/locales/en.yml +5 -0
  122. data/config/open_porch_example.yml +23 -0
  123. data/config/routes.rb +54 -0
  124. data/config/schedule.rb +9 -0
  125. data/config.ru +4 -0
  126. data/db/migrate/01_create_areas.rb +21 -0
  127. data/db/migrate/02_create_users.rb +28 -0
  128. data/db/migrate/03_create_memberships.rb +14 -0
  129. data/db/migrate/04_create_posts.rb +20 -0
  130. data/db/migrate/05_create_issue_numbers.rb +13 -0
  131. data/db/migrate/06_create_issues.rb +21 -0
  132. data/db/migrate/20110204173301_add_published_to_areas.rb +10 -0
  133. data/db/migrate/20110204194840_create_user_activities.rb +13 -0
  134. data/db/migrate/20110208163604_add_zip_to_areas.rb +11 -0
  135. data/db/migrate/20110209175723_add_counters_to_areas.rb +11 -0
  136. data/db/migrate/20110209182244_remove_subject_from_issues.rb +9 -0
  137. data/db/migrate/20110209190146_add_reviewer_info_to_posts.rb +9 -0
  138. data/db/migrate/20110215173144_add_email_validation_key_to_users.rb +13 -0
  139. data/db/migrate/20110215182716_remove_published_from_areas.rb +10 -0
  140. data/db/migrate/20110215211012_create_area_activities.rb +19 -0
  141. data/db/migrate/20110215213802_create_email_messages.rb +19 -0
  142. data/db/migrate/20110217165018_change_send_mode_to_string.rb +17 -0
  143. data/db/migrate/20110223160609_denormalize_user_info_in_posts.rb +19 -0
  144. data/db/seeds.rb +7 -0
  145. data/doc/README_FOR_APP +2 -0
  146. data/lib/generators/open_porch_generator.rb +37 -0
  147. data/lib/open_porch/engine.rb +20 -0
  148. data/lib/open_porch.rb +3 -0
  149. data/lib/tasks/.gitkeep +0 -0
  150. data/lib/tasks/open_porch.rake +10 -0
  151. data/lib/tasks/postageapp_tasks.rake +78 -0
  152. data/open_porch.gemspec +335 -0
  153. data/public/404.html +26 -0
  154. data/public/422.html +26 -0
  155. data/public/500.html +26 -0
  156. data/public/favicon.ico +0 -0
  157. data/public/images/icons/ajax-loader.gif +0 -0
  158. data/public/images/icons/calendar.png +0 -0
  159. data/public/images/icons/post_new.png +0 -0
  160. data/public/images/icons/post_reviewed.png +0 -0
  161. data/public/javascripts/application.js +3 -0
  162. data/public/javascripts/google_maps.js +153 -0
  163. data/public/javascripts/highcharts.js +162 -0
  164. data/public/javascripts/highcharts_init.js +30 -0
  165. data/public/javascripts/issue_edit.js +57 -0
  166. data/public/javascripts/jquery-ui.min.js +191 -0
  167. data/public/javascripts/jquery.js +154 -0
  168. data/public/javascripts/rails.js +137 -0
  169. data/public/javascripts/region_editor.js +616 -0
  170. data/public/javascripts/user_activity.js +29 -0
  171. data/public/robots.txt +5 -0
  172. data/public/stylesheets/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  173. data/public/stylesheets/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  174. data/public/stylesheets/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  175. data/public/stylesheets/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  176. data/public/stylesheets/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  177. data/public/stylesheets/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  178. data/public/stylesheets/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  179. data/public/stylesheets/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  180. data/public/stylesheets/images/ui-icons_222222_256x240.png +0 -0
  181. data/public/stylesheets/images/ui-icons_2e83ff_256x240.png +0 -0
  182. data/public/stylesheets/images/ui-icons_454545_256x240.png +0 -0
  183. data/public/stylesheets/images/ui-icons_888888_256x240.png +0 -0
  184. data/public/stylesheets/images/ui-icons_cd0a0a_256x240.png +0 -0
  185. data/public/stylesheets/jquery-ui.css +362 -0
  186. data/script/rails +6 -0
  187. data/test/dummy/area.rb +5 -0
  188. data/test/dummy/area_activity.rb +7 -0
  189. data/test/dummy/issue.rb +4 -0
  190. data/test/dummy/membership.rb +4 -0
  191. data/test/dummy/post.rb +12 -0
  192. data/test/dummy/user.rb +29 -0
  193. data/test/dummy/user_activity.rb +11 -0
  194. data/test/functional/admin/areas/issues_controller_test.rb +48 -0
  195. data/test/functional/admin/areas/memberships_controller_test.rb +45 -0
  196. data/test/functional/admin/areas/posts_controller_test.rb +54 -0
  197. data/test/functional/admin/areas_controller_test.rb +134 -0
  198. data/test/functional/admin/base_controller_test.rb +8 -0
  199. data/test/functional/admin/user_activity_controller_test.rb +28 -0
  200. data/test/functional/admin/users_controller_test.rb +144 -0
  201. data/test/functional/areas/issues_controller_test.rb +33 -0
  202. data/test/functional/areas/posts_controller_test.rb +50 -0
  203. data/test/functional/areas_controller_test.rb +12 -0
  204. data/test/functional/passwords_controller_test.rb +64 -0
  205. data/test/functional/registrations_controller_test.rb +64 -0
  206. data/test/functional/sessions_controller_test.rb +120 -0
  207. data/test/functional/users_controller_test.rb +144 -0
  208. data/test/performance/browsing_test.rb +9 -0
  209. data/test/test_helper.rb +92 -0
  210. data/test/unit/address_test.rb +25 -0
  211. data/test/unit/area_activity_test.rb +57 -0
  212. data/test/unit/area_test.rb +92 -0
  213. data/test/unit/email_message_test.rb +8 -0
  214. data/test/unit/issue_number_test.rb +25 -0
  215. data/test/unit/issue_test.rb +154 -0
  216. data/test/unit/membership_test.rb +20 -0
  217. data/test/unit/post_test.rb +69 -0
  218. data/test/unit/session_user_test.rb +65 -0
  219. data/test/unit/user_activity_test.rb +31 -0
  220. data/test/unit/user_mailer_test.rb +20 -0
  221. data/test/unit/user_test.rb +61 -0
  222. data/vendor/plugins/.gitkeep +0 -0
  223. metadata +456 -0
@@ -0,0 +1,104 @@
1
+ class EmailMessage < ActiveRecord::Base
2
+
3
+ # == Relationships ========================================================
4
+
5
+ has_many :posts
6
+
7
+ # == Class Methods ========================================================
8
+
9
+ def self.create_from_pop3
10
+ last_message_number = EmailMessage.maximum(:number).to_i
11
+ msg_count = 0
12
+
13
+ puts "Connecting to #{OPEN_PORCH_POP3['host']} ..."
14
+ Net::POP.enable_ssl(OpenSSL::SSL::VERIFY_NONE) if OPEN_PORCH_POP3['enable_ssl']
15
+ Net::POP3.start(OPEN_PORCH_POP3['host'], OPEN_PORCH_POP3['port'], OPEN_PORCH_POP3['username'], OPEN_PORCH_POP3['password']) do |pop|
16
+ puts "Found #{pop.n_mails} messages on the server"
17
+ puts "Last saved message: #{last_message_number}"
18
+ unless pop.mails.empty?
19
+ pop.mails.each do |m|
20
+ next if (m.number <= last_message_number)
21
+ print "\tSaving message ##{m.number} (#{"%.1f" % (m.length/1024.to_f)} KB) ... "
22
+
23
+ begin
24
+ email_message = EmailMessage.create(
25
+ :number => m.number,
26
+ :content => m.pop,
27
+ :size => m.length,
28
+ :parsed => false
29
+ )
30
+ print 'ok'
31
+ if email_message.parse
32
+ msg_count += 1
33
+ end
34
+ rescue ActiveRecord::StatementInvalid => e
35
+ puts
36
+ puts "========> ERROR saving message ##{m.number}"
37
+ puts e.message
38
+ end
39
+
40
+ break if msg_count >= 10
41
+ end
42
+ puts "\tCopied #{msg_count} new messages from the server."
43
+ end
44
+ end
45
+ puts "Done!"
46
+ rescue SocketError => e
47
+ puts "ERROR: Could not connect to #{OPEN_PORCH_POP3['host']} - #{e.message}"
48
+ end
49
+
50
+ # == Instance Methods ========================================================
51
+
52
+ def parse
53
+ return true if self.parsed?
54
+
55
+ print "\tParsing message:"
56
+ mail = Mail.new(self.content)
57
+
58
+ # Parse the body of the message
59
+ if mail.multipart?
60
+ if mail.text_part
61
+ body = mail.text_part.body
62
+ else
63
+ body = mail.parts[0].body
64
+ end
65
+ else
66
+ body = mail.body
67
+ end
68
+
69
+ # Find the user
70
+ user = User.where(:email => mail.from).first
71
+ if user.blank?
72
+ print "\tUser: Could not find user with email #{mail.from}"
73
+ puts
74
+ return false
75
+ end
76
+ print "\tUser: #{user.id}"
77
+
78
+ # Find the area
79
+ if mail.to.nil?
80
+ print "\tArea: recipient is empty. Skipping."
81
+ puts
82
+ end
83
+ slug = Mail::Address.new(mail.to.first).local
84
+ area = Area.where(:slug => slug).first
85
+ if area.blank?
86
+ print "\tArea: Could not find area with slug #{slug}"
87
+ puts
88
+ return false
89
+ end
90
+ print "\tArea: #{area.name}"
91
+
92
+ print "\tCreating post ... "
93
+ area.posts.create(
94
+ :user_id => user.id,
95
+ :email_message_id => self.id,
96
+ :title => mail.subject,
97
+ :content => Iconv.conv("UTF-8//TRANSLIT//IGNORE", 'UTF-8', body.to_s + ' ')[0..-2]
98
+ )
99
+
100
+ self.update_attribute(:parsed, true)
101
+ puts 'ok'
102
+ return true
103
+ end
104
+ end
@@ -0,0 +1,66 @@
1
+ class Issue < ActiveRecord::Base
2
+
3
+ # == Validations ==========================================================
4
+
5
+ validates :area_id,
6
+ :presence => true
7
+
8
+ validates :number,
9
+ :uniqueness => { :scope => :area_id }
10
+
11
+ validate :scheduled_time_is_ahead
12
+
13
+ # == Relationships ========================================================
14
+
15
+ belongs_to :area,
16
+ :counter_cache => true
17
+
18
+ has_many :posts,
19
+ :dependent => :destroy
20
+
21
+ # == Scopes ===============================================================
22
+
23
+ scope :sent, where('sent_at IS NOT NULL')
24
+
25
+ scope :scheduled_before, lambda { |t|
26
+ where(['sent_at IS NULL AND scheduled_at <= ?', t.utc])
27
+ }
28
+
29
+ scope :in_month, lambda { |m|
30
+ where(["to_char(sent_at, 'YYYY-MM') = ?", m])
31
+ }
32
+
33
+ # == Callbacks ============================================================
34
+
35
+ before_create :set_issue_number
36
+
37
+ # == Instance Methods =====================================================
38
+
39
+ def send!
40
+ UserMailer.new_issue(self).deliver
41
+
42
+ self.update_attribute(:sent_at, Time.now.utc)
43
+
44
+ # Create a new issue for the area if there are any new posts left
45
+ if self.area.send_mode?(:batched) && self.area.posts.in_issue(nil).count > 0
46
+ self.area.issues.create
47
+ end
48
+
49
+ self.area.record_activity_for!(:issues_published)
50
+ end
51
+
52
+ def to_params
53
+ self.number
54
+ end
55
+
56
+ protected
57
+ def scheduled_time_is_ahead
58
+ if self.scheduled_at.present? && (self.scheduled_at < Time.now.utc)
59
+ errors.add(:scheduled_at, "can't be in the past.")
60
+ end
61
+ end
62
+
63
+ def set_issue_number
64
+ self.number ||= self.area.issue_number.next
65
+ end
66
+ end
@@ -0,0 +1,22 @@
1
+ class IssueNumber < ActiveRecord::Base
2
+
3
+ # == Relationships ========================================================
4
+
5
+ belongs_to :area
6
+
7
+ # == Instance Methods =====================================================
8
+
9
+ def next
10
+ self.sequence_number = self.connection.select_value("
11
+ UPDATE #{self.class.table_name}
12
+ SET sequence_number=sequence_number+1
13
+ WHERE area_id= #{self.area_id}
14
+ RETURNING sequence_number"
15
+ ).to_i
16
+ end
17
+
18
+ def current
19
+ self.sequence_number
20
+ end
21
+
22
+ end
@@ -0,0 +1,27 @@
1
+ class Membership < ActiveRecord::Base
2
+
3
+ # == Validations ==========================================================
4
+
5
+ validates :user_id,
6
+ :uniqueness => { :scope => :area_id }
7
+
8
+ # == Relationships ========================================================
9
+
10
+ belongs_to :user
11
+ belongs_to :area,
12
+ :counter_cache => true
13
+
14
+ # == Callbacks ============================================================
15
+
16
+ after_create :record_activity_for_new_users
17
+ after_destroy :record_activity_for_quitters
18
+
19
+ protected
20
+ def record_activity_for_new_users
21
+ self.area.record_activity_for!(:new_users)
22
+ end
23
+
24
+ def record_activity_for_quitters
25
+ self.area.record_activity_for!(:quitters)
26
+ end
27
+ end
@@ -0,0 +1,64 @@
1
+ class Post < ActiveRecord::Base
2
+
3
+ # == Validations ==========================================================
4
+
5
+ validates :title, :content, :area_id, :user_id,
6
+ :presence => true
7
+ validates :user_first_name, :user_last_name, :user_email, :user_address,
8
+ :user_city, :user_state,
9
+ :presence => true
10
+
11
+ # == Relationships ========================================================
12
+
13
+ belongs_to :area
14
+ belongs_to :user
15
+ belongs_to :issue
16
+ belongs_to :email_message
17
+
18
+ # == Callbacks ============================================================
19
+
20
+ before_validation :copy_user_info
21
+ after_create :create_issue, :record_activity_for_new_post
22
+
23
+ # == Scope ================================================================
24
+
25
+ scope :in_issue, lambda { |issue| where(:issue_id => issue) }
26
+
27
+ # == Instance Methods =====================================================
28
+
29
+ def reviewed?
30
+ self.reviewed_by.present?
31
+ end
32
+
33
+ def user_full_name
34
+ [user_first_name, user_last_name].join(' ')
35
+ end
36
+
37
+ def send_immediatelly!
38
+ self.issue = self.area.issues.create!
39
+ self.save
40
+ self.issue.reload
41
+ self.issue.send!
42
+ end
43
+
44
+ protected
45
+ def create_issue
46
+ if self.area.send_mode?(:immediate)
47
+ self.send_immediatelly!
48
+ elsif self.area.send_mode?(:batched) && self.area.current_issue.blank?
49
+ self.area.issues.create
50
+ end
51
+ end
52
+
53
+ def record_activity_for_new_post
54
+ self.area.record_activity_for!(:new_posts)
55
+ end
56
+
57
+ def copy_user_info
58
+ if self.user.present?
59
+ %w(first_name last_name email address city state).each do |field|
60
+ self.send("user_#{field}=", self.user.send(field))
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,69 @@
1
+ class SessionUser
2
+ include ActiveModel::Validations
3
+
4
+ # == Attribute ==========================================================
5
+
6
+ attr_accessor :email
7
+ attr_accessor :password
8
+ attr_accessor :remember_me
9
+ attr_accessor :user
10
+
11
+ # == Validations ==========================================================
12
+
13
+ validates :email,
14
+ :presence => {:message => 'Please enter your email address'},
15
+ :length => {
16
+ :within => 6..100,
17
+ :too_short => "The email address you entered is to short"
18
+ },
19
+ :format => {
20
+ :with => /^([\w.%-+]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i,
21
+ :message => 'The email address you entered is not valid'
22
+ }
23
+
24
+ validates :password,
25
+ :length => {
26
+ :within => 4..40,
27
+ :too_short => "The password you entered is too short (minimum is 4 characters)"
28
+ },
29
+ :presence => {
30
+ :message => 'Please choose a password'
31
+ },
32
+ :confirmation => {:message => "The password you entered does not match with the confirmation"}
33
+
34
+ # == Class Methods ========================================================
35
+
36
+ def self.create(params={})
37
+ session_user = self.new(params)
38
+ session_user.save
39
+ session_user
40
+ end
41
+
42
+ # == Instance Methods =====================================================
43
+
44
+ def initialize(params = {})
45
+ if params
46
+ @email = params[:email]
47
+ @password = params[:password]
48
+ @remember_me = params[:remember_me]
49
+ end
50
+ end
51
+
52
+ def save
53
+ return unless self.valid?
54
+ if @user = User.authenticate(self.email, self.password)
55
+ if @user.is_verified?
56
+ return @user
57
+ else
58
+ @user.email_verification_key ||= @user.set_email_verification_key
59
+ @user.save!
60
+ self.errors.add(:email, "This email address needs to be verified before you can login. <a href='/resend-email-verification/#{@user.email_verification_key}'>Resend verification</a>".html_safe)
61
+ return false
62
+ end
63
+ end
64
+ end
65
+
66
+ def to_key
67
+ nil
68
+ end
69
+ end
@@ -0,0 +1,140 @@
1
+ class User < ActiveRecord::Base
2
+
3
+ # == Constants ============================================================
4
+
5
+ ROLES = %w{admin regular_user}
6
+
7
+ # == Attributes =====================================================
8
+
9
+ attr_protected :role
10
+
11
+ # == Extensions ===========================================================
12
+
13
+ if defined?(Wristband)
14
+ wristband :roles => ROLES, :has_authorities => true
15
+ end
16
+
17
+ # == Validations ==========================================================
18
+
19
+ validates :first_name,
20
+ :presence => {:message => 'Please enter your first name'}
21
+
22
+ validates :last_name,
23
+ :presence => {:message => 'Please enter your last name'}
24
+
25
+ validates :email,
26
+ :presence => {:message => 'Please enter your email address'},
27
+ :length => {
28
+ :within => 6..100,
29
+ :too_short => "The email address you entered is to short"
30
+ },
31
+ :format => {
32
+ :with => EmailSupport::RFC822::EmailAddress,
33
+ :message => 'The email address you entered is not valid'
34
+ },
35
+ :uniqueness => {:message => 'This email has already been taken'}
36
+
37
+ validates :password,
38
+ :length => {
39
+ :within => 4..40,
40
+ :too_short => "The password you entered is too short (minimum is 4 characters)",
41
+ :if => :password_required?
42
+ },
43
+ :presence => {
44
+ :message => 'Please choose a password',
45
+ :if => :password_required?
46
+ },
47
+ :confirmation => {:message => "The password you entered does not match with the confirmation"}
48
+
49
+ validates :role,
50
+ :inclusion => { :in => ROLES },
51
+ :presence => true
52
+
53
+ validates :address, :city, :state,
54
+ :presence => {
55
+ :message => 'Please enter your full address'
56
+ }
57
+
58
+ # == Relationships ========================================================
59
+
60
+ has_many :memberships,
61
+ :dependent => :destroy
62
+ has_many :areas,
63
+ :through => :memberships
64
+ has_many :posts,
65
+ :dependent => :nullify
66
+
67
+ accepts_nested_attributes_for :memberships, :allow_destroy => true
68
+
69
+ # == Scopes ===============================================================
70
+
71
+ # Search Scope
72
+ scope :email_or_name_or_address_search,
73
+ lambda {|str|
74
+ like_str = "%#{str}%"
75
+ where("email ILIKE ? OR ((first_name || ' ' || last_name) ILIKE ?) OR ((address || ', ' || city || ', ' || state) ILIKE ?)",
76
+ like_str, like_str, like_str)
77
+ }
78
+
79
+ if defined?(MetaSearch)
80
+ search_methods :email_or_name_or_address_search
81
+ end
82
+
83
+ scope :admins, where(:role => 'admin')
84
+
85
+ # == Callbacks ============================================================
86
+
87
+ before_validation :assign_role, :on => :create
88
+
89
+ # == Class Methods ========================================================
90
+
91
+ def self.per_page
92
+ 100
93
+ end
94
+
95
+ def self.roles_for_select
96
+ ROLES.collect{|r| [r.humanize, r]}
97
+ end
98
+
99
+ # == Instance Methods =====================================================
100
+
101
+ def address_attributes=(address_attr)
102
+ if address_attr.is_a?(Address)
103
+ self.address = address_attr.address
104
+ self.city = address_attr.city
105
+ self.state = address_attr.state
106
+ self.lat = address_attr.lat
107
+ self.lng = address_attr.lng
108
+ end
109
+ end
110
+
111
+ def full_address
112
+ [self.address, self.city, self.state].join(', ')
113
+ end
114
+
115
+ def full_name
116
+ [first_name, last_name].join(' ')
117
+ end
118
+
119
+ def member_of?(area)
120
+ self.areas.include?(area)
121
+ end
122
+
123
+ def set_email_verification_key
124
+ self.email_verification_key = Wristband::Support.random_salt.gsub(/[^A-Za-z0-9]/,'')
125
+ end
126
+
127
+ def is_verified?
128
+ !!self.verified_at?
129
+ end
130
+
131
+ protected
132
+ def assign_role
133
+ self.role = 'regular_user'
134
+ end
135
+
136
+ def password_required?
137
+ self.new_record?
138
+ end
139
+
140
+ end
@@ -0,0 +1,31 @@
1
+ class UserActivity < ActiveRecord::Base
2
+
3
+ # == Constants ============================================================
4
+
5
+ EXPIRES = 5 # in seconds
6
+
7
+ # == Relationships ========================================================
8
+
9
+ validates :name, :url,
10
+ :presence => true
11
+
12
+ # == Scopes ===============================================================
13
+
14
+ scope :active_for_page, lambda {|name, url|
15
+ where(
16
+ "expires_at >= ? AND name != ? AND url = ?",
17
+ Time.now, name, url
18
+ )
19
+ }
20
+
21
+ # == Callbacks ============================================================
22
+
23
+ after_create :set_expiration!
24
+
25
+ # == Instance Methods =====================================================
26
+
27
+ def set_expiration!
28
+ self.update_attribute(:expires_at, Time.now.advance(:seconds => EXPIRES))
29
+ end
30
+
31
+ end
@@ -0,0 +1,14 @@
1
+ class UserAuthorityCheck < AuthorityCheck
2
+
3
+ def manage_users?
4
+ unless (@user.is_admin?)
5
+ fail!("You don't have permission do manage users.")
6
+ end
7
+ end
8
+
9
+ def manage_areas?
10
+ unless (@user.is_admin?)
11
+ fail!("You don't have permission do manage users.")
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,8 @@
1
+ = f.text_field :name
2
+ = f.text_field :slug
3
+ = f.text_area :description
4
+ = f.text_field :city
5
+ = f.text_field :state
6
+ = f.text_field :households
7
+ = f.select :send_mode, Area::SEND_MODES
8
+ = f.submit 'Save', :cancel_url => link_to('cancel', admin_areas_path)
@@ -0,0 +1,12 @@
1
+ %ul.sub_nav
2
+ %li
3
+ %h3
4
+ = @area.name
5
+ in
6
+ = @area.location
7
+ - if @area.current_issue
8
+ %li= active_link_to 'Current issue', [:edit, :admin, @area, @area.current_issue], :active => { :when => /admin\/areas\/\d*\/issues\/\d*\/edit/ }
9
+ %li= active_link_to 'Issues', [:admin, @area, :issues], :active => { :when => :self_only }
10
+ %li= active_link_to 'Memberships', [:admin, @area, :memberships]
11
+ %li= active_link_to 'Edit borders', [:edit_borders, :admin, @area]
12
+ %li= active_link_to 'Edit details', [:edit, :admin, @area], :active => { :when => :self_only }
@@ -0,0 +1,22 @@
1
+ .columns
2
+ = render :partial => 'admin/areas/nav'
3
+
4
+ .left_column
5
+
6
+ = formatted_form_for @area, :url => [:admin, @area] do |f|
7
+ = render :partial => 'form', :locals => {:f => f}
8
+
9
+
10
+ .right_column
11
+
12
+ - if @area.border.present?
13
+ = content_for :head do
14
+ = javascript_include_tag "http://maps.google.com/maps/api/js?sensor=false"
15
+ = javascript_include_tag 'google_maps'
16
+
17
+ = content_for :doc_ready do
18
+ initialize_gmap(document.getElementById("map_canvas"), #{@area.center.x}, #{@area.center.y}, {}, #{@area.bounds.to_json});
19
+ add_region(#{@area.id}, [#{@area.border_coordinates}], 'selected')
20
+
21
+ #map_canvas{:style => 'width:260px; height:400px; float: right'}
22
+
@@ -0,0 +1,44 @@
1
+ .columns
2
+ = content_for :head do
3
+ = javascript_include_tag "http://maps.google.com/maps/api/js?libraries=geometry&sensor=false"
4
+ = javascript_include_tag 'region_editor'
5
+
6
+ :javascript
7
+ $(document).ready(function() {
8
+ $('#map_canvas').RegionEditor({
9
+ create_url: '#{admin_areas_path}',
10
+ update_url: '#{bulk_update_admin_areas_path}',
11
+ regions: #{@areas.collect(&:to_a).to_json},
12
+ selected_region_id: #{@selected_area.present? ? @selected_area.id : 'null'},
13
+ center: {
14
+ lat: #{@selected_area.present? ? @selected_area.center.x : @default_area.center.x},
15
+ lng: #{@selected_area.present? ? @selected_area.center.y : @default_area.center.y}
16
+ },
17
+ bounds: #{@selected_area.present? ? @selected_area.bounds.to_json : @default_area.bounds.to_json}
18
+ });
19
+ });
20
+ - if @area.present?
21
+ = render :partial => 'admin/areas/nav'
22
+
23
+ #map_canvas
24
+ .region_details
25
+
26
+ %h3 Manage regions:
27
+ %p
28
+ %strong 1. Click on a region to select it
29
+ %br/
30
+ %strong 2. Drag the markers to change the shape of a region
31
+ %br/
32
+ %strong 3. To add a new point to a region:
33
+ Right click on the map to add a new point to the selected region
34
+ %br/
35
+ %strong 4. To delete a point from a region:
36
+ SHIFT + Click on a marker to delete it
37
+
38
+ %h3 Create new regions:
39
+ %p
40
+ %strong 1. Click the 'New region' button
41
+ %br/
42
+ %strong 2. Click on the map to add points to the region
43
+ %br/
44
+ %strong 3. When you're done close the region by clicking on the first marker
@@ -0,0 +1,61 @@
1
+ .columns
2
+ .header
3
+ %h2 Areas
4
+ = formatted_form_for @search, :url => [:admin, :areas], :html => {:method => :get, :class => 'admin_search'} do |f|
5
+ = f.text_field :full_name_search, :label => 'name or location'
6
+ = f.element('and show activity between') do
7
+ = text_field_tag :activity_start_date, params[:activity_start_date], :type => :date
8
+ = text_field_tag :activity_end_date, params[:activity_end_date], :type => :date
9
+ = f.submit 'Find'
10
+ %h5= link_to 'Add new area', edit_borders_admin_area_path('new')
11
+
12
+ :javascript
13
+ $(document).ready(function() {
14
+ draw_chart({
15
+ days : #{@activities.collect{|d| d.day.strftime('%d<br/>%b')}.to_json},
16
+ series : [
17
+ {
18
+ name: 'New Users',
19
+ data : #{@activities.collect{|a| a.sum_new_users_count.to_i}.to_json}
20
+ },
21
+ {
22
+ name: 'Quitters',
23
+ data : #{@activities.collect{|a| a.sum_quitters_count.to_i}.to_json}
24
+ },
25
+ {
26
+ name: 'New Posts',
27
+ data : #{@activities.collect{|a| a.sum_new_posts_count.to_i}.to_json}
28
+ },
29
+ {
30
+ name: 'Issues Published',
31
+ data : #{@activities.collect{|a| a.sum_issues_published_count.to_i}.to_json}
32
+ }
33
+ ]
34
+ });
35
+ })
36
+ #chart-container
37
+
38
+ = will_paginate @areas
39
+
40
+ %table
41
+ %tr
42
+ %th= sort_link @search, :name
43
+ %th= sort_link @search, :city
44
+ %th.center= sort_link @search, :memberships_count, 'Users'
45
+ %th.center= sort_link @search, :households
46
+ %th.center Send mode
47
+ %th.center= sort_link @search, :issues_count, 'Issues'
48
+ %th.center New posts
49
+
50
+ - @areas.each do |area|
51
+ %tr{:class => @new_posts[area.id] && @new_posts[area.id] > 0 ? 'important' : ''}
52
+ %td= link_to area.name, [:edit_borders, :admin, area]
53
+ %td= area.location
54
+ %td.center= link_to area.memberships_count.to_i, [:admin, area, :memberships]
55
+ %td.center= area.households.to_i
56
+ %td.center= area.send_mode
57
+ %td.center= link_to area.issues_count.to_i, [:admin, area, :issues]
58
+ %td.center= link_to @new_posts[area.id].to_s, [:admin, area]
59
+
60
+ = will_paginate @areas
61
+