open_porch 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +41 -0
- data/Gemfile.lock +130 -0
- data/README.md +170 -0
- data/Rakefile +19 -0
- data/VERSION +1 -0
- data/app/controllers/admin/areas/base_controller.rb +11 -0
- data/app/controllers/admin/areas/issues_controller.rb +67 -0
- data/app/controllers/admin/areas/memberships_controller.rb +7 -0
- data/app/controllers/admin/areas/posts_controller.rb +44 -0
- data/app/controllers/admin/areas_controller.rb +89 -0
- data/app/controllers/admin/base_controller.rb +16 -0
- data/app/controllers/admin/user_activity_controller.rb +29 -0
- data/app/controllers/admin/users_controller.rb +57 -0
- data/app/controllers/application_controller.rb +15 -0
- data/app/controllers/areas/base_controller.rb +25 -0
- data/app/controllers/areas/issues_controller.rb +35 -0
- data/app/controllers/areas/posts_controller.rb +28 -0
- data/app/controllers/areas_controller.rb +30 -0
- data/app/controllers/passwords_controller.rb +42 -0
- data/app/controllers/registrations_controller.rb +42 -0
- data/app/controllers/sessions_controller.rb +33 -0
- data/app/controllers/users_controller.rb +77 -0
- data/app/helpers/open_porch_helper.rb +24 -0
- data/app/mailers/user_mailer.rb +35 -0
- data/app/models/address.rb +57 -0
- data/app/models/area.rb +171 -0
- data/app/models/area_activity.rb +24 -0
- data/app/models/email_message.rb +104 -0
- data/app/models/issue.rb +66 -0
- data/app/models/issue_number.rb +22 -0
- data/app/models/membership.rb +27 -0
- data/app/models/post.rb +64 -0
- data/app/models/session_user.rb +69 -0
- data/app/models/user.rb +140 -0
- data/app/models/user_activity.rb +31 -0
- data/app/models/user_authority_check.rb +14 -0
- data/app/views/admin/areas/_form.html.haml +8 -0
- data/app/views/admin/areas/_nav.html.haml +12 -0
- data/app/views/admin/areas/edit.html.haml +22 -0
- data/app/views/admin/areas/edit_borders.html.haml +44 -0
- data/app/views/admin/areas/index.html.haml +61 -0
- data/app/views/admin/areas/issues/_post.html.haml +15 -0
- data/app/views/admin/areas/issues/add_posts.js.rjs +3 -0
- data/app/views/admin/areas/issues/edit.html.haml +37 -0
- data/app/views/admin/areas/issues/index.html.haml +31 -0
- data/app/views/admin/areas/issues/remove_posts.js.rjs +3 -0
- data/app/views/admin/areas/issues/show.html.haml +13 -0
- data/app/views/admin/areas/memberships/index.html.haml +17 -0
- data/app/views/admin/areas/new.html.haml +6 -0
- data/app/views/admin/areas/new.js.haml +4 -0
- data/app/views/admin/areas/posts/_edit.html.haml +6 -0
- data/app/views/admin/areas/posts/_post_status.html.haml +1 -0
- data/app/views/admin/areas/posts/destroy.js.rjs +1 -0
- data/app/views/admin/areas/posts/edit.js.rjs +1 -0
- data/app/views/admin/areas/posts/show.js.rjs +1 -0
- data/app/views/admin/areas/posts/toggle_reviewed_by.js.rjs +1 -0
- data/app/views/admin/areas/posts/update.js.rjs +5 -0
- data/app/views/admin/areas/show.html.haml +5 -0
- data/app/views/admin/user_activity/show.html.haml +5 -0
- data/app/views/admin/users/_form.html.haml +21 -0
- data/app/views/admin/users/edit.html.haml +5 -0
- data/app/views/admin/users/index.html.haml +31 -0
- data/app/views/admin/users/new.html.haml +5 -0
- data/app/views/areas/issues/index.html.haml +31 -0
- data/app/views/areas/issues/show.html.haml +22 -0
- data/app/views/areas/posts/_post.html.haml +8 -0
- data/app/views/areas/posts/_posts_search_form.html.haml +8 -0
- data/app/views/areas/posts/index.html.haml +27 -0
- data/app/views/areas/posts/new.html.haml +10 -0
- data/app/views/areas/show.html.haml +47 -0
- data/app/views/layouts/_account_nav.html.haml +18 -0
- data/app/views/layouts/_flash_message.html.haml +4 -0
- data/app/views/layouts/_footer.html.haml +9 -0
- data/app/views/layouts/_head.html.haml +16 -0
- data/app/views/layouts/admin/_nav.html.haml +13 -0
- data/app/views/layouts/admin.html.haml +15 -0
- data/app/views/layouts/application.html.haml +14 -0
- data/app/views/layouts/area_editor.html.haml +11 -0
- data/app/views/passwords/edit.html.haml +9 -0
- data/app/views/passwords/new.html.haml +13 -0
- data/app/views/registrations/_address_form.html.haml +7 -0
- data/app/views/registrations/create.html.haml +49 -0
- data/app/views/registrations/index.html.haml +30 -0
- data/app/views/registrations/new.html.haml +17 -0
- data/app/views/sessions/new.html.haml +18 -0
- data/app/views/stylesheets/common.sass +239 -0
- data/app/views/stylesheets/content.sass +193 -0
- data/app/views/stylesheets/reset.sass +46 -0
- data/app/views/stylesheets/structure.sass +11 -0
- data/app/views/stylesheets/typography.sass +57 -0
- data/app/views/user_mailer/email_verification.html.erb +5 -0
- data/app/views/user_mailer/email_verification.text.erb +7 -0
- data/app/views/user_mailer/new_issue.html.erb +32 -0
- data/app/views/user_mailer/new_issue.text.erb +26 -0
- data/app/views/user_mailer/password_reset.html.erb +7 -0
- data/app/views/user_mailer/password_reset.text.erb +6 -0
- data/app/views/users/_form.html.haml +5 -0
- data/app/views/users/edit.html.haml +11 -0
- data/app/views/users/new.html.haml +41 -0
- data/app/views/users/show.html.haml +30 -0
- data/bin/open_porch_engine +135 -0
- data/config/application.rb +43 -0
- data/config/boot.rb +13 -0
- data/config/database_example.yml +59 -0
- data/config/environment.rb +5 -0
- data/config/environments/development.rb +26 -0
- data/config/environments/production.rb +49 -0
- data/config/environments/test.rb +35 -0
- data/config/initializers/backtrace_silencers.rb +7 -0
- data/config/initializers/email_regex.rb +38 -0
- data/config/initializers/geokit_config.rb +61 -0
- data/config/initializers/inflections.rb +20 -0
- data/config/initializers/meta_search.rb +7 -0
- data/config/initializers/mime_types.rb +5 -0
- data/config/initializers/open_porch.rb +41 -0
- data/config/initializers/sass.rb +1 -0
- data/config/initializers/secret_token.rb +7 -0
- data/config/initializers/session_store.rb +8 -0
- data/config/initializers/states_provinces.rb +2 -0
- data/config/initializers/will_paginate.rb +2 -0
- data/config/locales/en.yml +5 -0
- data/config/open_porch_example.yml +23 -0
- data/config/routes.rb +54 -0
- data/config/schedule.rb +9 -0
- data/config.ru +4 -0
- data/db/migrate/01_create_areas.rb +21 -0
- data/db/migrate/02_create_users.rb +28 -0
- data/db/migrate/03_create_memberships.rb +14 -0
- data/db/migrate/04_create_posts.rb +20 -0
- data/db/migrate/05_create_issue_numbers.rb +13 -0
- data/db/migrate/06_create_issues.rb +21 -0
- data/db/migrate/20110204173301_add_published_to_areas.rb +10 -0
- data/db/migrate/20110204194840_create_user_activities.rb +13 -0
- data/db/migrate/20110208163604_add_zip_to_areas.rb +11 -0
- data/db/migrate/20110209175723_add_counters_to_areas.rb +11 -0
- data/db/migrate/20110209182244_remove_subject_from_issues.rb +9 -0
- data/db/migrate/20110209190146_add_reviewer_info_to_posts.rb +9 -0
- data/db/migrate/20110215173144_add_email_validation_key_to_users.rb +13 -0
- data/db/migrate/20110215182716_remove_published_from_areas.rb +10 -0
- data/db/migrate/20110215211012_create_area_activities.rb +19 -0
- data/db/migrate/20110215213802_create_email_messages.rb +19 -0
- data/db/migrate/20110217165018_change_send_mode_to_string.rb +17 -0
- data/db/migrate/20110223160609_denormalize_user_info_in_posts.rb +19 -0
- data/db/seeds.rb +7 -0
- data/doc/README_FOR_APP +2 -0
- data/lib/generators/open_porch_generator.rb +37 -0
- data/lib/open_porch/engine.rb +20 -0
- data/lib/open_porch.rb +3 -0
- data/lib/tasks/.gitkeep +0 -0
- data/lib/tasks/open_porch.rake +10 -0
- data/lib/tasks/postageapp_tasks.rake +78 -0
- data/open_porch.gemspec +335 -0
- data/public/404.html +26 -0
- data/public/422.html +26 -0
- data/public/500.html +26 -0
- data/public/favicon.ico +0 -0
- data/public/images/icons/ajax-loader.gif +0 -0
- data/public/images/icons/calendar.png +0 -0
- data/public/images/icons/post_new.png +0 -0
- data/public/images/icons/post_reviewed.png +0 -0
- data/public/javascripts/application.js +3 -0
- data/public/javascripts/google_maps.js +153 -0
- data/public/javascripts/highcharts.js +162 -0
- data/public/javascripts/highcharts_init.js +30 -0
- data/public/javascripts/issue_edit.js +57 -0
- data/public/javascripts/jquery-ui.min.js +191 -0
- data/public/javascripts/jquery.js +154 -0
- data/public/javascripts/rails.js +137 -0
- data/public/javascripts/region_editor.js +616 -0
- data/public/javascripts/user_activity.js +29 -0
- data/public/robots.txt +5 -0
- data/public/stylesheets/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
- data/public/stylesheets/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
- data/public/stylesheets/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
- data/public/stylesheets/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/public/stylesheets/images/ui-bg_glass_75_dadada_1x400.png +0 -0
- data/public/stylesheets/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
- data/public/stylesheets/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
- data/public/stylesheets/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
- data/public/stylesheets/images/ui-icons_222222_256x240.png +0 -0
- data/public/stylesheets/images/ui-icons_2e83ff_256x240.png +0 -0
- data/public/stylesheets/images/ui-icons_454545_256x240.png +0 -0
- data/public/stylesheets/images/ui-icons_888888_256x240.png +0 -0
- data/public/stylesheets/images/ui-icons_cd0a0a_256x240.png +0 -0
- data/public/stylesheets/jquery-ui.css +362 -0
- data/script/rails +6 -0
- data/test/dummy/area.rb +5 -0
- data/test/dummy/area_activity.rb +7 -0
- data/test/dummy/issue.rb +4 -0
- data/test/dummy/membership.rb +4 -0
- data/test/dummy/post.rb +12 -0
- data/test/dummy/user.rb +29 -0
- data/test/dummy/user_activity.rb +11 -0
- data/test/functional/admin/areas/issues_controller_test.rb +48 -0
- data/test/functional/admin/areas/memberships_controller_test.rb +45 -0
- data/test/functional/admin/areas/posts_controller_test.rb +54 -0
- data/test/functional/admin/areas_controller_test.rb +134 -0
- data/test/functional/admin/base_controller_test.rb +8 -0
- data/test/functional/admin/user_activity_controller_test.rb +28 -0
- data/test/functional/admin/users_controller_test.rb +144 -0
- data/test/functional/areas/issues_controller_test.rb +33 -0
- data/test/functional/areas/posts_controller_test.rb +50 -0
- data/test/functional/areas_controller_test.rb +12 -0
- data/test/functional/passwords_controller_test.rb +64 -0
- data/test/functional/registrations_controller_test.rb +64 -0
- data/test/functional/sessions_controller_test.rb +120 -0
- data/test/functional/users_controller_test.rb +144 -0
- data/test/performance/browsing_test.rb +9 -0
- data/test/test_helper.rb +92 -0
- data/test/unit/address_test.rb +25 -0
- data/test/unit/area_activity_test.rb +57 -0
- data/test/unit/area_test.rb +92 -0
- data/test/unit/email_message_test.rb +8 -0
- data/test/unit/issue_number_test.rb +25 -0
- data/test/unit/issue_test.rb +154 -0
- data/test/unit/membership_test.rb +20 -0
- data/test/unit/post_test.rb +69 -0
- data/test/unit/session_user_test.rb +65 -0
- data/test/unit/user_activity_test.rb +31 -0
- data/test/unit/user_mailer_test.rb +20 -0
- data/test/unit/user_test.rb +61 -0
- data/vendor/plugins/.gitkeep +0 -0
- 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]}®ion=#{@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
|
data/app/models/area.rb
ADDED
@@ -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
|