open_porch 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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
+