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 %> + + + + + +<% end %> +
<%= 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 %>
+ +
+ +<%= 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