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,15 @@
1
+ class ApplicationController < ActionController::Base
2
+ protect_from_forgery
3
+ before_filter :login_from_cookie
4
+ before_filter :login_required
5
+
6
+ protected
7
+ def login_required
8
+ redirect_to login_path unless logged_in?
9
+ end
10
+
11
+ def redirect_if_logged_in
12
+ redirect_to area_path(current_user.areas.first) if logged_in?
13
+ end
14
+
15
+ end
@@ -0,0 +1,25 @@
1
+ class Areas::BaseController < ApplicationController
2
+ before_filter :load_area
3
+ before_filter :initialize_search
4
+
5
+ protected
6
+ def load_area
7
+ if current_user.is_admin?
8
+ @area = Area.find(params[:area_id] || params[:id])
9
+ else
10
+ @area = current_user.areas.find(params[:area_id] || params[:id])
11
+ end
12
+ rescue ActiveRecord::RecordNotFound
13
+ flash[:alert] = "The area you were looking for was not found"
14
+ if current_user.areas.empty?
15
+ redirect_to user_path
16
+ else
17
+ redirect_to area_path(current_user.areas.first)
18
+ end
19
+ end
20
+
21
+ def initialize_search
22
+ params[:search] ||= {}
23
+ @search = @area.posts.search(params[:search])
24
+ end
25
+ end
@@ -0,0 +1,35 @@
1
+ class Areas::IssuesController < Areas::BaseController
2
+ before_filter :load_issue,
3
+ :only => :show
4
+
5
+ def index
6
+ if params[:year].present? and params[:month].present?
7
+ @current_month = Time.parse([params[:year], params[:month], '01'].join('-')).to_date
8
+ else
9
+ @current_month = Date.today.beginning_of_month
10
+ end
11
+ @issues = @area.issues.sent.in_month(@current_month.strftime("%Y-%m")).order('sent_at DESC')
12
+ end
13
+
14
+ def show
15
+ @prev_issue = @area.issues.find_by_number(@issue.number-1)
16
+ @next_issue = @area.issues.find_by_number(@issue.number+1)
17
+ end
18
+
19
+ def current
20
+ @issue = @area.issues.sent.last
21
+ if @issue.present?
22
+ render :template => 'areas/issues/show'
23
+ else
24
+ render :text => 'Issue not found', :status => 404
25
+ end
26
+ end
27
+
28
+ protected
29
+ def load_issue
30
+ @issue = @area.issues.sent.find_by_number!(params[:id])
31
+ rescue ActiveRecord::RecordNotFound
32
+ render :text => 'Issue not found', :status => 404
33
+ end
34
+
35
+ end
@@ -0,0 +1,28 @@
1
+ class Areas::PostsController < Areas::BaseController
2
+ before_filter :build_post,
3
+ :only => [:new, :create]
4
+
5
+ def index
6
+ params[:search] ||= {}
7
+ @search = @area.posts.order('created_at DESC').joins(:issue).where('sent_at IS NOT NULL').search(params[:search])
8
+ @posts = @search.paginate(:page => params[:page])
9
+ end
10
+
11
+ def new
12
+ end
13
+
14
+ def create
15
+ @post.save!
16
+ redirect_to(area_path(@area), :notice => 'Your message has been successfully posted.')
17
+ rescue ActiveRecord::RecordInvalid
18
+ render :action => :new
19
+ end
20
+
21
+ protected
22
+
23
+ def build_post
24
+ @post = @area.posts.new(params[:post])
25
+ @post.user = current_user
26
+ end
27
+
28
+ end
@@ -0,0 +1,30 @@
1
+ class AreasController < Areas::BaseController
2
+ skip_before_filter :login_required, :only => :show
3
+ before_filter :load_area, :only => :show
4
+ before_filter :initialize_search
5
+
6
+ def show
7
+ end
8
+
9
+ protected
10
+ def load_area
11
+ if logged_in?
12
+ if current_user.is_admin?
13
+ @area = Area.find(params[:id])
14
+ else
15
+ @area = current_user.areas.find(params[:id])
16
+ end
17
+ else
18
+ @session_user = SessionUser.new
19
+ @area = Area.find(params[:id])
20
+ end
21
+ rescue ActiveRecord::RecordNotFound
22
+ flash[:alert] = "The area you were looking for was not found"
23
+ if current_user.areas.empty?
24
+ redirect_to user_path
25
+ else
26
+ redirect_to area_path(current_user.areas.first)
27
+ end
28
+ end
29
+
30
+ end
@@ -0,0 +1,42 @@
1
+ class PasswordsController < ApplicationController
2
+ skip_before_filter :login_required
3
+ before_filter :redirect_if_logged_in, :only => [ :new, :create, :edit, :update ]
4
+ before_filter :load_user_by_perishable_token, :only => [:edit, :update]
5
+
6
+ def new
7
+ # ...
8
+ end
9
+
10
+ def create
11
+ user = User.find_by_email!(params[:email])
12
+ user.reset_perishable_token!
13
+ UserMailer.password_reset(user).deliver
14
+ flash[:notice] = 'Email to reset password successfully sent.'
15
+ redirect_to login_path
16
+ rescue ActiveRecord::RecordNotFound
17
+ flash.now[:alert] = 'Your email was not found. Did you mistype?'
18
+ render :action => :new
19
+ end
20
+
21
+ def edit
22
+ # ...
23
+ end
24
+
25
+ def update
26
+ @user.update_attributes!(params[:user].merge(:perishable_token => nil))
27
+ login_as_user(@user)
28
+ flash[:notice] = 'Your password was successfully updated!'
29
+ redirect_to user_path(@user)
30
+ rescue ActiveRecord::RecordInvalid
31
+ flash[:notice] = 'An error occurred. Please try again.'
32
+ redirect_to login_path
33
+ end
34
+
35
+
36
+ protected
37
+ def load_user_by_perishable_token
38
+ @user = User.find_by_perishable_token!(params[:id])
39
+ rescue ActiveRecord::RecordNotFound
40
+ redirect_to login_path
41
+ end
42
+ end
@@ -0,0 +1,42 @@
1
+ class RegistrationsController < ApplicationController
2
+ skip_before_filter :login_required
3
+ before_filter :redirect_if_logged_in
4
+ before_filter :build_address, :only => [:new, :index, :create]
5
+
6
+ def index
7
+ # ...
8
+ end
9
+
10
+ def new
11
+ # ...
12
+ end
13
+
14
+ def create
15
+ if @address.valid?
16
+ @areas = @address.closest_regions
17
+ if @areas.empty?
18
+ flash[:alert] = "Sorry, we couldn't find any neighbourhoods close to you!"
19
+ redirect_to root_path
20
+ else
21
+ @selected_area = @areas.shift
22
+ @address.area_id = @selected_area.id
23
+
24
+ @user = User.new(:address_attributes => @address)
25
+ @user.memberships.build(:area_id => @selected_area.id)
26
+ end
27
+ else
28
+ flash[:alert] = "Please enter a valid address"
29
+ redirect_to root_path
30
+ end
31
+ rescue ActiveRecord::StatementInvalid
32
+ flash[:alert] = "Sorry, we couldn't find the address you specified"
33
+ redirect_to root_path
34
+ end
35
+
36
+
37
+ protected
38
+ def build_address
39
+ @address = Address.new(params[:address])
40
+ end
41
+
42
+ end
@@ -0,0 +1,33 @@
1
+ class SessionsController < ApplicationController
2
+ skip_before_filter :login_required, :except => :destroy
3
+ before_filter :redirect_if_logged_in, :only => [ :new, :create ]
4
+ before_filter :build_user_session, :only => [ :new, :create ]
5
+
6
+ def create
7
+ if @session_user.save
8
+ login(@session_user)
9
+ if current_user.areas.empty?
10
+ redirect_to(user_path, :notice => "Welcome, you are now logged in.")
11
+ else
12
+ redirect_to(area_path(current_user.areas.first), :notice => "Welcome, you are now logged in.")
13
+ end
14
+ else
15
+ flash.now[:alert] = 'Login failed. Did you mistype?'
16
+ render :action => :new
17
+ end
18
+ end
19
+
20
+ def destroy
21
+ logout
22
+ redirect_to root_path
23
+ end
24
+
25
+ protected
26
+ def build_user_session
27
+ @session_user = SessionUser.new(params[:session_user])
28
+ end
29
+
30
+ def redirect_if_logged_in
31
+ redirect_to user_path if logged_in?
32
+ end
33
+ end
@@ -0,0 +1,77 @@
1
+ class UsersController < ApplicationController
2
+ skip_before_filter :login_required, :only => [:new, :create, :verify_email, :resend_email_verification]
3
+ before_filter :redirect_if_logged_in, :only => [ :new, :create, :verify_email, :resend_email_verification ]
4
+ before_filter :build_user, :only => [:new, :create]
5
+ before_filter :load_user, :only => [:edit, :update, :destroy]
6
+
7
+ def new
8
+ # ...
9
+ end
10
+
11
+ def create
12
+ @user.set_email_verification_key
13
+ @user.save!
14
+ UserMailer::email_verification(@user).deliver
15
+ redirect_to root_path, :notice => "Thank you, please check your email to complete the registration."
16
+ rescue ActiveRecord::RecordInvalid
17
+ render :action => :new
18
+ end
19
+
20
+ def edit; end
21
+
22
+ def update
23
+ @user.update_attributes!(params[:user])
24
+ redirect_to(user_path, :notice => 'Your profile has been updated')
25
+ rescue ActiveRecord::RecordInvalid
26
+ render :action => :edit
27
+ end
28
+
29
+ def verify_email
30
+ if @user = User.where(:email_verification_key => params[:email_verification_key]).first
31
+ @user.update_attributes(
32
+ :verified_at => Time.now,
33
+ :email_verification_key => nil
34
+ )
35
+ flash[:notice] = "Your email address has been verified. You can now login"
36
+ redirect_to login_path
37
+ else
38
+ flash[:alert] = "We were not able to verify your account. Please contact us for assistance."
39
+ redirect_to root_path
40
+ end
41
+ end
42
+
43
+ def resend_email_verification
44
+ if @user = User.where(:email_verification_key => params[:email_verification_key]).first
45
+ # Just ignore it if the user is already verified
46
+ if @user.is_verified?
47
+ flash[:notice] = "Your account has already been validated. You can now login."
48
+ else
49
+ UserMailer::email_verification(@user).deliver
50
+ flash[:notice] = "The email verification has been sent to #{@user.email}"
51
+ end
52
+ end
53
+ redirect_to login_path
54
+ end
55
+
56
+ def destroy
57
+ logout
58
+ @user.destroy
59
+ flash[:alert] = "Your account has been deleted."
60
+ redirect_to root_path
61
+ end
62
+
63
+ protected
64
+ def build_user
65
+ @user = User.new(params[:user])
66
+ if @user.memberships.empty?
67
+ redirect_to root_path
68
+ else
69
+ @area = @user.memberships.first.area
70
+ end
71
+ end
72
+
73
+ def load_user
74
+ @user = current_user
75
+ end
76
+
77
+ end
@@ -0,0 +1,24 @@
1
+ module OpenPorchHelper
2
+ def show_user_activity_for_url(url)
3
+ content_for(:doc_ready) do
4
+ raw %{get_user_activity('#{current_user.full_name}', '#{url}', '#{UserActivity::EXPIRES * 1000 }');} # multiply by 1000 because javascript requires miliseconds
5
+ end
6
+ content_tag(:div, '', :id => "user_activity")
7
+ end
8
+
9
+ def update_user_activity_for_url(url)
10
+ content_for(:doc_ready) do
11
+ raw %{update_user_activity('#{current_user.full_name}', '#{url}', '#{(UserActivity::EXPIRES-2) * 1000}');}
12
+ end
13
+ end
14
+
15
+ def show_ad_for(zone_name, area)
16
+ return unless defined?(OPEN_PORCH_ZONES)
17
+ time = Time.now.to_i
18
+
19
+ link_to("http://d1.openx.org/ck.php?cb=#{time}&n=a8417ca6", :class => "ad #{zone_name}", :target => '_blank') do
20
+ image_tag("http://d1.openx.org/avw.php?zoneid=#{OPEN_PORCH_ZONES[zone_name.to_s]}&region=#{@area.slug}&cb=#{time}&n=a8417ca6")
21
+ end
22
+ end
23
+ end
24
+
@@ -0,0 +1,35 @@
1
+ class UserMailer < PostageApp::Mailer
2
+ default :from => 'open_porch@example.com'
3
+
4
+ if defined?(ActionController::Base)
5
+ default_url_options[:host] = defined?(HOST) ? HOST : 'localhost:3000'
6
+ end
7
+
8
+ def password_reset(user)
9
+ postageapp_template 'main_layout'
10
+ @user = user
11
+ mail(
12
+ :to => user.email,
13
+ :subject => "You have requested a new password"
14
+ )
15
+ end
16
+
17
+ def new_issue(issue)
18
+ postageapp_template 'main_layout'
19
+ @issue = issue
20
+ mail(
21
+ :subject => "#{issue.number}-#{issue.area.name}",
22
+ :to => issue.area.users.collect(&:email)
23
+ )
24
+ end
25
+
26
+ def email_verification(user)
27
+ postageapp_template 'main_layout'
28
+ @user = user
29
+ mail(
30
+ :subject => "Your account has been created. Please verify your email address",
31
+ :to => user.email
32
+ )
33
+ end
34
+
35
+ end
@@ -0,0 +1,57 @@
1
+ class Address
2
+ include ActiveModel::Validations
3
+ include GeoKit::Geocoders
4
+
5
+ # == Constants ============================================================
6
+
7
+ # == Attributes =====================================================
8
+
9
+ # == Attribute ==========================================================
10
+
11
+ attr_accessor :address, :city, :state, :area_id
12
+ attr_accessor :location, :lat, :lng
13
+
14
+ # == Validations ==========================================================
15
+
16
+ validates :address, :city, :state,
17
+ :presence => {:message => 'Please enter your address'}
18
+
19
+ # == Instance Methods =====================================================
20
+
21
+ def initialize(params = {})
22
+ if params
23
+ @address = params[:address]
24
+ @city = params[:city]
25
+ @state = params[:state]
26
+ end
27
+ end
28
+
29
+ def to_key; end
30
+
31
+ def full
32
+ [self.address, self.city, self.state].join(', ')
33
+ end
34
+
35
+ def closest_regions
36
+ location = self.geolocate
37
+ if location.all.length > 1 || location.street_address.nil?
38
+ []
39
+ else
40
+ point = Point.new()
41
+ point.set_x_y(self.lat, self.lng)
42
+ Area.closest_from(point, 400)
43
+ end
44
+ end
45
+
46
+ protected
47
+
48
+ def geolocate
49
+ @location ||= begin
50
+ loc = Geokit::Geocoders::GoogleGeocoder.geocode(self.full)
51
+ self.lat = loc.lat
52
+ self.lng = loc.lng
53
+ loc
54
+ end
55
+ end
56
+
57
+ end
@@ -0,0 +1,171 @@
1
+ class Area < ActiveRecord::Base
2
+
3
+ # == Constants ============================================================
4
+
5
+ SEND_MODES = %w{immediate batched}
6
+
7
+ # == Relationships ========================================================
8
+
9
+ has_many :memberships,
10
+ :dependent => :destroy
11
+ has_many :users,
12
+ :through => :memberships
13
+ has_many :posts,
14
+ :dependent => :destroy
15
+ has_one :issue_number,
16
+ :dependent => :destroy
17
+ has_many :issues,
18
+ :dependent => :destroy
19
+ has_many :activities,
20
+ :dependent => :destroy,
21
+ :class_name => 'AreaActivity'
22
+
23
+ # == Validations ==========================================================
24
+
25
+ validates :name,
26
+ :presence => {:message => 'Please enter the name of this area'}
27
+ validates :slug,
28
+ :uniqueness => true,
29
+ :format => { :with => /^[\w.-]+$/ },
30
+ :allow_nil => true
31
+ validates :send_mode,
32
+ :presence => true,
33
+ :inclusion => SEND_MODES
34
+
35
+ # == Scopes ===============================================================
36
+
37
+ scope :closest_from, lambda {|point, distance|
38
+ where("ST_DWithin(border, ST_GeomFromEWKT('SRID=4326;POINT(#{point.text_representation})'), #{distance})").
39
+ order("ST_Distance(border, ST_GeomFromEWKT('SRID=4326;POINT(#{point.text_representation})'))")
40
+ }
41
+
42
+ # Search Scope
43
+ scope :full_name_search,
44
+ lambda {|str|
45
+ like_str = "%#{str}%"
46
+ id = str
47
+ where("((name || ', ' || city || ', ' || state) ILIKE ?)", like_str)
48
+ }
49
+
50
+ if defined?(MetaSearch)
51
+ search_methods :full_name_search
52
+ end
53
+
54
+ # == Callbacks ============================================================
55
+
56
+ before_validation :set_send_mode, :on => :create
57
+ after_create :initialize_issue_numbers
58
+ after_save :check_send_mode_change, :on => :update
59
+
60
+ # == Class Methods ========================================================
61
+
62
+ # Return a hash with the form {area_id => posts.count}
63
+ def self.newposts_count
64
+ connection.select_rows("
65
+ SELECT area_id, COUNT(posts.id)
66
+ FROM posts LEFT JOIN areas ON (posts.area_id = areas.id)
67
+ WHERE issue_id IS NULL
68
+ GROUP BY area_id
69
+ ").inject({}){|h, count| h[count[0].to_i] = count[1].to_i; h}
70
+ end
71
+
72
+ # == Instance Methods =====================================================
73
+
74
+ def coordinates=(points)
75
+ coords = points.collect{|k, v| [v[0].to_f, v[1].to_f]}
76
+ self.border = Polygon.from_coordinates([coords + [coords.first]])
77
+ end
78
+
79
+
80
+ # Returns an array with coordinates
81
+ # removing the last (repeated) point
82
+ def to_a
83
+ if self.border.present?
84
+ {
85
+ :id => self.id,
86
+ :name => self.name,
87
+ :points => self.border.first.points[0..-2].collect{|p| [p.x, p.y]}
88
+ }
89
+ end
90
+ end
91
+
92
+ # Returns a Point object
93
+ def center
94
+ if self.border.present?
95
+ border.envelope.center
96
+ end
97
+ end
98
+
99
+ def bounds
100
+ return [] unless self.border.present?
101
+ [
102
+ [self.border.envelope.lower_corner.x, self.border.envelope.lower_corner.y],
103
+ [self.border.envelope.upper_corner.x, self.border.envelope.upper_corner.y]
104
+ ]
105
+ end
106
+
107
+ def border_coordinates
108
+ @border_coordinates ||= border.rings.first.points.collect{|point| "new google.maps.LatLng(#{point.x}, #{point.y})"}.join(',')
109
+ end
110
+
111
+ def location
112
+ [self.city, self.state].compact.join(', ')
113
+ end
114
+
115
+ def current_issue
116
+ @current_issue ||= self.issues.where(:sent_at => nil).first
117
+ end
118
+
119
+ def send_mode?(mode)
120
+ self.send_mode == mode.to_s
121
+ end
122
+
123
+ def record_activity_for!(field)
124
+ field = field.to_s
125
+ if AreaActivity::TRACKABLE.include?(field)
126
+ activity = self.activities.find_or_create_by_day(Time.now.utc.to_date)
127
+ activity.increment!([field, 'count'].join('_'), 1)
128
+ activity.reload
129
+ else
130
+ raise "Cannot find field in the list of trackable fields. Currently tracking: #{AreaActivity::TRACKABLE.join(', ')}"
131
+ end
132
+ end
133
+
134
+ def self.for_select
135
+ Area.order('state, city, name').collect{|a| [[a.name, a.city, a.state].join(', '), a.id]}
136
+ end
137
+
138
+ def email
139
+ if defined?(OPEN_PORCH_POP3)
140
+ "#{self.slug}@#{OPEN_PORCH_POP3['mailto']}"
141
+ end
142
+ end
143
+
144
+ protected
145
+ def initialize_issue_numbers
146
+ self.create_issue_number(:sequence_number => 0)
147
+ end
148
+
149
+ def set_send_mode
150
+ self.send_mode ||= 'immediate'
151
+ end
152
+
153
+ def check_send_mode_change
154
+ # Detach any posts from the current issue
155
+ if self.send_mode_changed? && self.send_mode?(:immediate)
156
+ if self.current_issue
157
+ self.current_issue.posts.each do |post|
158
+ post.update_attribute(:issue_id, nil)
159
+ end
160
+ # Delete the current issue
161
+ self.current_issue.reload
162
+ self.current_issue.destroy
163
+ @current_issue = nil
164
+ end
165
+ # Send any post left over
166
+ self.posts.in_issue(nil).each do |post|
167
+ post.send_immediatelly!
168
+ end
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,24 @@
1
+ class AreaActivity < ActiveRecord::Base
2
+
3
+ TRACKABLE = %w(quitters new_users new_posts issues_published).freeze
4
+
5
+ # == Relationships ========================================================
6
+
7
+ belongs_to :area
8
+
9
+ # == Validations ==========================================================
10
+
11
+ validates :day,
12
+ :presence => true
13
+ validates :area_id,
14
+ :presence => true,
15
+ :uniqueness => {:scope => :day}
16
+
17
+ # == Scopes ===============================================================
18
+
19
+ default_scope order('day ASC')
20
+
21
+ scope :grouped_by_day,
22
+ select([['day'] + TRACKABLE.collect{|a| "sum(#{a}_count) as sum_#{a}_count"}].join(', ')).group(:day)
23
+
24
+ end