switch from HTTP Auth to Cookie Auth
This commit is contained in:
parent
ba6b517e5b
commit
fd60839116
11 changed files with 335 additions and 390 deletions
540
index.pl
540
index.pl
|
@ -44,13 +44,10 @@ app->plugin(
|
|||
authentication => {
|
||||
autoload_user => 1,
|
||||
session_key => 'foodor',
|
||||
fail_render => { template => 'login' },
|
||||
load_user => sub {
|
||||
my ( $self, $uid ) = @_;
|
||||
my $data = $self->get_user_data($uid);
|
||||
if ($data) {
|
||||
return { name => $data->{name} };
|
||||
}
|
||||
return undef;
|
||||
return $self->get_user_data($uid);
|
||||
},
|
||||
validate_user => sub {
|
||||
my ( $self, $username, $password, $extradata ) = @_;
|
||||
|
@ -68,6 +65,7 @@ app->plugin(
|
|||
},
|
||||
}
|
||||
);
|
||||
app->sessions->default_expiration( 60 * 60 * 24 * 180 );
|
||||
|
||||
app->defaults( layout => 'default' );
|
||||
|
||||
|
@ -427,7 +425,7 @@ helper 'checkin' => sub {
|
|||
}
|
||||
|
||||
my $success = $self->app->checkin_query->execute(
|
||||
$self->get_user_id,
|
||||
$self->current_user->{id},
|
||||
$self->get_station_id(
|
||||
ds100 => $status->{station_ds100},
|
||||
name => $status->{station_name}
|
||||
|
@ -457,7 +455,7 @@ helper 'checkin' => sub {
|
|||
helper 'undo' => sub {
|
||||
my ($self) = @_;
|
||||
|
||||
my $uid = $self->get_user_id;
|
||||
my $uid = $self->current_user->{id};
|
||||
$self->app->get_last_actions_query->execute($uid);
|
||||
my $rows = $self->app->get_last_actions_query->fetchall_arrayref;
|
||||
|
||||
|
@ -469,10 +467,10 @@ helper 'undo' => sub {
|
|||
return 'Repeated undo is not supported';
|
||||
}
|
||||
|
||||
my $success
|
||||
= $self->app->undo_query->execute( $self->get_user_id,
|
||||
my $success = $self->app->undo_query->execute(
|
||||
$self->current_user->{id},
|
||||
DateTime->now( time_zone => 'Europe/Berlin' )->epoch,
|
||||
);
|
||||
);
|
||||
|
||||
if ( defined $success ) {
|
||||
return;
|
||||
|
@ -501,7 +499,7 @@ helper 'checkout' => sub {
|
|||
if ( not defined $train ) {
|
||||
if ($force) {
|
||||
my $success = $self->app->checkout_query->execute(
|
||||
$self->get_user_id,
|
||||
$self->current_user->{id},
|
||||
$self->get_station_id(
|
||||
ds100 => $status->{station_ds100},
|
||||
name => $status->{station_name}
|
||||
|
@ -523,7 +521,7 @@ helper 'checkout' => sub {
|
|||
}
|
||||
else {
|
||||
my $success = $self->app->checkout_query->execute(
|
||||
$self->get_user_id,
|
||||
$self->current_user->{id},
|
||||
$self->get_station_id(
|
||||
ds100 => $status->{station_ds100},
|
||||
name => $status->{station_name}
|
||||
|
@ -580,7 +578,7 @@ helper 'get_user_token' => sub {
|
|||
helper 'get_user_data' => sub {
|
||||
my ( $self, $uid ) = @_;
|
||||
|
||||
$uid //= $self->get_user_id;
|
||||
$uid //= $self->current_user->{id};
|
||||
my $query = $self->app->get_user_query;
|
||||
$query->execute($uid);
|
||||
my $rows = $query->fetchall_arrayref;
|
||||
|
@ -603,7 +601,7 @@ helper 'get_user_data' => sub {
|
|||
deletion_requested => $row[7]
|
||||
};
|
||||
}
|
||||
return;
|
||||
return undef;
|
||||
};
|
||||
|
||||
helper 'get_user_password' => sub {
|
||||
|
@ -623,80 +621,9 @@ helper 'get_user_password' => sub {
|
|||
return;
|
||||
};
|
||||
|
||||
helper 'get_user_name' => sub {
|
||||
my ($self) = @_;
|
||||
|
||||
my $user = $self->req->headers->header('X-Remote-User') // 'dev';
|
||||
|
||||
return $user;
|
||||
};
|
||||
|
||||
helper 'get_user_id' => sub {
|
||||
helper 'add_user' => sub {
|
||||
my ( $self, $user_name, $email, $token, $password ) = @_;
|
||||
|
||||
$user_name //= $self->get_user_name;
|
||||
|
||||
if ( not -e $dbname ) {
|
||||
$self->app->dbh->begin_work;
|
||||
$self->app->dbh->do(
|
||||
qq{
|
||||
create table schema_version (
|
||||
version integer primary key
|
||||
);
|
||||
}
|
||||
);
|
||||
$self->app->dbh->do(
|
||||
qq{
|
||||
create table users (
|
||||
id integer primary key,
|
||||
name char(64) not null unique,
|
||||
status int not null,
|
||||
public_level bool not null,
|
||||
email char(256),
|
||||
token char(80),
|
||||
password text,
|
||||
registered_at datetime not null,
|
||||
last_login datetime not null,
|
||||
deletion_requested datetime
|
||||
)
|
||||
}
|
||||
);
|
||||
$self->app->dbh->do(
|
||||
qq{
|
||||
create table stations (
|
||||
id integer primary key,
|
||||
ds100 char(16) not null unique,
|
||||
name char(64) not null unique
|
||||
)
|
||||
}
|
||||
);
|
||||
$self->app->dbh->do(
|
||||
qq{
|
||||
create table user_actions (
|
||||
user_id int not null,
|
||||
action_id int not null,
|
||||
station_id int,
|
||||
action_time int not null,
|
||||
train_type char(16),
|
||||
train_line char(16),
|
||||
train_no char(16),
|
||||
train_id char(128),
|
||||
sched_time int,
|
||||
real_time int,
|
||||
route text,
|
||||
messages text,
|
||||
primary key (user_id, action_time)
|
||||
)
|
||||
}
|
||||
);
|
||||
$self->app->dbh->do(
|
||||
qq{
|
||||
insert into schema_version (version) values (1);
|
||||
}
|
||||
);
|
||||
$self->app->dbh->commit;
|
||||
}
|
||||
|
||||
$self->app->get_userid_query->execute($user_name);
|
||||
my $rows = $self->app->get_userid_query->fetchall_arrayref;
|
||||
|
||||
|
@ -737,7 +664,7 @@ helper 'check_if_user_name_exists' => sub {
|
|||
helper 'get_user_travels' => sub {
|
||||
my ( $self, $limit ) = @_;
|
||||
|
||||
my $uid = $self->get_user_id;
|
||||
my $uid = $self->current_user->{id};
|
||||
my $query = $self->app->get_all_actions_query;
|
||||
if ($limit) {
|
||||
$query = $self->app->get_last_actions_query;
|
||||
|
@ -800,7 +727,7 @@ helper 'get_user_travels' => sub {
|
|||
helper 'get_user_status' => sub {
|
||||
my ($self) = @_;
|
||||
|
||||
my $uid = $self->get_user_id;
|
||||
my $uid = $self->current_user->{id};
|
||||
$self->app->get_last_actions_query->execute($uid);
|
||||
my $rows = $self->app->get_last_actions_query->fetchall_arrayref;
|
||||
|
||||
|
@ -886,7 +813,217 @@ helper 'navbar_class' => sub {
|
|||
|
||||
get '/' => sub {
|
||||
my ($self) = @_;
|
||||
$self->render( 'landingpage', with_geolocation => 1 );
|
||||
if ( $self->is_user_authenticated ) {
|
||||
$self->render( 'landingpage', with_geolocation => 1 );
|
||||
}
|
||||
else {
|
||||
$self->render( 'landingpage', intro => 1 );
|
||||
}
|
||||
};
|
||||
|
||||
get '/about' => sub {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->render( 'about', version => $VERSION );
|
||||
};
|
||||
|
||||
get '/impressum' => sub {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->render('imprint');
|
||||
};
|
||||
|
||||
get '/imprint' => sub {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->render('imprint');
|
||||
};
|
||||
|
||||
post '/geolocation' => sub {
|
||||
my ($self) = @_;
|
||||
|
||||
my $lon = $self->param('lon');
|
||||
my $lat = $self->param('lat');
|
||||
|
||||
if ( not $lon or not $lat ) {
|
||||
$self->render( json => { error => 'Invalid lon/lat received' } );
|
||||
}
|
||||
else {
|
||||
my @candidates = map {
|
||||
{
|
||||
ds100 => $_->[0][0],
|
||||
name => $_->[0][1],
|
||||
eva => $_->[0][2],
|
||||
lon => $_->[0][3],
|
||||
lat => $_->[0][4],
|
||||
distance => $_->[1],
|
||||
}
|
||||
} Travel::Status::DE::IRIS::Stations::get_station_by_location( $lon,
|
||||
$lat, 5 );
|
||||
$self->render(
|
||||
json => {
|
||||
candidates => [@candidates],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
get '/login' => sub {
|
||||
my ($self) = @_;
|
||||
$self->render('login');
|
||||
};
|
||||
|
||||
post '/login' => sub {
|
||||
my ($self) = @_;
|
||||
my $user = $self->req->param('user');
|
||||
my $password = $self->req->param('password');
|
||||
|
||||
# Keep cookies for 6 months
|
||||
$self->session( expiration => 60 * 60 * 24 * 180 );
|
||||
|
||||
if ( $self->validation->csrf_protect->has_error('csrf_token') ) {
|
||||
$self->render(
|
||||
'login',
|
||||
invalid => 'csrf',
|
||||
);
|
||||
}
|
||||
else {
|
||||
if ( $self->authenticate( $user, $password ) ) {
|
||||
$self->redirect_to('/');
|
||||
}
|
||||
else {
|
||||
$self->render( 'login', invalid => 'credentials' );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
get '/register' => sub {
|
||||
my ($self) = @_;
|
||||
$self->render('register');
|
||||
};
|
||||
|
||||
post '/register' => sub {
|
||||
my ($self) = @_;
|
||||
my $user = $self->req->param('user');
|
||||
my $email = $self->req->param('email');
|
||||
my $password = $self->req->param('password');
|
||||
my $password2 = $self->req->param('password2');
|
||||
my $ip = $self->req->headers->header('X-Forwarded-For');
|
||||
my $ua = $self->req->headers->user_agent;
|
||||
my $date = DateTime->now( time_zone => 'Europe/Berlin' )
|
||||
->strftime('%d.%m.%Y %H:%M:%S %z');
|
||||
|
||||
# In case Mojolicious is not running behind a reverse proxy
|
||||
$ip
|
||||
//= sprintf( '%s:%s', $self->tx->remote_address, $self->tx->remote_port );
|
||||
|
||||
if ( $self->validation->csrf_protect->has_error('csrf_token') ) {
|
||||
$self->render(
|
||||
'register',
|
||||
invalid => 'csrf',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if ( not length($user) ) {
|
||||
$self->render( 'register', invalid => 'user_empty' );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( not length($email) ) {
|
||||
$self->render( 'register', invalid => 'mail_empty' );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $user !~ m{ ^ [0-9a-zA-Z_-]+ $ }x ) {
|
||||
$self->render( 'register', invalid => 'user_format' );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $self->check_if_user_name_exists($user) ) {
|
||||
$self->render( 'register', invalid => 'user_collision' );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $password ne $password2 ) {
|
||||
$self->render( 'register', invalid => 'password_notequal' );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( length($password) < 8 ) {
|
||||
$self->render( 'register', invalid => 'password_short' );
|
||||
return;
|
||||
}
|
||||
|
||||
my $token = make_token();
|
||||
my $pw_hash = hash_password($password);
|
||||
my $user_id = $self->add_user( $user, $email, $token, $pw_hash );
|
||||
|
||||
my $body = "Hallo, ${user}!\n\n";
|
||||
$body .= "Mit deiner E-Mail-Adresse (${email}) wurde ein Account auf\n";
|
||||
$body .= "travelynx.finalrewind.org angelegt.\n\n";
|
||||
$body
|
||||
.= "Falls die Registrierung von dir ausging, kannst du den Account unter\n";
|
||||
$body .= "https://travelynx.finalrewind.org/reg/${user_id}/${token}\n";
|
||||
$body .= "freischalten.\n\n";
|
||||
$body
|
||||
.= "Falls nicht, ignoriere diese Mail bitte. Nach 48 Stunden wird deine\n";
|
||||
$body
|
||||
.= "Mail-Adresse erneut zur Registrierung freigeschaltet. Falls auch diese fehlschlägt,\n";
|
||||
$body
|
||||
.= "werden wir sie dauerhaft sperren und keine Mails mehr dorthin schicken.\n\n";
|
||||
$body .= "Daten zur Registrierung:\n";
|
||||
$body .= " * Datum: ${date}\n";
|
||||
$body .= " * Verwendete IP: ${ip}\n";
|
||||
$body .= " * Verwendeter Browser gemäß User Agent: ${ua}\n\n\n";
|
||||
$body .= "Impressum: https://travelynx.finalrewind.org/impressum\n";
|
||||
|
||||
my $reg_mail = Email::Simple->create(
|
||||
header => [
|
||||
To => $email,
|
||||
From => 'Travelynx <travelynx@finalrewind.org>',
|
||||
Subject => 'Registrierung auf travelynx.finalrewind.org',
|
||||
'Content-Type' => 'text/plain; charset=UTF-8',
|
||||
],
|
||||
body => encode( 'utf-8', $body ),
|
||||
);
|
||||
|
||||
my $success = try_to_sendmail($reg_mail);
|
||||
if ($success) {
|
||||
$self->render( 'login', from => 'register' );
|
||||
}
|
||||
else {
|
||||
$self->render( 'register', invalid => 'sendmail' );
|
||||
}
|
||||
};
|
||||
|
||||
get '/reg/:id/:token' => sub {
|
||||
my ($self) = @_;
|
||||
|
||||
my $id = $self->stash('id');
|
||||
my $token = $self->stash('token');
|
||||
|
||||
my @db_user = $self->get_user_token($id);
|
||||
|
||||
if ( not @db_user ) {
|
||||
$self->render( 'register', invalid => 'token' );
|
||||
return;
|
||||
}
|
||||
|
||||
my ( $db_name, $db_status, $db_token ) = @db_user;
|
||||
|
||||
if ( not $db_name or $token ne $db_token or $db_status != 0 ) {
|
||||
$self->render( 'register', invalid => 'token' );
|
||||
return;
|
||||
}
|
||||
$self->app->set_status_query->execute( 1, $id );
|
||||
$self->render( 'login', from => 'verification' );
|
||||
};
|
||||
|
||||
under sub {
|
||||
my ($self) = @_;
|
||||
return $self->is_user_authenticated;
|
||||
};
|
||||
|
||||
post '/action' => sub {
|
||||
|
@ -979,15 +1116,21 @@ post '/action' => sub {
|
|||
}
|
||||
};
|
||||
|
||||
get '/a/account' => sub {
|
||||
get '/account' => sub {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->render('account');
|
||||
};
|
||||
|
||||
get '/a/export.json' => sub {
|
||||
get '/history' => sub {
|
||||
my ($self) = @_;
|
||||
my $uid = $self->get_user_id;
|
||||
|
||||
$self->render('history');
|
||||
};
|
||||
|
||||
get '/export.json' => sub {
|
||||
my ($self) = @_;
|
||||
my $uid = $self->current_user->{id};
|
||||
my $query = $self->app->get_all_actions_query;
|
||||
|
||||
$query->execute($uid);
|
||||
|
@ -1031,220 +1174,13 @@ get '/a/export.json' => sub {
|
|||
);
|
||||
};
|
||||
|
||||
get '/a/history' => sub {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->render('history');
|
||||
};
|
||||
|
||||
get '/x/about' => sub {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->render( 'about', version => $VERSION );
|
||||
};
|
||||
|
||||
get '/x/impressum' => sub {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->render('imprint');
|
||||
};
|
||||
|
||||
get '/x/imprint' => sub {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->render('imprint');
|
||||
};
|
||||
|
||||
post '/x/geolocation' => sub {
|
||||
my ($self) = @_;
|
||||
|
||||
my $lon = $self->param('lon');
|
||||
my $lat = $self->param('lat');
|
||||
|
||||
if ( not $lon or not $lat ) {
|
||||
$self->render( json => { error => 'Invalid lon/lat received' } );
|
||||
}
|
||||
else {
|
||||
my @candidates = map {
|
||||
{
|
||||
ds100 => $_->[0][0],
|
||||
name => $_->[0][1],
|
||||
eva => $_->[0][2],
|
||||
lon => $_->[0][3],
|
||||
lat => $_->[0][4],
|
||||
distance => $_->[1],
|
||||
}
|
||||
} Travel::Status::DE::IRIS::Stations::get_station_by_location( $lon,
|
||||
$lat, 5 );
|
||||
$self->render(
|
||||
json => {
|
||||
candidates => [@candidates],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
get '/x/login' => sub {
|
||||
my ($self) = @_;
|
||||
$self->render('login');
|
||||
};
|
||||
|
||||
post '/x/login' => sub {
|
||||
my ($self) = @_;
|
||||
my $user = $self->req->param('user');
|
||||
my $password = $self->req->param('password');
|
||||
|
||||
# Keep cookies for 6 months
|
||||
$self->session( expiration => 60 * 60 * 24 * 180 );
|
||||
|
||||
if ( $self->validation->csrf_protect->has_error('csrf_token') ) {
|
||||
$self->render(
|
||||
'login',
|
||||
invalid => 'csrf',
|
||||
);
|
||||
}
|
||||
else {
|
||||
if ( $self->authenticate( $user, $password ) ) {
|
||||
$self->redirect_to('/');
|
||||
}
|
||||
else {
|
||||
$self->render( 'login', invalid => 'credentials' );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
get '/x/logout' => sub {
|
||||
post '/logout' => sub {
|
||||
my ($self) = @_;
|
||||
$self->logout;
|
||||
$self->redirect_to('/x/login');
|
||||
$self->redirect_to('/login');
|
||||
};
|
||||
|
||||
get '/x/register' => sub {
|
||||
my ($self) = @_;
|
||||
$self->render('register');
|
||||
};
|
||||
|
||||
post '/x/register' => sub {
|
||||
my ($self) = @_;
|
||||
my $user = $self->req->param('user');
|
||||
my $email = $self->req->param('email');
|
||||
my $password = $self->req->param('password');
|
||||
my $password2 = $self->req->param('password2');
|
||||
my $ip = $self->req->headers->header('X-Forwarded-For');
|
||||
my $ua = $self->req->headers->user_agent;
|
||||
my $date = DateTime->now( time_zone => 'Europe/Berlin' )
|
||||
->strftime('%d.%m.%Y %H:%M:%S %z');
|
||||
|
||||
# In case Mojolicious is not running behind a reverse proxy
|
||||
$ip
|
||||
//= sprintf( '%s:%s', $self->tx->remote_address, $self->tx->remote_port );
|
||||
|
||||
if ( $self->validation->csrf_protect->has_error('csrf_token') ) {
|
||||
$self->render(
|
||||
'register',
|
||||
invalid => 'csrf',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if ( not length($user) ) {
|
||||
$self->render( 'register', invalid => 'user_empty' );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( not length($email) ) {
|
||||
$self->render( 'register', invalid => 'mail_empty' );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $user !~ m{ ^ [0-9a-zA-Z_-]+ $ }x ) {
|
||||
$self->render( 'register', invalid => 'user_format' );
|
||||
return;
|
||||
}
|
||||
|
||||
#if ( $self->check_if_user_name_exists($user) or $user eq 'dev' ) {
|
||||
if ( $user ne $self->get_user_name ) {
|
||||
$self->render( 'register', invalid => 'user_collision' );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $password ne $password2 ) {
|
||||
$self->render( 'register', invalid => 'password_notequal' );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( length($password) < 8 ) {
|
||||
$self->render( 'register', invalid => 'password_short' );
|
||||
return;
|
||||
}
|
||||
|
||||
my $token = make_token();
|
||||
my $pw_hash = hash_password($password);
|
||||
my $user_id = $self->get_user_id( $user, $email, $token, $pw_hash );
|
||||
|
||||
my $body = "Hallo, ${user}!\n\n";
|
||||
$body .= "Mit deiner E-Mail-Adresse (${email}) wurde ein Account auf\n";
|
||||
$body .= "travelynx.finalrewind.org angelegt.\n\n";
|
||||
$body
|
||||
.= "Falls die Registrierung von dir ausging, kannst du den Account unter\n";
|
||||
$body .= "https://travelynx.finalrewind.org/x/reg/${user_id}/${token}\n";
|
||||
$body .= "freischalten.\n\n";
|
||||
$body
|
||||
.= "Falls nicht, ignoriere diese Mail bitte. Nach 48 Stunden wird deine\n";
|
||||
$body
|
||||
.= "Mail-Adresse erneut zur Registrierung freigeschaltet. Falls auch diese fehlschlägt,\n";
|
||||
$body
|
||||
.= "werden wir sie dauerhaft sperren und keine Mails mehr dorthin schicken.\n\n";
|
||||
$body .= "Daten zur Registrierung:\n";
|
||||
$body .= " * Datum: ${date}\n";
|
||||
$body .= " * Verwendete IP: ${ip}\n";
|
||||
$body .= " * Verwendeter Browser gemäß User Agent: ${ua}\n\n\n";
|
||||
$body .= "Impressum: https://travelynx.finalrewind.org/x/impressum\n";
|
||||
|
||||
my $reg_mail = Email::Simple->create(
|
||||
header => [
|
||||
To => $email,
|
||||
From => 'Travelynx <travelynx@finalrewind.org>',
|
||||
Subject => 'Registrierung auf travelynx.finalrewind.org',
|
||||
'Content-Type' => 'text/plain; charset=UTF-8',
|
||||
],
|
||||
body => encode( 'utf-8', $body ),
|
||||
);
|
||||
|
||||
my $success = try_to_sendmail($reg_mail);
|
||||
if ($success) {
|
||||
$self->render( 'login', from => 'register' );
|
||||
}
|
||||
else {
|
||||
$self->render( 'register', invalid => 'sendmail' );
|
||||
}
|
||||
};
|
||||
|
||||
get '/x/reg/:id/:token' => sub {
|
||||
my ($self) = @_;
|
||||
|
||||
my $id = $self->stash('id');
|
||||
my $token = $self->stash('token');
|
||||
|
||||
my @db_user = $self->get_user_token($id);
|
||||
|
||||
if ( not @db_user ) {
|
||||
$self->render( 'register', invalid => 'token' );
|
||||
return;
|
||||
}
|
||||
|
||||
my ( $db_name, $db_status, $db_token ) = @db_user;
|
||||
|
||||
if ( not $db_name or $token ne $db_token or $db_status != 0 ) {
|
||||
$self->render( 'register', invalid => 'token' );
|
||||
return;
|
||||
}
|
||||
$self->app->set_status_query->execute( 1, $id );
|
||||
$self->render( 'login', from => 'verification' );
|
||||
};
|
||||
|
||||
get '/*station' => sub {
|
||||
get '/s/*station' => sub {
|
||||
my ($self) = @_;
|
||||
my $station = $self->stash('station');
|
||||
|
||||
|
|
|
@ -25,14 +25,14 @@ $(document).ready(function() {
|
|||
stationlink.attr('href', ds100);
|
||||
stationlink.text(name);
|
||||
|
||||
resultBody.append('<tr><td><a href="/' + ds100 + '">' + name + '</a></td></tr>');
|
||||
resultBody.append('<tr><td><a href="/s/' + ds100 + '">' + name + '</a></td></tr>');
|
||||
});
|
||||
placeholder.replaceWith(resultTable);
|
||||
}
|
||||
};
|
||||
|
||||
var processLocation = function(loc) {
|
||||
$.post('/x/geolocation', {lon: loc.coords.longitude, lat: loc.coords.latitude}, processResult);
|
||||
$.post('/geolocation', {lon: loc.coords.longitude, lat: loc.coords.latitude}, processResult);
|
||||
};
|
||||
|
||||
var processError = function(error) {
|
||||
|
|
2
public/static/js/geolocation.min.js
vendored
2
public/static/js/geolocation.min.js
vendored
|
@ -1 +1 @@
|
|||
$(document).ready(function(){var e=$("p.geolocationhint"),t=$("div.geolocation div.progress"),o=function(o,a,n){e.remove(),t.remove()},a=function(e){e.error?o(0,e.error):0==e.candidates.length?o():(resultTable=$("<table><tbody></tbody></table>"),resultBody=resultTable.children(),$.each(e.candidates,function(e,t){var o=t.ds100,a=t.name,n=t.distance;n=n.toFixed(1);var r=$(document.createElement("a"));r.attr("href",o),r.text(a),resultBody.append('<tr><td><a href="/'+o+'">'+a+"</a></td></tr>")}),t.replaceWith(resultTable))},n=function(e){$.post("/x/geolocation",{lon:e.coords.longitude,lat:e.coords.latitude},a)},r=function(e){e.code==e.PERMISSION_DENIED?o():e.code==e.POSITION_UNAVAILABLE?o():(e.code,e.TIMEOUT,o())};navigator.geolocation?navigator.geolocation.getCurrentPosition(n,r):o()});
|
||||
$(document).ready(function(){var a=$("p.geolocationhint"),n=$("div.geolocation div.progress"),t=function(e,t,o){a.remove(),n.remove()},o=function(e){e.error?t(0,e.error):0==e.candidates.length?t():(resultTable=$("<table><tbody></tbody></table>"),resultBody=resultTable.children(),$.each(e.candidates,function(e,t){var o=t.ds100,a=t.name,n=t.distance;n=n.toFixed(1);var r=$(document.createElement("a"));r.attr("href",o),r.text(a),resultBody.append('<tr><td><a href="/s/'+o+'">'+a+"</a></td></tr>")}),n.replaceWith(resultTable))};navigator.geolocation?navigator.geolocation.getCurrentPosition(function(e){$.post("/geolocation",{lon:e.coords.longitude,lat:e.coords.latitude},o)},function(e){e.code==e.PERMISSION_DENIED||e.code==e.POSITION_UNAVAILABLE||(e.code,e.TIMEOUT),t()}):t()});
|
||||
|
|
|
@ -34,7 +34,7 @@ $(document).ready(function() {
|
|||
station: link.data('station'),
|
||||
force: link.data('force'),
|
||||
};
|
||||
tvly_run(link, req, '/' + req.station, function() {
|
||||
tvly_run(link, req, '/s/' + req.station, function() {
|
||||
link.append(' – Ohne Echtzeitdaten auschecken?')
|
||||
link.data('force', true);
|
||||
});
|
||||
|
|
2
public/static/js/travelynx-actions.min.js
vendored
2
public/static/js/travelynx-actions.min.js
vendored
|
@ -1 +1 @@
|
|||
function tvly_run(n,t,i,a){var c='<i class="material-icons">error</i>',o=$('<div class="progress"><div class="indeterminate"></div></div>');n.hide(),n.after(o),$.post("/action",t,function(t){t.success?$(location).attr("href",i):(M.toast({html:c+" "+t.error}),o.remove(),a&&a(),n.append(" "+c),n.show())})}$(document).ready(function(){$(".action-checkin").click(function(){var t=$(this);tvly_run(t,{action:"checkin",station:t.data("station"),train:t.data("train")},"/")}),$(".action-checkout").click(function(){var t=$(this),n={action:"checkout",station:t.data("station"),force:t.data("force")};tvly_run(t,n,"/"+n.station,function(){t.append(" – Ohne Echtzeitdaten auschecken?"),t.data("force",!0)})}),$(".action-undo").click(function(){tvly_run($(this),{action:"undo"},window.location.href)})});
|
||||
function tvly_run(n,t,i,a){var c='<i class="material-icons">error</i>',o=$('<div class="progress"><div class="indeterminate"></div></div>');n.hide(),n.after(o),$.post("/action",t,function(t){t.success?$(location).attr("href",i):(M.toast({html:c+" "+t.error}),o.remove(),a&&a(),n.append(" "+c),n.show())})}$(document).ready(function(){$(".action-checkin").click(function(){var t=$(this);tvly_run(t,{action:"checkin",station:t.data("station"),train:t.data("train")},"/")}),$(".action-checkout").click(function(){var t=$(this),n={action:"checkout",station:t.data("station"),force:t.data("force")};tvly_run(t,n,"/s/"+n.station,function(){t.append(" – Ohne Echtzeitdaten auschecken?"),t.data("force",!0)})}),$(".action-undo").click(function(){tvly_run($(this),{action:"undo"},window.location.href)})});
|
||||
|
|
1
public/static/v1
Symbolic link
1
public/static/v1
Symbolic link
|
@ -0,0 +1 @@
|
|||
.
|
|
@ -22,7 +22,7 @@
|
|||
<div class="row">
|
||||
<div class="col s12">
|
||||
<ul>
|
||||
<li><a href="/a/export.json">Rohdaten</a> (Kein API-Ersatz, das Format kann sich jederzeit ändern)</li>
|
||||
<li><a href="/export.json">Rohdaten</a> (Kein API-Ersatz, das Format kann sich jederzeit ändern)</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,72 +1,77 @@
|
|||
<div class="row">
|
||||
<div class="col s12">
|
||||
% my $status = $self->get_user_status;
|
||||
% if ($status->{checked_in}) {
|
||||
<div class="card green darken-4">
|
||||
<div class="card-content white-text">
|
||||
<span class="card-title">Hallo, <%= $self->get_user_name %>!</span>
|
||||
<p>Du bist gerade eingecheckt in
|
||||
<%= $status->{train_type} %> <%= $status->{train_no} %>
|
||||
ab <%= $status->{station_name} %>.
|
||||
% if ($status->{timestamp_delta} < 3600) {
|
||||
<a class="action-undo"><i class="material-icons">undo</i> Rückgängig</a>
|
||||
% }
|
||||
</p>
|
||||
<p>Auschecken?</p>
|
||||
<table>
|
||||
<tbody>
|
||||
% my $is_after = 0;
|
||||
% for my $station (@{$status->{route_after}}) {
|
||||
<tr><td><a class="action-checkout" data-station="<%= $station %>"><%= $station %></a></td></tr>
|
||||
% if (is_user_authenticated()) {
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
% my $status = get_user_status();
|
||||
% if ($status->{checked_in}) {
|
||||
<div class="card green darken-4">
|
||||
<div class="card-content white-text">
|
||||
<span class="card-title">Hallo, <%= current_user()->{name} %>!</span>
|
||||
<p>Du bist gerade eingecheckt in
|
||||
<%= $status->{train_type} %> <%= $status->{train_no} %>
|
||||
ab <%= $status->{station_name} %>.
|
||||
% if ($status->{timestamp_delta} < 3600) {
|
||||
<a class="action-undo"><i class="material-icons">undo</i> Rückgängig</a>
|
||||
% }
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
% }
|
||||
% else {
|
||||
<div class="card grey darken-4">
|
||||
<div class="card-content white-text">
|
||||
<span class="card-title">Hallo, <%= $self->get_user_name %>!</span>
|
||||
<p>Du bist gerade nicht eingecheckt.</p>
|
||||
<p class="geolocationhint">Stationen in der Umgebung:</p>
|
||||
<div class="geolocation">
|
||||
<div class="progress"><div class="indeterminate"></div></div>
|
||||
</p>
|
||||
<p>Auschecken?</p>
|
||||
<table>
|
||||
<tbody>
|
||||
% my $is_after = 0;
|
||||
% for my $station (@{$status->{route_after}}) {
|
||||
<tr><td><a class="action-checkout" data-station="<%= $station %>"><%= $station %></a></td></tr>
|
||||
% }
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
% }
|
||||
</div>
|
||||
</div>
|
||||
<h1>Letzte Fahrten</h1>
|
||||
<div class="row">
|
||||
<table class="striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Datum</th>
|
||||
<th>Zug</th>
|
||||
<th>Strecke</th>
|
||||
<th>Dauer</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
% for my $travel (get_user_travels(1)) {
|
||||
% if ($travel->{completed}) {
|
||||
<tr>
|
||||
<td><%= $travel->{sched_departure}->strftime('%d.%m.%Y') %></td>
|
||||
<td><%= $travel->{type} %> <%= $travel->{line} // $travel->{no} %></td>
|
||||
<td><%= $travel->{from_name} %> → <%= $travel->{to_name} %></td>
|
||||
% if ($travel->{rt_arrival}->epoch and $travel->{rt_departure}->epoch) {
|
||||
<td><%= ($travel->{rt_arrival}->epoch - $travel->{rt_departure}->epoch) / 60 %> min
|
||||
</td>
|
||||
% } else {
|
||||
<td><%= sprintf('%.f', $self->get_travel_distance($travel->{from_name}, $travel->{to_name}, $travel->{route})) %>km
|
||||
<i class="material-icons">timer_off</i>
|
||||
</td>
|
||||
% }
|
||||
</tr>
|
||||
% }
|
||||
% }
|
||||
</tbody>
|
||||
</tabel>
|
||||
</div>
|
||||
% else {
|
||||
<div class="card grey darken-4">
|
||||
<div class="card-content white-text">
|
||||
<span class="card-title">Hallo, <%= current_user()->{name} %>!</span>
|
||||
<p>Du bist gerade nicht eingecheckt.</p>
|
||||
<p class="geolocationhint">Stationen in der Umgebung:</p>
|
||||
<div class="geolocation">
|
||||
<div class="progress"><div class="indeterminate"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
% }
|
||||
</div>
|
||||
</div>
|
||||
<h1>Letzte Fahrten</h1>
|
||||
<div class="row">
|
||||
<table class="striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Datum</th>
|
||||
<th>Zug</th>
|
||||
<th>Strecke</th>
|
||||
<th>Dauer</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
% for my $travel (get_user_travels(1)) {
|
||||
% if ($travel->{completed}) {
|
||||
<tr>
|
||||
<td><%= $travel->{sched_departure}->strftime('%d.%m.%Y') %></td>
|
||||
<td><%= $travel->{type} %> <%= $travel->{line} // $travel->{no} %></td>
|
||||
<td><%= $travel->{from_name} %> → <%= $travel->{to_name} %></td>
|
||||
% if ($travel->{rt_arrival}->epoch and $travel->{rt_departure}->epoch) {
|
||||
<td><%= ($travel->{rt_arrival}->epoch - $travel->{rt_departure}->epoch) / 60 %> min
|
||||
</td>
|
||||
% } else {
|
||||
<td><%= sprintf('%.f', $self->get_travel_distance($travel->{from_name}, $travel->{to_name}, $travel->{route})) %>km
|
||||
<i class="material-icons">timer_off</i>
|
||||
</td>
|
||||
% }
|
||||
</tr>
|
||||
% }
|
||||
% }
|
||||
</tbody>
|
||||
</tabel>
|
||||
</div>
|
||||
% }
|
||||
% else {
|
||||
Huhu!
|
||||
% }
|
||||
|
|
|
@ -5,14 +5,15 @@
|
|||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="theme-color" content="#673ab7">
|
||||
%= stylesheet '/static/css/materialize.min.css'
|
||||
%= stylesheet '/static/css/material-icons.css'
|
||||
%= stylesheet '/static/css/local.css'
|
||||
%= javascript '/static/js/jquery-2.2.4.min.js'
|
||||
%= javascript '/static/js/materialize.min.js'
|
||||
%= javascript '/static/js/travelynx-actions.min.js'
|
||||
% my $av = 'v1'; # asset version
|
||||
%= stylesheet "/static/${av}/css/materialize.min.css"
|
||||
%= stylesheet "/static/${av}/css/material-icons.css"
|
||||
%= stylesheet "/static/${av}/css/local.css"
|
||||
%= javascript "/static/${av}/js/jquery-2.2.4.min.js"
|
||||
%= javascript "/static/${av}/js/materialize.min.js"
|
||||
%= javascript "/static/${av}/js/travelynx-actions.min.js"
|
||||
% if (stash('with_geolocation')) {
|
||||
%= javascript '/static/js/geolocation.min.js'
|
||||
%= javascript "/static/${av}/js/geolocation.min.js"
|
||||
% }
|
||||
</head>
|
||||
<body>
|
||||
|
@ -21,9 +22,11 @@
|
|||
<div class="nav-wrapper container">
|
||||
<a href="/" class="brand-logo left">travelynx</a>
|
||||
<ul id="nav-mobile" class="right">
|
||||
<li class="<%= navbar_class('/a/history') %>"><a href='/a/history' title="History"><i class="material-icons">history</i></a></li>
|
||||
<li class="<%= navbar_class('/a/account') %>"><a href="/a/account" title="Account"><i class="material-icons">account_circle</i></a></li>
|
||||
<li class="<%= navbar_class('/x/about') %>"><a href='/x/about' title="About"><i class="material-icons">info_outline</i></a></li>
|
||||
% if (is_user_authenticated()) {
|
||||
<li class="<%= navbar_class('/history') %>"><a href='/history' title="History"><i class="material-icons">history</i></a></li>
|
||||
<li class="<%= navbar_class('/account') %>"><a href="/account" title="Account"><i class="material-icons">account_circle</i></a></li>
|
||||
% }
|
||||
<li class="<%= navbar_class('/about') %>"><a href='/about' title="About"><i class="material-icons">info_outline</i></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<p>
|
||||
Du bist bereits angemeldet. Falls du mehrere Accounts hast
|
||||
und auf einen anderen wechseln möchtest, musst du dich
|
||||
vorher <a href="/x/logout">abmelden</a>.
|
||||
vorher <a href="/logout">abmelden</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -64,7 +64,7 @@
|
|||
</div>
|
||||
% }
|
||||
<div class="row">
|
||||
%= form_for '/x/login' => (class => 'col s12', method => 'POST') => begin
|
||||
%= form_for '/login' => (class => 'col s12', method => 'POST') => begin
|
||||
%= csrf_field
|
||||
<div class="row">
|
||||
<div class="input-field col s12">
|
||||
|
|
|
@ -59,7 +59,7 @@
|
|||
</div>
|
||||
% }
|
||||
<div class="row">
|
||||
%= form_for '/x/register' => (class => 'col s12', method => 'POST') => begin
|
||||
%= form_for '/register' => (class => 'col s12', method => 'POST') => begin
|
||||
%= csrf_field
|
||||
<div class="row">
|
||||
<div class="input-field col l6 m12 s12">
|
||||
|
@ -103,7 +103,7 @@
|
|||
Die Mail-Adresse wird ausschließlich zur Bestätigung der Anmeldung
|
||||
und für die "Passwort vergessen"-Funktionalität verwendet und nicht
|
||||
an Dritte weitergegeben. Die <a
|
||||
href="/x/impressum">Datenschutzerklärung</a> beschreibt weitere
|
||||
href="/impressum">Datenschutzerklärung</a> beschreibt weitere
|
||||
erhobene Daten sowie deren Zweck und Speicherfristen.
|
||||
Accounts werden nach einem Jahr ohne Nutzung automatisch gelöscht.
|
||||
</p>
|
||||
|
|
Loading…
Reference in a new issue