Initial commit
This commit is contained in:
commit
21643b053d
37 changed files with 3263 additions and 0 deletions
604
index.pl
Normal file
604
index.pl
Normal file
|
@ -0,0 +1,604 @@
|
||||||
|
#!/usr/bin/env perl
|
||||||
|
|
||||||
|
use Mojolicious::Lite;
|
||||||
|
use Cache::File;
|
||||||
|
use DateTime;
|
||||||
|
use DBI;
|
||||||
|
use List::Util qw(first);
|
||||||
|
use Travel::Status::DE::IRIS;
|
||||||
|
use Travel::Status::DE::IRIS::Stations;
|
||||||
|
|
||||||
|
our $VERSION = qx{git describe --dirty} || 'experimental';
|
||||||
|
|
||||||
|
my $cache_iris_main = Cache::File->new(
|
||||||
|
cache_root => $ENV{TRAVELYNX_IRIS_CACHE} // '/tmp/dbf-iris-main',
|
||||||
|
default_expires => '6 hours',
|
||||||
|
lock_level => Cache::File::LOCK_LOCAL(),
|
||||||
|
);
|
||||||
|
|
||||||
|
my $cache_iris_rt = Cache::File->new(
|
||||||
|
cache_root => $ENV{TRAVELYNX_IRISRT_CACHE} // '/tmp/dbf-iris-realtime',
|
||||||
|
default_expires => '70 seconds',
|
||||||
|
lock_level => Cache::File::LOCK_LOCAL(),
|
||||||
|
);
|
||||||
|
|
||||||
|
my $dbname = $ENV{TRAVELYNX_DB_FILE} // 'travelynx.sqlite';
|
||||||
|
|
||||||
|
my %action_type = (
|
||||||
|
checkin => 1,
|
||||||
|
checkout => 2,
|
||||||
|
undo => -1
|
||||||
|
);
|
||||||
|
|
||||||
|
app->defaults( layout => 'default' );
|
||||||
|
|
||||||
|
app->attr(
|
||||||
|
add_station_query => sub {
|
||||||
|
my ($self) = @_;
|
||||||
|
|
||||||
|
return $self->app->dbh->prepare(
|
||||||
|
qq{
|
||||||
|
insert into stations (ds100, name) values (?, ?)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
app->attr(
|
||||||
|
add_user_query => sub {
|
||||||
|
my ($self) = @_;
|
||||||
|
|
||||||
|
return $self->app->dbh->prepare(
|
||||||
|
qq{
|
||||||
|
insert into users (name) values (?)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
app->attr(
|
||||||
|
checkin_query => sub {
|
||||||
|
my ($self) = @_;
|
||||||
|
|
||||||
|
return $self->app->dbh->prepare(
|
||||||
|
qq{
|
||||||
|
insert into user_actions (
|
||||||
|
user_id, action_id, station_id, action_time,
|
||||||
|
train_type, train_line, train_no, train_id,
|
||||||
|
sched_time, real_time,
|
||||||
|
route, messages
|
||||||
|
) values (
|
||||||
|
?, $action_type{checkin}, ?, ?,
|
||||||
|
?, ?, ?, ?,
|
||||||
|
?, ?,
|
||||||
|
?, ?
|
||||||
|
)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
app->attr(
|
||||||
|
checkout_query => sub {
|
||||||
|
my ($self) = @_;
|
||||||
|
|
||||||
|
return $self->app->dbh->prepare(
|
||||||
|
qq{
|
||||||
|
insert into user_actions (
|
||||||
|
user_id, action_id, station_id, action_time,
|
||||||
|
train_type, train_line, train_no, train_id,
|
||||||
|
sched_time, real_time,
|
||||||
|
route, messages
|
||||||
|
) values (
|
||||||
|
?, $action_type{checkout}, ?, ?,
|
||||||
|
?, ?, ?, ?,
|
||||||
|
?, ?,
|
||||||
|
?, ?
|
||||||
|
)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
app->attr(
|
||||||
|
dbh => sub {
|
||||||
|
my ($self) = @_;
|
||||||
|
|
||||||
|
return DBI->connect( "dbi:SQLite:dbname=${dbname}", q{}, q{} );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
app->attr(
|
||||||
|
get_all_actions_query => sub {
|
||||||
|
my ($self) = @_;
|
||||||
|
|
||||||
|
return $self->app->dbh->prepare(
|
||||||
|
qq{
|
||||||
|
select action_id, action_time, stations.ds100, stations.name,
|
||||||
|
train_type, train_line, train_no, train_id,
|
||||||
|
sched_time, real_time,
|
||||||
|
route, messages
|
||||||
|
from user_actions
|
||||||
|
join stations on station_id = stations.id
|
||||||
|
where user_id = ?
|
||||||
|
order by action_time asc
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
app->attr(
|
||||||
|
get_last_actions_query => sub {
|
||||||
|
my ($self) = @_;
|
||||||
|
|
||||||
|
return $self->app->dbh->prepare(
|
||||||
|
qq{
|
||||||
|
select action_id, action_time, stations.ds100, stations.name,
|
||||||
|
train_type, train_line, train_no, train_id, route
|
||||||
|
from user_actions
|
||||||
|
join stations on station_id = stations.id
|
||||||
|
where user_id = ?
|
||||||
|
order by action_time desc
|
||||||
|
limit 10
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
app->attr(
|
||||||
|
get_userid_query => sub {
|
||||||
|
my ($self) = @_;
|
||||||
|
|
||||||
|
return $self->app->dbh->prepare(
|
||||||
|
qq{select id from users where name = ?});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
app->attr(
|
||||||
|
get_stationid_by_ds100_query => sub {
|
||||||
|
my ($self) = @_;
|
||||||
|
|
||||||
|
return $self->app->dbh->prepare(
|
||||||
|
qq{select id from stations where ds100 = ?});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
app->attr(
|
||||||
|
get_stationid_by_name_query => sub {
|
||||||
|
my ($self) = @_;
|
||||||
|
|
||||||
|
return $self->app->dbh->prepare(
|
||||||
|
qq{select id from stations where name = ?});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
app->attr(
|
||||||
|
undo_query => sub {
|
||||||
|
my ($self) = @_;
|
||||||
|
|
||||||
|
return $self->app->dbh->prepare(
|
||||||
|
qq{
|
||||||
|
insert into user_actions (
|
||||||
|
user_id, action_id, action_time,
|
||||||
|
) values (
|
||||||
|
?, $action_type{undo}, ?
|
||||||
|
)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
sub epoch_to_dt {
|
||||||
|
my ($epoch) = @_;
|
||||||
|
|
||||||
|
return DateTime->from_epoch(
|
||||||
|
epoch => $epoch,
|
||||||
|
time_zone => 'Europe/Berlin'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get_departures {
|
||||||
|
my ( $station, $lookbehind ) = @_;
|
||||||
|
|
||||||
|
$lookbehind //= 20;
|
||||||
|
|
||||||
|
my @station_matches
|
||||||
|
= Travel::Status::DE::IRIS::Stations::get_station($station);
|
||||||
|
|
||||||
|
if ( @station_matches == 1 ) {
|
||||||
|
$station = $station_matches[0][0];
|
||||||
|
my $status = Travel::Status::DE::IRIS->new(
|
||||||
|
station => $station,
|
||||||
|
main_cache => $cache_iris_main,
|
||||||
|
realtime_cache => $cache_iris_rt,
|
||||||
|
lookbehind => 20,
|
||||||
|
datetime => DateTime->now( time_zone => 'Europe/Berlin' )
|
||||||
|
->subtract( minutes => $lookbehind ),
|
||||||
|
lookahead => $lookbehind + 20,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
results => [ $status->results ],
|
||||||
|
errstr => $status->errstr,
|
||||||
|
station_ds100 =>
|
||||||
|
( $status->station ? $status->station->{ds100} : 'undef' ),
|
||||||
|
station_name =>
|
||||||
|
( $status->station ? $status->station->{name} : 'undef' ),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
elsif ( @station_matches > 1 ) {
|
||||||
|
return {
|
||||||
|
results => [],
|
||||||
|
errstr => 'Ambiguous station name',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return {
|
||||||
|
results => [],
|
||||||
|
errstr => 'Unknown station name',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
helper 'checkin' => sub {
|
||||||
|
my ( $self, $station, $train_id ) = @_;
|
||||||
|
|
||||||
|
my $status = get_departures($station);
|
||||||
|
if ( $status->{errstr} ) {
|
||||||
|
return ( undef, $status->{errstr} );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
my ($train)
|
||||||
|
= first { $_->train_id eq $train_id } @{ $status->{results} };
|
||||||
|
if ( not defined $train ) {
|
||||||
|
return ( undef, "Train ${train_id} not found" );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
my $success = $self->app->checkin_query->execute(
|
||||||
|
$self->app->get_user_id,
|
||||||
|
$self->get_station_id(
|
||||||
|
ds100 => $status->{station_ds100},
|
||||||
|
name => $status->{station_name}
|
||||||
|
),
|
||||||
|
DateTime->now( time_zone => 'Europe/Berlin' )->epoch,
|
||||||
|
$train->type,
|
||||||
|
$train->line_no,
|
||||||
|
$train->train_no,
|
||||||
|
$train->train_id,
|
||||||
|
$train->sched_departure->epoch,
|
||||||
|
$train->departure->epoch,
|
||||||
|
join( '|', $train->route ),
|
||||||
|
join( '|',
|
||||||
|
map { ( $_->[0] ? $_->[0]->epoch : q{} ) . ':' . $_->[1] }
|
||||||
|
$train->messages )
|
||||||
|
);
|
||||||
|
if ( defined $success ) {
|
||||||
|
return ( $train, undef );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return ( undef, 'INSERT failed' );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
helper 'checkout' => sub {
|
||||||
|
my ( $self, $station, $force ) = @_;
|
||||||
|
|
||||||
|
my $status = get_departures( $station, 180 );
|
||||||
|
my $user = $self->get_user_status;
|
||||||
|
my $train_id = $user->{train_id};
|
||||||
|
|
||||||
|
if ( $status->{errstr} and not $force ) {
|
||||||
|
return $status->{errstr};
|
||||||
|
}
|
||||||
|
if ( not $user->{checked_in} ) {
|
||||||
|
return 'You are not checked into any train';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
my ($train)
|
||||||
|
= first { $_->train_id eq $train_id } @{ $status->{results} };
|
||||||
|
if ( not defined $train ) {
|
||||||
|
if ($force) {
|
||||||
|
my $success = $self->app->checkout_query->execute(
|
||||||
|
$self->app->get_user_id,
|
||||||
|
$self->get_station_id(
|
||||||
|
ds100 => $status->{station_ds100},
|
||||||
|
name => $status->{station_name}
|
||||||
|
),
|
||||||
|
DateTime->now( time_zone => 'Europe/Berlin' )->epoch,
|
||||||
|
undef, undef, undef, undef, undef,
|
||||||
|
undef, undef, undef
|
||||||
|
);
|
||||||
|
if ( defined $success ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return 'INSERT failed';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return "Train ${train_id} not found";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
my $success = $self->app->checkout_query->execute(
|
||||||
|
$self->app->get_user_id,
|
||||||
|
$self->get_station_id(
|
||||||
|
ds100 => $status->{station_ds100},
|
||||||
|
name => $status->{station_name}
|
||||||
|
),
|
||||||
|
DateTime->now( time_zone => 'Europe/Berlin' )->epoch,
|
||||||
|
$train->type,
|
||||||
|
$train->line_no,
|
||||||
|
$train->train_no,
|
||||||
|
$train->train_id,
|
||||||
|
$train->sched_departure->epoch,
|
||||||
|
$train->departure->epoch,
|
||||||
|
join( '|', $train->route ),
|
||||||
|
join( '|',
|
||||||
|
map { ( $_->[0] ? $_->[0]->epoch : q{} ) . ':' . $_->[1] }
|
||||||
|
$train->messages )
|
||||||
|
);
|
||||||
|
if ( defined $success ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return 'INSERT failed';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
helper 'get_station_id' => sub {
|
||||||
|
my ( $self, %opt ) = @_;
|
||||||
|
|
||||||
|
$self->app->get_stationid_by_ds100_query->execute( $opt{ds100} );
|
||||||
|
my $rows = $self->app->get_stationid_by_ds100_query->fetchall_arrayref;
|
||||||
|
if ( @{$rows} ) {
|
||||||
|
return $rows->[0][0];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$self->app->add_station_query->execute( $opt{ds100}, $opt{name} );
|
||||||
|
$self->app->get_stationid_by_ds100_query->execute( $opt{ds100} );
|
||||||
|
my $rows = $self->app->get_stationid_by_ds100_query->fetchall_arrayref;
|
||||||
|
return $rows->[0][0];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
helper 'get_user_name' => sub {
|
||||||
|
my ($self) = @_;
|
||||||
|
|
||||||
|
my $user = $self->req->headers->header('X-Remote-User') // 'dev';
|
||||||
|
|
||||||
|
return $user;
|
||||||
|
};
|
||||||
|
|
||||||
|
helper 'get_user_id' => sub {
|
||||||
|
my ( $self, $user_name ) = @_;
|
||||||
|
|
||||||
|
$user_name //= $self->get_user_name;
|
||||||
|
|
||||||
|
if ( not -e $dbname ) {
|
||||||
|
$self->app->dbh->do(
|
||||||
|
qq{
|
||||||
|
create table users (
|
||||||
|
id integer primary key,
|
||||||
|
name char(64) not null unique
|
||||||
|
)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
$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->get_userid_query->execute($user_name);
|
||||||
|
my $rows = $self->app->get_userid_query->fetchall_arrayref;
|
||||||
|
|
||||||
|
if ( @{$rows} ) {
|
||||||
|
return $rows->[0][0];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$self->app->add_user_query->execute($user_name);
|
||||||
|
$self->app->get_userid_query->execute($user_name);
|
||||||
|
my $rows = $self->app->get_userid_query->fetchall_arrayref;
|
||||||
|
return $rows->[0][0];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
helper 'get_user_travels' => sub {
|
||||||
|
my ($self) = @_;
|
||||||
|
|
||||||
|
my $uid = $self->get_user_id( $self->get_user_name );
|
||||||
|
$self->app->get_all_actions_query->execute($uid);
|
||||||
|
|
||||||
|
my @travels;
|
||||||
|
|
||||||
|
while ( my @row = $self->app->get_all_actions_query->fetchrow_array ) {
|
||||||
|
my (
|
||||||
|
$action, $raw_ts, $ds100, $name,
|
||||||
|
$train_type, $train_line, $train_no, $train_id,
|
||||||
|
$raw_sched_ts, $raw_real_ts, $raw_route, $raw_messages
|
||||||
|
) = @row;
|
||||||
|
|
||||||
|
if ( $action == $action_type{checkin} ) {
|
||||||
|
push(
|
||||||
|
@travels,
|
||||||
|
{
|
||||||
|
from_name => $name,
|
||||||
|
sched_departure => epoch_to_dt($raw_sched_ts),
|
||||||
|
rt_departure => epoch_to_dt($raw_real_ts),
|
||||||
|
type => $train_type,
|
||||||
|
line => $train_line,
|
||||||
|
no => $train_no,
|
||||||
|
messages => [ split( qr{|}, $raw_messages ) ],
|
||||||
|
completed => 0,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
elsif ( $action == $action_type{checkout} ) {
|
||||||
|
my $ref = $travels[-1];
|
||||||
|
$ref->{to_name} = $name;
|
||||||
|
$ref->{completed} = 1;
|
||||||
|
|
||||||
|
# if train_no is undef, we have a forced checkout without data
|
||||||
|
if ($train_no) {
|
||||||
|
$ref->{sched_arrival} = epoch_to_dt($raw_sched_ts),
|
||||||
|
$ref->{rt_arrival} = epoch_to_dt($raw_real_ts),
|
||||||
|
$ref->{messages} = [ split( qr{|}, $raw_messages ) ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@travels = reverse @travels;
|
||||||
|
|
||||||
|
return @travels;
|
||||||
|
};
|
||||||
|
|
||||||
|
helper 'get_user_status' => sub {
|
||||||
|
my ($self) = @_;
|
||||||
|
|
||||||
|
my $uid = $self->get_user_id( $self->get_user_name );
|
||||||
|
$self->app->get_last_actions_query->execute($uid);
|
||||||
|
my $rows = $self->app->get_last_actions_query->fetchall_arrayref;
|
||||||
|
|
||||||
|
if ( @{$rows} ) {
|
||||||
|
my $now = DateTime->now( time_zone => 'Europe/Berlin' );
|
||||||
|
my $ts = DateTime->from_epoch(
|
||||||
|
epoch => $rows->[0][1],
|
||||||
|
time_zone => 'Europe/Berlin'
|
||||||
|
);
|
||||||
|
my $checkin_station_name = $rows->[0][3];
|
||||||
|
my @route = split( qr{[|]}, $rows->[0][8] // q{} );
|
||||||
|
my @route_after;
|
||||||
|
my $is_after = 0;
|
||||||
|
for my $station (@route) {
|
||||||
|
if ( $station eq $checkin_station_name ) {
|
||||||
|
$is_after = 1;
|
||||||
|
}
|
||||||
|
if ($is_after) {
|
||||||
|
push( @route_after, $station );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
checked_in => ( $rows->[0][0] == $action_type{checkin} ),
|
||||||
|
timestamp => $ts,
|
||||||
|
timestamp_delta => $now->subtract_datetime($ts),
|
||||||
|
station_ds100 => $rows->[0][2],
|
||||||
|
station_name => $rows->[0][3],
|
||||||
|
train_type => $rows->[0][4],
|
||||||
|
train_line => $rows->[0][5],
|
||||||
|
train_no => $rows->[0][6],
|
||||||
|
train_id => $rows->[0][7],
|
||||||
|
route => \@route,
|
||||||
|
route_after => \@route_after,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
checked_in => 0,
|
||||||
|
timestamp => 0
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
helper 'navbar_class' => sub {
|
||||||
|
my ( $self, $path ) = @_;
|
||||||
|
|
||||||
|
if ( $self->req->url eq $self->url_for($path) ) {
|
||||||
|
return 'active';
|
||||||
|
}
|
||||||
|
return q{};
|
||||||
|
};
|
||||||
|
|
||||||
|
get '/' => sub {
|
||||||
|
my ($self) = @_;
|
||||||
|
$self->render('landingpage');
|
||||||
|
};
|
||||||
|
|
||||||
|
get '/a/checkin' => sub {
|
||||||
|
my ($self) = @_;
|
||||||
|
my $station = $self->param('station');
|
||||||
|
my $train_id = $self->param('train');
|
||||||
|
|
||||||
|
my ( $train, $error ) = $self->checkin( $station, $train_id );
|
||||||
|
|
||||||
|
if ($error) {
|
||||||
|
$self->render(
|
||||||
|
'checkin',
|
||||||
|
error => $error,
|
||||||
|
train => undef
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$self->render(
|
||||||
|
'checkin',
|
||||||
|
error => undef,
|
||||||
|
train => $train
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
get '/a/checkout' => sub {
|
||||||
|
my ($self) = @_;
|
||||||
|
my $station = $self->param('station');
|
||||||
|
my $force = $self->param('force');
|
||||||
|
|
||||||
|
my $error = $self->checkout( $station, $force );
|
||||||
|
|
||||||
|
if ($error) {
|
||||||
|
$self->render( 'checkout', error => $error );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$self->redirect_to("/${station}");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
get '/*station' => sub {
|
||||||
|
my ($self) = @_;
|
||||||
|
my $station = $self->stash('station');
|
||||||
|
|
||||||
|
my $status = get_departures($station);
|
||||||
|
|
||||||
|
if ( $status->{errstr} ) {
|
||||||
|
$self->render( 'landingpage', error => $status->{errstr} );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
my @results = sort { $a->line cmp $b->line } @{ $status->{results} };
|
||||||
|
|
||||||
|
# You can't check into a train which terminates here
|
||||||
|
@results = grep { $_->departure } @results;
|
||||||
|
$self->render(
|
||||||
|
'departures',
|
||||||
|
ds100 => $status->{station_ds100},
|
||||||
|
results => \@results,
|
||||||
|
station => $status->{station_name}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
app->defaults( layout => 'default' );
|
||||||
|
|
||||||
|
app->config(
|
||||||
|
hypnotoad => {
|
||||||
|
accepts => 10,
|
||||||
|
listen => [ $ENV{DBFAKEDISPLAY_LISTEN} // 'http://*:8092' ],
|
||||||
|
pid_file => '/tmp/db-fakedisplay.pid',
|
||||||
|
workers => $ENV{DBFAKEDISPLAY_WORKERS} // 2,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
app->start;
|
0
public/static/css/local.css
Normal file
0
public/static/css/local.css
Normal file
38
public/static/css/material-icons.css
Normal file
38
public/static/css/material-icons.css
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Material Icons';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url(/static/fonts/MaterialIcons-Regular.eot); /* For IE6-8 */
|
||||||
|
src: local('Material Icons'),
|
||||||
|
local('MaterialIcons-Regular'),
|
||||||
|
url(/static/fonts/MaterialIcons-Regular.woff2) format('woff2'),
|
||||||
|
url(/static/fonts/MaterialIcons-Regular.woff) format('woff'),
|
||||||
|
url(/static/fonts/MaterialIcons-Regular.ttf) format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
.material-icons {
|
||||||
|
font-family: 'Material Icons';
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 24px; /* Preferred icon size */
|
||||||
|
display: inline-block;
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
line-height: 1;
|
||||||
|
text-transform: none;
|
||||||
|
letter-spacing: normal;
|
||||||
|
word-wrap: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
direction: ltr;
|
||||||
|
|
||||||
|
/* Support for all WebKit browsers. */
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
/* Support for Safari and Chrome. */
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
|
||||||
|
/* Support for Firefox. */
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
|
||||||
|
/* Support for IE. */
|
||||||
|
font-feature-settings: 'liga';
|
||||||
|
}
|
16
public/static/css/materialize.min.css
vendored
Normal file
16
public/static/css/materialize.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
public/static/fonts/MaterialIcons-Regular.eot
Normal file
BIN
public/static/fonts/MaterialIcons-Regular.eot
Normal file
Binary file not shown.
1
public/static/fonts/MaterialIcons-Regular.ijmap
Normal file
1
public/static/fonts/MaterialIcons-Regular.ijmap
Normal file
File diff suppressed because one or more lines are too long
2373
public/static/fonts/MaterialIcons-Regular.svg
Normal file
2373
public/static/fonts/MaterialIcons-Regular.svg
Normal file
File diff suppressed because it is too large
Load diff
After Width: | Height: | Size: 275 KiB |
BIN
public/static/fonts/MaterialIcons-Regular.ttf
Normal file
BIN
public/static/fonts/MaterialIcons-Regular.ttf
Normal file
Binary file not shown.
BIN
public/static/fonts/MaterialIcons-Regular.woff
Normal file
BIN
public/static/fonts/MaterialIcons-Regular.woff
Normal file
Binary file not shown.
BIN
public/static/fonts/MaterialIcons-Regular.woff2
Normal file
BIN
public/static/fonts/MaterialIcons-Regular.woff2
Normal file
Binary file not shown.
BIN
public/static/fonts/roboto/Roboto-Bold.eot
Normal file
BIN
public/static/fonts/roboto/Roboto-Bold.eot
Normal file
Binary file not shown.
BIN
public/static/fonts/roboto/Roboto-Bold.ttf
Normal file
BIN
public/static/fonts/roboto/Roboto-Bold.ttf
Normal file
Binary file not shown.
BIN
public/static/fonts/roboto/Roboto-Bold.woff
Normal file
BIN
public/static/fonts/roboto/Roboto-Bold.woff
Normal file
Binary file not shown.
BIN
public/static/fonts/roboto/Roboto-Bold.woff2
Normal file
BIN
public/static/fonts/roboto/Roboto-Bold.woff2
Normal file
Binary file not shown.
BIN
public/static/fonts/roboto/Roboto-Light.eot
Normal file
BIN
public/static/fonts/roboto/Roboto-Light.eot
Normal file
Binary file not shown.
BIN
public/static/fonts/roboto/Roboto-Light.ttf
Normal file
BIN
public/static/fonts/roboto/Roboto-Light.ttf
Normal file
Binary file not shown.
BIN
public/static/fonts/roboto/Roboto-Light.woff
Normal file
BIN
public/static/fonts/roboto/Roboto-Light.woff
Normal file
Binary file not shown.
BIN
public/static/fonts/roboto/Roboto-Light.woff2
Normal file
BIN
public/static/fonts/roboto/Roboto-Light.woff2
Normal file
Binary file not shown.
BIN
public/static/fonts/roboto/Roboto-Medium.eot
Normal file
BIN
public/static/fonts/roboto/Roboto-Medium.eot
Normal file
Binary file not shown.
BIN
public/static/fonts/roboto/Roboto-Medium.ttf
Normal file
BIN
public/static/fonts/roboto/Roboto-Medium.ttf
Normal file
Binary file not shown.
BIN
public/static/fonts/roboto/Roboto-Medium.woff
Normal file
BIN
public/static/fonts/roboto/Roboto-Medium.woff
Normal file
Binary file not shown.
BIN
public/static/fonts/roboto/Roboto-Medium.woff2
Normal file
BIN
public/static/fonts/roboto/Roboto-Medium.woff2
Normal file
Binary file not shown.
BIN
public/static/fonts/roboto/Roboto-Regular.eot
Normal file
BIN
public/static/fonts/roboto/Roboto-Regular.eot
Normal file
Binary file not shown.
BIN
public/static/fonts/roboto/Roboto-Regular.ttf
Normal file
BIN
public/static/fonts/roboto/Roboto-Regular.ttf
Normal file
Binary file not shown.
BIN
public/static/fonts/roboto/Roboto-Regular.woff
Normal file
BIN
public/static/fonts/roboto/Roboto-Regular.woff
Normal file
Binary file not shown.
BIN
public/static/fonts/roboto/Roboto-Regular.woff2
Normal file
BIN
public/static/fonts/roboto/Roboto-Regular.woff2
Normal file
Binary file not shown.
BIN
public/static/fonts/roboto/Roboto-Thin.eot
Normal file
BIN
public/static/fonts/roboto/Roboto-Thin.eot
Normal file
Binary file not shown.
BIN
public/static/fonts/roboto/Roboto-Thin.ttf
Normal file
BIN
public/static/fonts/roboto/Roboto-Thin.ttf
Normal file
Binary file not shown.
BIN
public/static/fonts/roboto/Roboto-Thin.woff
Normal file
BIN
public/static/fonts/roboto/Roboto-Thin.woff
Normal file
Binary file not shown.
BIN
public/static/fonts/roboto/Roboto-Thin.woff2
Normal file
BIN
public/static/fonts/roboto/Roboto-Thin.woff2
Normal file
Binary file not shown.
4
public/static/js/jquery-2.2.4.min.js
vendored
Normal file
4
public/static/js/jquery-2.2.4.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
10
public/static/js/materialize.min.js
vendored
Normal file
10
public/static/js/materialize.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
47
templates/checkin.html.ep
Normal file
47
templates/checkin.html.ep
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
% if ($error) {
|
||||||
|
<div class="row">
|
||||||
|
<div class="col s12 m6">
|
||||||
|
<div class="card red darken-4">
|
||||||
|
<div class="card-content white-text">
|
||||||
|
<span class="card-title">I am Error</span>
|
||||||
|
<p><%= $error %></p>
|
||||||
|
</div>
|
||||||
|
<div class="card-action">
|
||||||
|
% if (param('station')) {
|
||||||
|
<a href="/<%= param('station') %>">Zurück zu den Abfahrten</a>
|
||||||
|
% }
|
||||||
|
% else {
|
||||||
|
<a href="/">Zur Hauptseite</a>
|
||||||
|
% }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
% }
|
||||||
|
% else {
|
||||||
|
<div class="row">
|
||||||
|
<div class="col s12">
|
||||||
|
<div class="card green darken-4">
|
||||||
|
<div class="card-content white-text">
|
||||||
|
<span class="card-title">Eingecheckt in <%= $train->line %></span>
|
||||||
|
<p>Abfahrt um <%= $train->sched_departure->strftime('%H:%M') %>
|
||||||
|
% if ($train->departure_delay) {
|
||||||
|
+<%= $train->departure_delay %>
|
||||||
|
% }
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col s12">
|
||||||
|
Weitere Route:
|
||||||
|
<ul>
|
||||||
|
% for my $station ($train->route_post) {
|
||||||
|
<li><%= $station %>
|
||||||
|
(<a href="/a/checkout?station=<%= $station %>">hier auschecken</a>)
|
||||||
|
</li>
|
||||||
|
% }
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
% }
|
31
templates/checkout.html.ep
Normal file
31
templates/checkout.html.ep
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
% if ($error) {
|
||||||
|
<div class="row">
|
||||||
|
<div class="col s12 m6">
|
||||||
|
<div class="card red darken-4">
|
||||||
|
<div class="card-content white-text">
|
||||||
|
<span class="card-title">I am Error</span>
|
||||||
|
<p><%= $error %></p>
|
||||||
|
</div>
|
||||||
|
<div class="card-action">
|
||||||
|
<a href="/a/checkout?station=<%= param('station') %>&force=1">Ohne Echtzeitdaten auschecken</a>
|
||||||
|
% if (param('station')) {
|
||||||
|
<a href="/<%= param('station') %>">Zurück zu den Abfahrten</a>
|
||||||
|
% }
|
||||||
|
% else {
|
||||||
|
<a href="/">Zur Hauptseite</a>
|
||||||
|
% }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
% }
|
||||||
|
% else {
|
||||||
|
<div class="row">
|
||||||
|
<div class="col s12">
|
||||||
|
<div class="card green darken-4">
|
||||||
|
<div class="card-content white-text">
|
||||||
|
<span class="card-title">Erfolgreich ausgecheckt</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
% }
|
49
templates/departures.html.ep
Normal file
49
templates/departures.html.ep
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
<div class="row">
|
||||||
|
<div class="col s12">
|
||||||
|
% my $status = $self->get_user_status;
|
||||||
|
% if ($status->{checked_in}) {
|
||||||
|
<div class="card grey darken-4">
|
||||||
|
<div class="card-content white-text">
|
||||||
|
<span class="card-title">Aktuell eingecheckt</span>
|
||||||
|
<p>In <%= $status->{train_type} %> <%= $status->{train_no} %>
|
||||||
|
ab <%= $status->{station_name} %></p>
|
||||||
|
</div>
|
||||||
|
<div class="card-action">
|
||||||
|
<a href="/a/checkout?station=<%= $ds100 %>">Hier auschecken</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
% }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<table class="striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Zug</th>
|
||||||
|
<th></th>
|
||||||
|
<th>Abfahrt</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
% for my $result (@{$results}) {
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="/a/checkin?station=<%= $ds100 %>&train=<%= $result->train_id %>" title="Check In">
|
||||||
|
<%= $result->line %>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="/a/checkin?station=<%= $ds100 %>&train=<%= $result->train_id %>" title="Check In">
|
||||||
|
<%= $result->destination %>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td><%= $result->departure->strftime('%H:%M') %>
|
||||||
|
% if ($result->departure_delay) {
|
||||||
|
(+<%= $result->departure_delay %>)
|
||||||
|
% }
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
% }
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
61
templates/landingpage.html.ep
Normal file
61
templates/landingpage.html.ep
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
<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} %>.</p>
|
||||||
|
<p>Auschecken?
|
||||||
|
<ul>
|
||||||
|
% my $is_after = 0;
|
||||||
|
% for my $station (@{$status->{route_after}}) {
|
||||||
|
<li><a href="/a/checkout?station=<%= $station %>"><%= $station %></a></li>
|
||||||
|
% }
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
</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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
% }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h1>Bisherige 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()) {
|
||||||
|
% 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} and $travel->{rt_departure}) {
|
||||||
|
<td><%= ($travel->{rt_arrival}->epoch - $travel->{rt_departure}->epoch) / 60 %> min</td>
|
||||||
|
% }
|
||||||
|
% else {
|
||||||
|
<td>?</td>
|
||||||
|
% }
|
||||||
|
</tr>
|
||||||
|
% }
|
||||||
|
% }
|
||||||
|
</tbody>
|
||||||
|
</tabel>
|
||||||
|
</div>
|
29
templates/layouts/default.html.ep
Normal file
29
templates/layouts/default.html.ep
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<title><%= stash('title') // 'travelynx' %></title>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
%= 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'
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<nav class="blue">
|
||||||
|
<div class="nav-wrapper container">
|
||||||
|
<a href="/" class="brand-logo left">travelynx</a>
|
||||||
|
<ul id="nav-mobile" class="right">
|
||||||
|
<li class="<%= navbar_class('/') %>"><a href='/' title="Einchecken"><i class="material-icons">Check In</i></a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
%= content
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in a new issue