[Jifty-commit] r4478 - in apps/spensive: . bin doc etc lib/Spensive
lib/Spensive/Action lib/Spensive/Notification
lib/Spensive/Notification/EmailError
jifty-commit at lists.jifty.org
jifty-commit at lists.jifty.org
Tue Nov 20 12:10:06 EST 2007
Author: jesse
Date: Tue Nov 20 12:10:05 2007
New Revision: 4478
Added:
apps/spensive/bin/mailgate (contents, props changed)
apps/spensive/doc/TODO
apps/spensive/lib/Spensive/Action/EmailDispatch.pm (contents, props changed)
apps/spensive/lib/Spensive/Notification/
apps/spensive/lib/Spensive/Notification/EmailError/
apps/spensive/lib/Spensive/Notification/EmailError.pm (contents, props changed)
Modified:
apps/spensive/ (props changed)
apps/spensive/etc/config.yml
apps/spensive/lib/Spensive/Model/Attachment.pm
apps/spensive/lib/Spensive/Model/Expense.pm
apps/spensive/lib/Spensive/Model/User.pm
apps/spensive/lib/Spensive/View.pm
Log:
r67115 at pinglin (orig r7292): jesse | 2007-09-10 22:42:47 -0400
Merging!
Added: apps/spensive/bin/mailgate
==============================================================================
--- (empty file)
+++ apps/spensive/bin/mailgate Tue Nov 20 12:10:05 2007
@@ -0,0 +1,81 @@
+#!/usr/bin/env perl
+
+use strict;
+use warnings;
+use Getopt::Long;
+use LWP::UserAgent;
+use Pod::Usage;
+use Jifty::YAML;
+
+# Option parsing
+my %opts;
+GetOptions( \%opts, "address=s", "url=s", "timeout=i", "sender=s", "help" )
+ or pod2usage( -exitval => 1, -verbose => 1 );
+pod2usage( -exitval => 1, -verbose => 1 ) if $opts{help} or not $opts{url};
+
+# Set up POST parameters
+my %args = ( "J:A-dispatch" => "EmailDispatch" );
+
+# Where the email was sent to
+$args{'J:A:F-address-dispatch'} = $opts{address} || $ENV{RECIPIENT};
+die "$0: No --address argument, and EXTENSION env variable is empty!"
+ unless $args{'J:A:F-address-dispatch'};
+
+# Who sent it?
+$args{'J:A:F-envelope_sender-dispatch'} = $opts{sender} || $ENV{SENDER};
+die "$0: No --sender argument, and SENDER env variable is empty!"
+ unless $args{'J:A:F-envelope_sender-dispatch'};
+
+# Grab the message from STDIN
+$args{'J:A:F-email-dispatch'} = do { local (@ARGV, $/); <> };
+die "$0: No message passed on STDIN\n"
+ unless $args{'J:A:F-email-dispatch'} =~ /\S/;
+
+# Send 'er away!
+my $ua = LWP::UserAgent->new();
+$ua->timeout( $opts{timeout} ) if defined $opts{timeout};
+my $r = $ua->post("$opts{url}/__jifty/webservices/yaml", { %args });
+if ($r->is_success) {
+ my $data = Jifty::YAML::Load($r->content);
+ exit( $data->{dispatch}{failure} ? 100 : 0 );
+} else {
+ warn "Failure to post";
+ exit 1;
+}
+
+=head1 SYNOPSIS
+
+ mailgate --help # This text
+
+ mailgate --url http://hiveminder.com/
+
+=head1 OPTIONS
+
+=over 3
+
+=item --url
+
+The only required parameter; this specifies the complete base URL to
+the Hiveminder server that the mail is destined for.
+
+=item --address
+
+Specifies the published address that the mail is destined for. If
+this option is omitted, C<mailgate> looks at the C<EXTENSION>
+environment variable. The address determines what will be done with
+the email when it is received.
+
+=item --timeout
+
+Sets the timeout before the connection aborts. The default is three
+minutes, which should be more than sufficient.
+
+=item --sender
+
+Sets the envelope sender of the message. This will be pulled from the
+C<SENDER> environment variable if not specified.
+
+=back
+
+=cut
+
Added: apps/spensive/doc/TODO
==============================================================================
--- (empty file)
+++ apps/spensive/doc/TODO Tue Nov 20 12:10:05 2007
@@ -0,0 +1,37 @@
+V1
+
+
+* record expenses
+
+* stick expenses in reports
+
+* print reports
+
+* reports should have totals
+
+* attach images to expenses
+
+
+V2
+
+
+* submit attachments for new or existing expenses
+* (create expenses by mail)
+
+
+v3
+
+ * organizations
+ * organizations have an expense admin
+ * organizations have staff
+ * for each staff person, organization admins can designate an approver
+
+ * staff can "submit" an expense report.
+ * the optional "approver" for the expense approves it
+ * the expense admin can mark it as "paid" or reject it
+ * rejected reports bounce back to the sender for edit or resend
+
+VLATER
+
+
+* report expenses by im
Modified: apps/spensive/etc/config.yml
==============================================================================
--- apps/spensive/etc/config.yml (original)
+++ apps/spensive/etc/config.yml Tue Nov 20 12:10:05 2007
@@ -19,8 +19,8 @@
PoDir: share/po
LogLevel: DEBUG
Mailer: IO
- MailerArgs: []
-
+ MailerArgs:
+ - %log/mail.log%
Plugins:
- SkeletonApp: {}
- ErrorTemplates: {}
Added: apps/spensive/lib/Spensive/Action/EmailDispatch.pm
==============================================================================
--- (empty file)
+++ apps/spensive/lib/Spensive/Action/EmailDispatch.pm Tue Nov 20 12:10:05 2007
@@ -0,0 +1,171 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Spensive::Action::EmailDispatch
+
+=cut
+
+package Spensive::Action::EmailDispatch;
+use base qw/Jifty::Action/;
+
+use Email::MIME;
+
+use Spensive::Notification::EmailError;
+
+=head2 arguments
+
+address
+
+email
+
+envelope_sender '--sender' => 'gooduser at example.com'
+
+=cut
+
+sub arguments {
+ { address => {},
+ email => {},
+ envelope_sender => {},
+ };
+}
+
+=head2 validate_address
+
+Make sure the email address looks sane
+
+=cut
+
+sub validate_address {
+ my $self = shift;
+ my $email = shift;
+
+
+
+ my $pa = Spensive::Model::User->new(current_user => Jifty->app_class('CurrentUser')->superuser);
+ $pa->load_by_cols( published_address => $email );
+ if ( $pa->id ) {
+ return $self->validation_ok('address');
+ } else {
+ return $self->validation_error( address => "Address '$email' didn't match a published address" );
+ }
+
+}
+
+=head2 take_action
+
+Dispatches the C<email> based on the C<address> by looking up the
+appropriate L<Spensive::Model::User>.
+
+=cut
+
+sub take_action {
+ my $self = shift;
+
+ my $email = Email::MIME->new( $self->argument_value('email') );
+
+ my $body = $self->extract_body($email);
+
+ # Load the sending address, making it a user if need be
+ my ($from) = Email::Address->parse( $email->header("From") );
+ return unless $from;
+ my $sender = Jifty->app_class('Model', 'User')->new(
+ current_user => Jifty->app_class('CurrentUser')->superuser );
+ $sender->load_or_create( email => $from->address );
+
+ # Load that as a CurrentUser
+ my $current = Jifty->app_class('CurrentUser')->new( id => $sender->id );
+ Jifty->web->temporary_current_user($current);
+
+ # See if this is a published address
+ my $address = $self->argument_value('address');
+ my $pa = Jifty->app_class('Model', 'User')->new( current_user => Jifty->app_class('CurrentUser')->superuser );
+ $pa->load_by_cols( published_address => $address );
+
+ my $action;
+ if ( $pa->id ) {
+ # We got a user
+ # if a user set up auto_attributes we need to parse
+ # for braindump syntax. Put them first so that if
+ # auto_attributes contains [due] a [due] in Subject will clobber
+ my $summary = $email->header("Subject");
+
+ $action = Jifty->web->new_action(
+ class => 'CreateExpense',
+ moniker => "email_dispatch",
+ arguments => {
+ title => $summary,
+ description => $body,
+ incurred_by => $pa->id,
+ #email_content => $self->argument_value('email'),
+ },
+ # We clobber the current_user for group tasks so
+ # anyone can create group tasks, which is not usually
+ # the case
+ current_user => $pa
+ );
+ }
+ if($action) {
+ $action->run;
+ if($action->result->failure) {
+ $self->send_bounce( 'Spensive::Notification::EmailError', $action );
+ Jifty->log->warn("$action failed");
+ } else {
+ $self->result->message("Dispatched to an action $action");
+ }
+
+ }
+ Jifty->web->temporary_current_user(undef);
+}
+
+=head2 send_bounce
+
+=cut
+
+sub send_bounce {
+ my $self = shift;
+ my $class = shift;
+ my $action = shift;
+
+ # Generate a bounce with the error message
+ my $to = Jifty->app_class('Model', 'User')->new(current_user => Jifty->app_class('CurrentUser')->superuser);
+ $to->load_or_create(email => $self->argument_value('envelope_sender'));
+ $class->new(
+ to => $to,
+ address => $self->argument_value('address'),
+ result => $action->result,
+ email => $self->argument_value('email'),
+ )->send;
+}
+
+=head2 extract_body EMAIL
+
+Class method to extract the textual body from an Email::MIME email,
+and to attempt to guess the encoding appropriately
+
+=cut
+
+sub extract_body {
+ my $self = shift;
+ my $email = shift;
+
+ my $body = ( grep { ($_->content_type || '') =~ '^text/plain' } $email->parts )[0]
+ || $email;
+
+ my $content_type = $body->content_type
+ ? parse_content_type( $body->content_type )->{charset}
+ : '';
+ $body = $body->body;
+
+ Encode::_utf8_off($body);
+
+ $body = Jifty::I18N->promote_encoding($body, $content_type);
+
+ $body =~ s/\s*\n-- \n.*$//s;
+
+ return $body;
+}
+
+
+1;
Modified: apps/spensive/lib/Spensive/Model/Attachment.pm
==============================================================================
--- apps/spensive/lib/Spensive/Model/Attachment.pm (original)
+++ apps/spensive/lib/Spensive/Model/Attachment.pm Tue Nov 20 12:10:05 2007
@@ -5,6 +5,9 @@
use Jifty::DBI::Schema;
use Spensive::Record schema {
+ column expense => references Spensive::Model::Expense;
+ column image => is ImageColumn;
+ column caption => type is 'text';
};
Modified: apps/spensive/lib/Spensive/Model/Expense.pm
==============================================================================
--- apps/spensive/lib/Spensive/Model/Expense.pm (original)
+++ apps/spensive/lib/Spensive/Model/Expense.pm Tue Nov 20 12:10:05 2007
@@ -13,8 +13,8 @@
column tags => type is 'text';
column local_cost => type is 'float';
column cost => type is 'float';
- column payment_currency => type is 'text', valid_values are qw(USD CAD UKP EUR TWD NZD HKD);
- column payment_method => type is 'text', valid_values are qw(personal_credit corporate_credit cash);
+ column payment_currency => type is 'text', valid_values are qw(USD CAD UKP EUR TWD NZD HKD), default is 'USD';
+ #column payment_method => type is 'text', valid_values are qw(personal_credit corporate_credit cash);
column expense_report=> references Spensive::Model::ExpenseReport;
};
Modified: apps/spensive/lib/Spensive/Model/User.pm
==============================================================================
--- apps/spensive/lib/Spensive/Model/User.pm (original)
+++ apps/spensive/lib/Spensive/Model/User.pm Tue Nov 20 12:10:05 2007
@@ -9,6 +9,7 @@
column url => type is 'text';
column accepted_eula => type is 'boolean';
+ column published_address => type is 'text';
};
Added: apps/spensive/lib/Spensive/Notification/EmailError.pm
==============================================================================
--- (empty file)
+++ apps/spensive/lib/Spensive/Notification/EmailError.pm Tue Nov 20 12:10:05 2007
@@ -0,0 +1,97 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Spensive::Notification::EmailError -- A bounce message when there's an
+ error processing incoming mail
+
+=head1 DESCRIPTION
+
+We send an EmailError notification when we receive incoming mail that
+we get an error handling, for whatever reason.
+
+=cut
+
+package Spensive::Notification::EmailError;
+
+use base qw(Spensive::Notification);
+
+__PACKAGE__->mk_accessors(qw(result email address));
+
+=head2 result
+
+A Jifty::Result of the action that failed that caused us to send this
+bounce.
+
+=head2 email
+
+The text of the email that we received that generated the error.
+
+=head2 address
+
+The address to which the user attempted to send mail.
+
+=head2 setup
+
+Set up our subject and sender
+
+=cut
+
+sub setup {
+ my $self = shift;
+
+ $self->subject('Hiveminder.com -- Error processing email');
+ $self->from('Hiveminder <jesse at bestpractical.com>');
+}
+
+=head2 preface
+
+Return an apologetic message explaining that an error happened,
+including the text of the error.
+
+=cut
+
+sub preface {
+ my $self = shift;
+ my $to = $self->address;
+ my $error = $self->result->error;
+
+ return <<"END_PREFACE";
+We're sorry, but we encountered an error processing your email to us
+at $to.
+
+The error was: $error
+
+We hope we didn't mess up your day too badly with this. Drop us a line
+at support\@hiveminder.com if you need help fixing this problem, or
+try again in a little while.
+
+END_PREFACE
+
+}
+
+=head2 parts
+
+Returns the body, as well as an attachment of the original message.
+
+=cut
+
+sub parts {
+ my $self = shift;
+ return [
+ @{$self->SUPER::parts},
+ Email::MIME->create(
+ attributes => {
+ content_type => 'text/plain',
+ disposition => 'attachment',
+ },
+ body => Encode::encode_utf8($self->email),
+ )
+ ];
+}
+
+
+
+
+1;
Modified: apps/spensive/lib/Spensive/View.pm
==============================================================================
--- apps/spensive/lib/Spensive/View.pm (original)
+++ apps/spensive/lib/Spensive/View.pm Tue Nov 20 12:10:05 2007
@@ -6,15 +6,15 @@
template 'index.html' => page {
- h1 { _('Spensive helps you keep track of your expenses') };
+ h1 { _('Spensive helps you keep track of your business expenses') };
h2 { _('(It also helps you get reimbursed for them)') };
+ h3 { _('Unsubmitted expenses') };
+ span { {class is 'hint'}; hyperlink(label => 'All my expenses...', path => '/expenses/all') };
form {
my $search = Spensive::Model::ExpenseCollection->new();
- $search->limit(
- column => 'incurred_by',
- value => Jifty->web->current_user->id
- );
+ $search->limit( column => 'expense_report', value => 'null', operator => 'is', quote_value => 0);
+ $search->limit( column => 'incurred_by', value => Jifty->web->current_user->id);
set (search_collection => $search);
render_region(
name => 'expenses',
@@ -25,6 +25,8 @@
);
};
+ h3 { _('Open expense reports')};
+ span { {class is 'hint'}; hyperlink(label => 'All my expense reports...', path => '/reports/all') };
form {
my $search = Spensive::Model::ExpenseReportCollection->new();
$search->limit(
@@ -61,8 +63,19 @@
template report_tabs => sub {
my $self = shift;
- $self->render_tabs( 'reports', [qw(report)],
- qw(attach_reports_tab remove_reports_tab ) );
+ $self->render_tabs(
+ 'reports',
+ [qw(report)],
+ { label => 'Add expenses', path => '/attach_reports' ,
+ defer => 1
+ },
+ { label => 'Remove expenses',
+ path => '/remove_reports',
+ defer => 1,
+ }
+ );
+
+ # qw(/attach_reports_tab /remove_reports_tab ) );
};
template 'attach_reports' => sub {
More information about the Jifty-commit
mailing list