The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl -w
use strict;
use warnings FATAL => qw(all);

use fields qw(dbic
	      cf_datadir
	      cf_dbname cf_dbuser cf_dbpass);

use YATT::Lite::Entities qw(*CON);

sub DBIC () { __PACKAGE__ . '::DBIC' }

use YATT::Lite::WebMVC0::DBSchema::DBIC
  (DBIC, verbose => $ENV{DEBUG_DBSCHEMA}
   , [user => undef
      , uid => [integer => -primary_key
		, [-has_many
		   , [address => undef
		      , addrid => [integer => -primary_key]
		      , owner => [int => [-belongs_to => 'user']]
		      , country => 'text'
		      , zip => 'text'
		      , prefecture => 'text'
		      , city => 'text'
		      , address => 'text'], 'owner']
		, [-has_many
		   , [entry => undef
		      , eid => [integer => -primary_key]
		      , owner => [int => [-belongs_to => 'user']]
		      , title => 'text'
		      , text  => 'text'], 'owner']]
      , login => 'text'
      , encpass => 'text'
      , tmppass => 'text'
      , tmppass_expire => 'datetime'
      , email => 'text'
      , confirm_token => ['text', -unique]
     ]
   );

#========================================
Entity resultset => sub {
  shift->YATT->dbic->resultset(@_);
};

#========================================
Entity LOGIN => sub { 'login' };

Entity is_logged_in => sub {
  my ($this) = @_;
  $this->entity_sess($this->entity_LOGIN);
};

Entity set_logged_in => sub {
  my ($this, $login) = @_;
  if (defined $login and $login ne '') {
    $CON->start_session([$this->entity_LOGIN => $login]);
  } else {
    $CON->delete_session;
  }
};
#========================================
use Digest::MD5 qw(md5_hex);

sub is_user {
  my ($self, $loginname) = @_;
  $self->dbic->resultset('user')->single({login => $loginname})
}

sub has_auth_failure {
  my ($self, $loginname, $plain_pass) = @_;
  my $user = $self->dbic->resultset('user')->single({login => $loginname})
    or return "No such user: $loginname";
  return 'Password mismatch' unless $user->encpass eq md5_hex($plain_pass);
  return undef;
}

sub add_user {
  my ($self, $login, $pass, $email) = @_;

  # XXX: Is this good token?
  my $token = $self->encrypt_password
    ($self->make_password, $login, $pass);

  my $newuser = $self->dbic->resultset('user')
    ->new({login => $login
	   , email => $email
	   , encpass => md5_hex($pass)
	   , confirm_token => $token
	   # XXX: tmppass_expire
	  });

  $newuser->insert;

  ($newuser, $token);
}

# Stolen from Slash/Utility/Data/Data.pm:changePassword
{
  my @chars = grep !/[0O1Iil]/, 0..9, 'A'..'Z', 'a'..'z';
  sub make_password {
    my ($self, $len) = @_;
    return join '', map { $chars[rand @chars] } 1 .. ($len // 8);
  }

  sub encrypt_password {
    my ($self, @rest) = @_;
    md5_hex(join ":", reverse @rest);
  }
}

#========================================
use YATT::Lite::XHF qw(parse_xhf);
use YATT::Lite::Util qw(terse_dump);

sub output_file {
  my ($fn) = @_;
  open my $fh, '>', $fn or die "Can't open file '$fn': $!";
  $fh;
}

sub sendmail {
  my ($self, $con, $page, $widget_name, $to, @rest) = @_;
  if (grep {not defined $_} $widget_name, $to) {
    die "Not enough parameter!";
  }
  my $sub = $page->can("render_$widget_name")
    or die "Unknown widget $widget_name";

  my $transport = $ENV{EMAIL_SENDER_TRANSPORT};
  my $is_debug = defined $transport && $transport eq 'YATT_TEST';

  my $fh = $is_debug ? output_file("$self->{cf_datadir}/.htdebug.eml")
    : ostream(my $buffer);

  $sub->($page, $fh, $to, @rest);

  if ($is_debug) {
    return 'ok';
  } else {
    require Email::Simple;
    require Email::Sender::Simple;
    my $msg = Email::Simple->new($buffer);

    Email::Sender::Simple->send($msg);
  }
}

#========================================

sub dbic {
  my MY $self = shift;
  $self->{dbic} //= do {
    my ($dbi, $user, $pass) = $self->dbi_dsn;
    # DBIC warns 'AutoCommit => 0'.
    $self->DBIC->connect
      ($dbi, $user, $pass
       , {PrintError => 0, RaiseError => 1, AutoCommit => 1});
  };
}

sub dbi_dsn {
  my MY $self = shift;
  my $dsn = "dbi:mysql:database=$self->{cf_dbname}";
  wantarray ? ($dsn, $self->{cf_dbuser}, $self->{cf_dbpass}) : $dsn;
}

sub cmd_setup {
  my MY $self = shift;
  unless (-d $self->{cf_datadir}) {
    require File::Path;
    File::Path::make_path($self->{cf_datadir}, {mode => 02775, verbose => 1});
  }
  $self->DBIC->YATT_DBSchema->cf_let
    ([verbose => $ENV{VERBOSE} // 1, auto_create => 1
      , coltype_map => {text => 'varchar(80)'}]
     , connect_to => $self->dbi_dsn);
}

#========================================
sub after_new {
  my MY $self = shift;

  my $passfile = "$self->{cf_dir}/../.htdbpass";
  unless (-e $passfile) {
    die "Can't find $passfile";
  }
  unless (-r $passfile) {
    die "Can't read $passfile";
  }

  $self->cf_by_filetype(xhf => $passfile);

  foreach my $name (qw/dbname dbuser dbpass/) {
    unless (defined $self->{"cf_$name"}) {
      $self->error("'%s' is empty in '%s'!", $name, $passfile);
    }
  }

  $self->{cf_datadir} //= "$self->{cf_dir}/../data";
}