diff options
Diffstat (limited to 'lib')
-rwxr-xr-x | lib/Travelynx.pm | 113 | ||||
-rw-r--r-- | lib/Travelynx/Command/database.pm | 25 | ||||
-rw-r--r-- | lib/Travelynx/Controller/Account.pm | 25 |
3 files changed, 163 insertions, 0 deletions
diff --git a/lib/Travelynx.pm b/lib/Travelynx.pm index 48fb22b..f058eee 100755 --- a/lib/Travelynx.pm +++ b/lib/Travelynx.pm @@ -347,6 +347,7 @@ sub startup { "Checkin($uid): INSERT failed: $@"); return ( undef, 'INSERT failed: ' . $@ ); } + $self->run_hook( $self->current_user->{id}, 'checkin' ); return ( $train, undef ); } } @@ -366,6 +367,7 @@ sub startup { $self->app->log->error("Undo($uid, $journey_id): $@"); return "Undo($journey_id): $@"; } + $self->run_hook( $uid, 'undo' ); return undef; } if ( $journey_id !~ m{ ^ \d+ $ }x ) { @@ -421,6 +423,7 @@ sub startup { $self->app->log->error("Undo($uid, $journey_id): $@"); return "Undo($journey_id): $@"; } + $self->run_hook( $uid, 'undo' ); return undef; } ); @@ -572,6 +575,7 @@ sub startup { if ( $has_arrived or $force ) { return ( 0, undef ); + $self->run_hook( $uid, 'checkout' ); } return ( 1, undef ); } @@ -985,6 +989,113 @@ sub startup { ); $self->helper( + 'get_webhook' => sub { + my ( $self, $uid ) = @_; + $uid //= $self->current_user->{id}; + + my $res_h + = $self->pg->db->select( 'webhooks_str', '*', + { user_id => $uid } )->hash; + + $res_h->{latest_run} = epoch_to_dt( $res_h->{latest_run_ts} ); + + return $res_h; + } + ); + + $self->helper( + 'set_webhook' => sub { + my ( $self, %opt ) = @_; + + $opt{uid} //= $self->current_user->{id}; + + my $res = $self->pg->db->insert( + 'webhooks', + { + user_id => $opt{uid}, + enabled => $opt{enabled}, + url => $opt{url}, + token => $opt{token} + }, + { + on_conflict => \ +'(user_id) do update set enabled = EXCLUDED.enabled, url = EXCLUDED.url, token = EXCLUDED.token, errored = null, latest_run = null, output = null' + } + ); + } + ); + + $self->helper( + 'mark_hook_status' => sub { + my ( $self, $uid, $url, $success, $text ) = @_; + + if ( length($text) > 1024 ) { + $text = "(output too long)"; + } + + $self->pg->db->update( + 'webhooks', + { + errored => !$success, + latest_run => DateTime->now( time_zone => 'Europe/Berlin' ), + output => $text, + }, + { + user_id => $uid, + url => $url + } + ); + } + ); + + $self->helper( + 'run_hook' => sub { + my ( $self, $uid, $reason ) = @_; + + my $hook = $self->get_webhook($uid); + + if ( not $hook->{enabled} or not $hook->{url} =~ m{^ https?:// }x ) + { + return; + } + + my $status = { todo => 1 }; + my $header = {}; + my $hook_body = { + reason => $reason, + status => $status, + }; + + if ( $hook->{token} ) { + $hook->{token} =~ tr{\r\n}{}d; + $header->{Authorization} = "Bearer $hook->{token}"; + } + + my $ua = $self->ua; + $ua->request_timeout(10); + + $ua->post_p( $hook->{url} => $header => json => $hook_body )->then( + sub { + my ($tx) = @_; + if ( my $err = $tx->error ) { + $self->mark_hook_status( $uid, $hook->{url}, 0, + "HTTP $err->{code} $err->{message}" ); + } + else { + $self->mark_hook_status( $uid, $hook->{url}, 1, + $tx->result->body ); + } + } + )->catch( + sub { + my ($err) = @_; + $self->mark_hook_status( $uid, $hook->{url}, 0, $err ); + } + )->wait; + } + ); + + $self->helper( 'get_user_password' => sub { my ( $self, $name ) = @_; @@ -1753,6 +1864,7 @@ sub startup { $authed_r->get('/account')->to('account#account'); $authed_r->get('/account/privacy')->to('account#privacy'); + $authed_r->get('/account/hooks')->to('account#webhook'); $authed_r->get('/ajax/status_card.html')->to('traveling#status_card'); $authed_r->get('/cancelled')->to('traveling#cancelled'); $authed_r->get('/account/password')->to('account#password_form'); @@ -1767,6 +1879,7 @@ sub startup { $authed_r->get('/s/*station')->to('traveling#station'); $authed_r->get('/confirm_mail/:token')->to('account#confirm_mail'); $authed_r->post('/account/privacy')->to('account#privacy'); + $authed_r->post('/account/hooks')->to('account#webhook'); $authed_r->post('/journey/add')->to('traveling#add_journey_form'); $authed_r->post('/journey/edit')->to('traveling#edit_journey'); $authed_r->post('/account/password')->to('account#change_password'); diff --git a/lib/Travelynx/Command/database.pm b/lib/Travelynx/Command/database.pm index 79ff086..11a946e 100644 --- a/lib/Travelynx/Command/database.pm +++ b/lib/Travelynx/Command/database.pm @@ -456,6 +456,31 @@ my @migrations = ( } ); }, + + # v10 -> v11 + sub { + my ($db) = @_; + $db->query( + qq{ + create table webhooks ( + user_id integer not null references users (id) primary key, + enabled boolean not null, + url varchar(1000) not null, + token varchar(250), + errored boolean, + latest_run timestamptz, + output text + ); + comment on table webhooks is 'URLs and bearer tokens for push events'; + create view webhooks_str as select + user_id, enabled, url, token, errored, output, + extract(epoch from latest_run) as latest_run_ts + from webhooks + ; + update schema_version set version = 11; + } + ); + }, ); sub setup_db { diff --git a/lib/Travelynx/Controller/Account.pm b/lib/Travelynx/Controller/Account.pm index 8d5b21f..75b8f02 100644 --- a/lib/Travelynx/Controller/Account.pm +++ b/lib/Travelynx/Controller/Account.pm @@ -230,6 +230,31 @@ sub privacy { } } +sub webhook { + my ($self) = @_; + + my $hook = $self->get_webhook; + + if ( $self->param('action') and $self->param('action') eq 'save' ) { + $hook->{url} = $self->param('url'); + $hook->{token} = $self->param('token'); + $hook->{enabled} = $self->param('enabled') // 0; + $self->set_webhook( + url => $hook->{url}, + token => $hook->{token}, + enabled => $hook->{enabled} + ); + $hook = $self->get_webhook; + } + else { + $self->param( url => $hook->{url} ); + $self->param( token => $hook->{token} ); + $self->param( enabled => $hook->{enabled} ); + } + + $self->render( 'webhooks', hook => $hook ); +} + sub change_mail { my ($self) = @_; |