From 11e3356f55deddf52f9590699116f34e9809a4e8 Mon Sep 17 00:00:00 2001
From: andrew
Date: Sun, 3 Feb 2008 20:16:53 +0000
Subject: [PATCH] adding error emails and invitation mailing
git-svn-id: http://svn.barleysodas.com/barleysodas/trunk@126 0f7b21a7-9e3a-4941-bbeb-ce5c7c368fa7
---
app/controllers/application.rb | 17 +++++
app/controllers/invitations_controller.rb | 63 +++++++++++++++++++
app/controllers/peoples_controller.rb | 4 +-
app/helpers/application_helper.rb | 5 +-
app/helpers/invitations_helper.rb | 2 +
app/models/error_mailer.rb | 14 +++++
app/models/invitation.rb | 17 +++++
app/models/invitation_mailer.rb | 10 +++
app/models/people.rb | 2 +
app/views/error_mailer/snapshot.rhtml | 38 +++++++++++
app/views/invitation_mailer/invitation.rhtml | 13 ++++
app/views/invitations/_invitations.rhtml | 2 +
app/views/invitations/edit.rhtml | 12 ++++
app/views/invitations/index.rhtml | 18 ++++++
app/views/invitations/new.rhtml | 11 ++++
app/views/invitations/show.rhtml | 3 +
app/views/layouts/application.rhtml | 2 +
app/views/peoples/_people_form.rhtml | 4 +-
app/views/peoples/new.rhtml | 3 +-
app/views/peoples/show.rhtml | 12 ++++
config/environment.rb | 5 +-
config/routes.rb | 3 +-
db/migrate/007_create_roles.rb | 2 +
db/migrate/015_create_invitations.rb | 14 +++++
generate_permissions | 2 +-
test/fixtures/invitations.yml | 5 ++
.../functional/invitations_controller_test.rb | 57 +++++++++++++++++
test/unit/invitation_test.rb | 10 +++
28 files changed, 340 insertions(+), 10 deletions(-)
create mode 100644 app/controllers/invitations_controller.rb
create mode 100644 app/helpers/invitations_helper.rb
create mode 100644 app/models/error_mailer.rb
create mode 100644 app/models/invitation.rb
create mode 100644 app/models/invitation_mailer.rb
create mode 100644 app/views/error_mailer/snapshot.rhtml
create mode 100644 app/views/invitation_mailer/invitation.rhtml
create mode 100644 app/views/invitations/_invitations.rhtml
create mode 100644 app/views/invitations/edit.rhtml
create mode 100644 app/views/invitations/index.rhtml
create mode 100644 app/views/invitations/new.rhtml
create mode 100644 app/views/invitations/show.rhtml
create mode 100644 db/migrate/015_create_invitations.rb
create mode 100644 test/fixtures/invitations.yml
create mode 100644 test/functional/invitations_controller_test.rb
create mode 100644 test/unit/invitation_test.rb
diff --git a/app/controllers/application.rb b/app/controllers/application.rb
index 0ef97e2..a8488c0 100644
--- a/app/controllers/application.rb
+++ b/app/controllers/application.rb
@@ -127,6 +127,23 @@ class ApplicationController < ActionController::Base
true
end
+ ##
+ # Log the error and mail it out if the request is not a local request.
+ # Usually only used in production mode.
+ #
+ def log_error(exception)
+ super(exception)
+ begin
+ unless local_request? or RAILS_ENV == 'development' or
+ exception.class == ActiveRecord::RecordNotFound
+ ErrorMailer.deliver_snapshot(exception, clean_backtrace(exception),
+ session.instance_variable_get("@data"), params, request.env)
+ end
+ rescue => e
+ logger.info e.to_s
+ end
+ end
+
private
##
diff --git a/app/controllers/invitations_controller.rb b/app/controllers/invitations_controller.rb
new file mode 100644
index 0000000..b787f7e
--- /dev/null
+++ b/app/controllers/invitations_controller.rb
@@ -0,0 +1,63 @@
+class InvitationsController < ApplicationController
+ append_before_filter :ensure_xhr, :only => [ :create, :send ]
+
+ # GET /invitations
+ # GET /invitations.xml
+ def index
+ @invitations = Invitation.find(:all, :include => [ 'people' ],
+ :order => 'peoples.title ASC')
+ respond_to do |format|
+ format.html # index.rhtml
+ format.xml { render :xml => @invitations.to_xml }
+ end
+ end
+
+ # POST /invitations
+ def create
+ @invitation = Invitation.new
+ @invitation.people_id = params[:people_id].to_i if params[:people_id]
+ @people = @invitation.people
+ if @invitation.save
+ render :partial => 'invitations/invitations'
+ else
+ render :inline => "<%= error_messages_for('invitation') -%>",
+ :status => 500
+ end
+ end
+
+ # DELETE /invitations/1
+ # DELETE /invitations/1.xml
+ def destroy
+ @invitation = Invitation.find(params[:id])
+ @invitation.destroy
+ respond_to do |format|
+ format.html { redirect_to invitations_url }
+ format.xml { head :ok }
+ end
+ end
+
+ def send_invitation
+ @invitation = Invitation.find(:first, :include => [ 'people' ],
+ :conditions => [ "people_id = ?", session[:people_id] ])
+ @invitation ||= Invitation.new :people_id => session[:people_id]
+ @people = @invitation.people
+ recipient = params[:email].to_s
+ begin
+ if @invitation.new_record?
+ @invitation.errors.add(:you, "have no invitations")
+ end
+ if recipient.to_s.empty?
+ @invitation.errors.add(:recipient, "is missing")
+ end
+ raise "Misconfigured invitation!" unless @invitation.errors.empty?
+ InvitationMailer.deliver_invitation(@invitation, recipient)
+ rescue
+ logger.info("Failed to deliver invitation: #{$!}")
+ render :inline => "<%= error_messages_for('invitation') -%>",
+ :status => 500
+ return
+ end
+ @invitation.toggle! :sent
+ render :partial => 'invitations/invitations'
+ end
+end
diff --git a/app/controllers/peoples_controller.rb b/app/controllers/peoples_controller.rb
index c955f1c..9c30ad4 100644
--- a/app/controllers/peoples_controller.rb
+++ b/app/controllers/peoples_controller.rb
@@ -50,8 +50,10 @@ class PeoplesController < ApplicationController
set_people_role
@page = Page.new(params[:page])
@people.page = @page
+ invitation = Invitation.find_by_code(params[:code])
respond_to do |format|
- if @people.save
+ if invitation and @people.save
+ invitation.destroy
flash[:notice] = 'People was successfully created.'
format.html { redirect_to people_url(@people.page.title_for_url) }
format.xml { head :created,
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 3566a1b..ae147e4 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -116,9 +116,8 @@ module ApplicationHelper
# Renders everything you need to display the TagImage browser.
#
def tagged_image_browser(obj)
- javascript_include_tag('control.modal.js') +
- render(:partial => 'shared/tagged_image_browser',
- :locals => { :obj => obj })
+ render(:partial => 'shared/tagged_image_browser',
+ :locals => { :obj => obj })
end
##
diff --git a/app/helpers/invitations_helper.rb b/app/helpers/invitations_helper.rb
new file mode 100644
index 0000000..1483b9e
--- /dev/null
+++ b/app/helpers/invitations_helper.rb
@@ -0,0 +1,2 @@
+module InvitationsHelper
+end
diff --git a/app/models/error_mailer.rb b/app/models/error_mailer.rb
new file mode 100644
index 0000000..e261f03
--- /dev/null
+++ b/app/models/error_mailer.rb
@@ -0,0 +1,14 @@
+class ErrorMailer < ActionMailer::Base
+ def snapshot(exception, trace, session, params, env)
+ @headers["Content-Type"] = "text/plain"
+ @recipients = 'penguincoder@gmail.com'
+ @from = 'BarleySodas '
+ @subject = "[BarleySodas Exception: #{env['REQUEST_URI']}]"
+ @sent_on = Time.now
+ @body["exception"] = exception
+ @body["trace"] = trace
+ @body["session"] = session
+ @body["params"] = params
+ @body["env"] = env
+ end
+end
\ No newline at end of file
diff --git a/app/models/invitation.rb b/app/models/invitation.rb
new file mode 100644
index 0000000..2b607a2
--- /dev/null
+++ b/app/models/invitation.rb
@@ -0,0 +1,17 @@
+class Invitation < ActiveRecord::Base
+ belongs_to :people
+ validates_presence_of :people_id
+ before_create :set_invitation_code
+
+ protected
+
+ def set_invitation_code
+ chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
+ check = nil
+ begin
+ check = ''
+ 1.upto(32) { |k| check << chars[rand(chars.size - 1)] }
+ end while Invitation.find_by_code(check)
+ self.code = check
+ end
+end
diff --git a/app/models/invitation_mailer.rb b/app/models/invitation_mailer.rb
new file mode 100644
index 0000000..7f658b4
--- /dev/null
+++ b/app/models/invitation_mailer.rb
@@ -0,0 +1,10 @@
+class InvitationMailer < ActionMailer::Base
+ def invitation(invitation, recipient)
+ @headers["Content-Type"] = "text/plain"
+ @recipients = recipient
+ @from = 'BarleySodas '
+ @subject = "You've been invited to BarleySodas!"
+ @body["invitation"] = invitation
+ @body["people_title"] = invitation.people.title
+ end
+end
\ No newline at end of file
diff --git a/app/models/people.rb b/app/models/people.rb
index 18b8541..e87e940 100644
--- a/app/models/people.rb
+++ b/app/models/people.rb
@@ -14,6 +14,8 @@ class People < ActiveRecord::Base
has_many :actual_friends, :through => :friends, :source => :destination
has_many :experiences, :dependent => :destroy
has_many :beers, :through => :experiences
+ has_many :invitations, :dependent => :destroy,
+ :conditions => [ 'sent IS NULL or sent = ?', false ]
make_authenticatable
validates_length_of :password, :minimum => 8, :if => :password_required?,
diff --git a/app/views/error_mailer/snapshot.rhtml b/app/views/error_mailer/snapshot.rhtml
new file mode 100644
index 0000000..9ab0de8
--- /dev/null
+++ b/app/views/error_mailer/snapshot.rhtml
@@ -0,0 +1,38 @@
+Error report from <%= Time.now %>
+
+* Message: <%= @exception.message %>
+* Location: <%= @env['REQUEST_URI'] %>
+* Action: <%= @params.delete('action') %>
+* Controller: <%= @params.delete('controller') %>
+* Query: <%= @env['QUERY_STRING'] %>
+* Method: <%= @env['REQUEST_METHOD'] %>
+* SSL: <%= @env['SERVER_PORT'].to_i == 443 ? "true" : "false" %>
+* Agent: <%= @env['HTTP_USER_AGENT'] %>
+
+Backtrace
+<%= @trace.to_a.join("\n") -%>
+
+<% if @params -%>
+Params
+<% for key, val in @params -%>
+* <%= key %>
+ <%= val.to_yaml.to_a.join("\n ") %>
+<% end -%>
+<% end -%>
+
+<% if @session -%>
+Session
+<% for key, val in @session -%>
+<% next if key.to_s == 'permissions' -%>
+* <%= key %>
+ <%= val.to_yaml.to_a.join("\n ") %>
+<% end -%>
+<% end -%>
+
+<% if @env -%>
+Environment
+<% for key, val in @env -%>
+* <%= key %>
+ <%= val.to_yaml.to_a.join("\n ") %>
+<% end -%>
+<% end -%>
\ No newline at end of file
diff --git a/app/views/invitation_mailer/invitation.rhtml b/app/views/invitation_mailer/invitation.rhtml
new file mode 100644
index 0000000..8572c92
--- /dev/null
+++ b/app/views/invitation_mailer/invitation.rhtml
@@ -0,0 +1,13 @@
+Hello!
+
+You have been nice enough that someone has given you an invitation to join
+the best beer site. We have a wiki, style guidelines, beers, breweries,
+and sweet image tagging that lets you build a network of things and places
+experienced in your journey through inebriation.
+
+Sign up today at:
+<%= new_people_url(:code => @invitation.code, :host => "barleysodas.com") %>
+
+Thanks,
+
+The BarleySodas Society for the preservation of ancient and modern techniques regarding both the intellectual and philisophical management of varied sizes animals vice the expected volume of celestial entities older than, but not equal to, sixteen billion years in age.
diff --git a/app/views/invitations/_invitations.rhtml b/app/views/invitations/_invitations.rhtml
new file mode 100644
index 0000000..6a5c15b
--- /dev/null
+++ b/app/views/invitations/_invitations.rhtml
@@ -0,0 +1,2 @@
+You have <%= pluralize(@people.invitations.size, 'Invitations') rescue '0 Invitations' -%>.
+<% if @people and !@people.invitations.empty? -%>
<%= link_to_function 'Send Invitation', "lightboxes['invitation_dialog'].open()" -%><% end -%>
\ No newline at end of file
diff --git a/app/views/invitations/edit.rhtml b/app/views/invitations/edit.rhtml
new file mode 100644
index 0000000..093b6a5
--- /dev/null
+++ b/app/views/invitations/edit.rhtml
@@ -0,0 +1,12 @@
+Editing invitation
+
+<%= error_messages_for :invitation %>
+
+<% form_for(:invitation, :url => invitation_path(@invitation), :html => { :method => :put }) do |f| %>
+
+ <%= submit_tag "Update" %>
+
+<% end %>
+
+<%= link_to 'Show', invitation_path(@invitation) %> |
+<%= link_to 'Back', invitations_path %>
\ No newline at end of file
diff --git a/app/views/invitations/index.rhtml b/app/views/invitations/index.rhtml
new file mode 100644
index 0000000..ddb7417
--- /dev/null
+++ b/app/views/invitations/index.rhtml
@@ -0,0 +1,18 @@
+Listing invitations
+
+
+
+
+
+<% for invitation in @invitations %>
+
+ <%= link_to 'Show', invitation_path(invitation) %> |
+ <%= link_to 'Edit', edit_invitation_path(invitation) %> |
+ <%= link_to 'Destroy', invitation_path(invitation), :confirm => 'Are you sure?', :method => :delete %> |
+
+<% end %>
+
+
+
+
+<%= link_to 'New invitation', new_invitation_path %>
\ No newline at end of file
diff --git a/app/views/invitations/new.rhtml b/app/views/invitations/new.rhtml
new file mode 100644
index 0000000..6933a0a
--- /dev/null
+++ b/app/views/invitations/new.rhtml
@@ -0,0 +1,11 @@
+New invitation
+
+<%= error_messages_for :invitation %>
+
+<% form_for(:invitation, :url => invitations_path) do |f| %>
+
+ <%= submit_tag "Create" %>
+
+<% end %>
+
+<%= link_to 'Back', invitations_path %>
\ No newline at end of file
diff --git a/app/views/invitations/show.rhtml b/app/views/invitations/show.rhtml
new file mode 100644
index 0000000..0a94fa3
--- /dev/null
+++ b/app/views/invitations/show.rhtml
@@ -0,0 +1,3 @@
+
+<%= link_to 'Edit', edit_invitation_path(@invitation) %> |
+<%= link_to 'Back', invitations_path %>
\ No newline at end of file
diff --git a/app/views/layouts/application.rhtml b/app/views/layouts/application.rhtml
index 02d9eb4..03c4e79 100644
--- a/app/views/layouts/application.rhtml
+++ b/app/views/layouts/application.rhtml
@@ -5,6 +5,8 @@
<%= stylesheet_link_tag 'application', :media => 'all' %>
<%= javascript_include_tag :defaults %>
+ <%= javascript_include_tag 'control.modal.js' %>
+ <%= javascript_include_tag 'control.rating.js' %>
diff --git a/app/views/peoples/_people_form.rhtml b/app/views/peoples/_people_form.rhtml
index 4624da5..0bd4abc 100644
--- a/app/views/peoples/_people_form.rhtml
+++ b/app/views/peoples/_people_form.rhtml
@@ -2,9 +2,9 @@
<%= text_field 'people', 'title' %>
- <%= text_field 'people', 'password' %>
+ <%= password_field 'people', 'password' %>
- <%= text_field 'people', 'password_confirmation' %>
+ <%= password_field 'people', 'password_confirmation' %>
<%= render :partial => 'pages/page_form' %>
diff --git a/app/views/peoples/new.rhtml b/app/views/peoples/new.rhtml
index edb8f69..a70f2e7 100644
--- a/app/views/peoples/new.rhtml
+++ b/app/views/peoples/new.rhtml
@@ -1,6 +1,7 @@
<%= error_messages_for :people %>
-<% form_for(:people, :url => peoples_path) do |f| %>
+<% form_for(:people, :url => peoples_path, :code => params[:code]) do |f| %>
+ Invitation Code: <%= params[:code] -%><%= hidden_field_tag 'code', params[:code] -%>
<%= render :partial => 'people_form' %>
<%= submit_tag "Create" %>
diff --git a/app/views/peoples/show.rhtml b/app/views/peoples/show.rhtml
index 8cb3d95..fc05fec 100644
--- a/app/views/peoples/show.rhtml
+++ b/app/views/peoples/show.rhtml
@@ -2,6 +2,16 @@
<%= render :partial => 'pages/page' %>
+<% lightbox :title => 'Send An Invitation', :window_id => 'invitation_dialog' do -%>
+
+
+
Enter the recipient's email address: <%= text_field_tag 'email' -%>
+
+
+ <%= link_to_remote "Send", :url => { :controller => :invitations, :action => :send_invitation }, :with => "'email='+escape($('email').value)", :update => { :failure => 'invitation_errors', :success => 'person_invitations' }, :success => "Control.Modal.close()" -%>
+
+<% end -%>
+
<% content_for :sidebar do -%>
<%= new_people_link -%>
<%= edit_people_link(@people) %>
@@ -11,4 +21,6 @@
<% unless @people.id == session[:people_id] or @people.friend_of?(session[:people_id]) -%><%= add_friend_link(@people) -%><% end -%>
<% if @people.friend_of?(session[:people_id]) -%><%= remove_friend_link(@people) -%>
<% end -%>
<%= link_to "#{pluralize(@people.beers.size, 'Beer')} Experience", experience_path(:id => @people.page.title_for_url) -%>
+ <%= render :partial => 'invitations/invitations' -%>
+ <% if has_permission_for_action?(:create, :invitations) -%><%= link_to_remote "#{image_tag('list-add.png')} Invitation", :update => 'person_invitations', :url => invitations_path, :with => "'people_id=#{@people.id}'", :success => "new Effect.Highlight('person_invitations', { duration: 2.0 })" -%>
<% end -%>
<% end -%>
\ No newline at end of file
diff --git a/config/environment.rb b/config/environment.rb
index 4aae99d..e938c43 100644
--- a/config/environment.rb
+++ b/config/environment.rb
@@ -2,9 +2,12 @@ RAILS_GEM_VERSION = '1.2.3' unless defined? RAILS_GEM_VERSION
require File.join(File.dirname(__FILE__), 'boot')
Rails::Initializer.run do |config|
- config.frameworks -= [ :action_web_service, :action_mailer ]
+ config.frameworks -= [ :action_web_service ]
config.action_controller.session_store = :active_record_store
config.active_record.default_timezone = :utc
+ config.action_mailer.delivery_method = :sendmail
+ config.action_mailer.perform_deliveries = true
+ config.action_mailer.raise_delivery_errors = true
end
require 'redcloth'
diff --git a/config/routes.rb b/config/routes.rb
index a27f495..98fce91 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,6 +1,7 @@
ActionController::Routing::Routes.draw do |map|
map.resources :beers, :breweries, :pages, :discussions, :peoples, :roles,
- :sessions, :styles, :galleries, :tag_images, :friends, :experiences
+ :sessions, :styles, :galleries, :tag_images, :friends, :experiences,
+ :invitations
map.connect ':controller/:action/:id.:format'
map.connect ':controller/:action/:id'
map.connect '/', :controller => 'pages', :action => 'default_action'
diff --git a/db/migrate/007_create_roles.rb b/db/migrate/007_create_roles.rb
index e711417..93c750a 100644
--- a/db/migrate/007_create_roles.rb
+++ b/db/migrate/007_create_roles.rb
@@ -10,6 +10,8 @@ class CreateRoles < ActiveRecord::Migration
br = Role.create :code => 'base', :name => 'Base Role'
ar = Role.create :code => 'admin', :name => 'Administrative Role',
:parent_id => br.id
+ nr = Role.create :code => 'normal', :name => 'Normal User Role',
+ :parent_id => br.id
end
def self.down
diff --git a/db/migrate/015_create_invitations.rb b/db/migrate/015_create_invitations.rb
new file mode 100644
index 0000000..1594115
--- /dev/null
+++ b/db/migrate/015_create_invitations.rb
@@ -0,0 +1,14 @@
+class CreateInvitations < ActiveRecord::Migration
+ def self.up
+ create_table :invitations do |t|
+ t.column :code, :string, :limit => 32
+ t.column :people_id, :integer
+ t.column :sent, :boolean, :default => false
+ end
+ add_index :invitations, :people_id
+ end
+
+ def self.down
+ drop_table :invitations
+ end
+end
diff --git a/generate_permissions b/generate_permissions
index 8dd4b6a..6bc8159 100644
--- a/generate_permissions
+++ b/generate_permissions
@@ -6,7 +6,7 @@ base_actions = ApplicationController.action_methods
controllers = [ PagesController, DiscussionsController, StylesController,
PeoplesController, BeersController, BreweriesController, RolesController,
GalleriesController, TagImagesController, FriendsController,
- ExperiencesController ]
+ ExperiencesController, InvitationsController ]
controllers.each do |c|
actions = c.action_methods - base_actions
cname = c.controller_name
diff --git a/test/fixtures/invitations.yml b/test/fixtures/invitations.yml
new file mode 100644
index 0000000..b49c4eb
--- /dev/null
+++ b/test/fixtures/invitations.yml
@@ -0,0 +1,5 @@
+# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
+one:
+ id: 1
+two:
+ id: 2
diff --git a/test/functional/invitations_controller_test.rb b/test/functional/invitations_controller_test.rb
new file mode 100644
index 0000000..d9667b0
--- /dev/null
+++ b/test/functional/invitations_controller_test.rb
@@ -0,0 +1,57 @@
+require File.dirname(__FILE__) + '/../test_helper'
+require 'invitations_controller'
+
+# Re-raise errors caught by the controller.
+class InvitationsController; def rescue_action(e) raise e end; end
+
+class InvitationsControllerTest < Test::Unit::TestCase
+ fixtures :invitations
+
+ def setup
+ @controller = InvitationsController.new
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ end
+
+ def test_should_get_index
+ get :index
+ assert_response :success
+ assert assigns(:invitations)
+ end
+
+ def test_should_get_new
+ get :new
+ assert_response :success
+ end
+
+ def test_should_create_invitation
+ old_count = Invitation.count
+ post :create, :invitation => { }
+ assert_equal old_count+1, Invitation.count
+
+ assert_redirected_to invitation_path(assigns(:invitation))
+ end
+
+ def test_should_show_invitation
+ get :show, :id => 1
+ assert_response :success
+ end
+
+ def test_should_get_edit
+ get :edit, :id => 1
+ assert_response :success
+ end
+
+ def test_should_update_invitation
+ put :update, :id => 1, :invitation => { }
+ assert_redirected_to invitation_path(assigns(:invitation))
+ end
+
+ def test_should_destroy_invitation
+ old_count = Invitation.count
+ delete :destroy, :id => 1
+ assert_equal old_count-1, Invitation.count
+
+ assert_redirected_to invitations_path
+ end
+end
diff --git a/test/unit/invitation_test.rb b/test/unit/invitation_test.rb
new file mode 100644
index 0000000..e3025de
--- /dev/null
+++ b/test/unit/invitation_test.rb
@@ -0,0 +1,10 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+class InvitationTest < Test::Unit::TestCase
+ fixtures :invitations
+
+ # Replace this with your real tests.
+ def test_truth
+ assert true
+ end
+end