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