WiP webhook support
This commit is contained in:
parent
55581d1f25
commit
b36ba45aef
4 changed files with 225 additions and 0 deletions
113
lib/Travelynx.pm
113
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 );
|
||||
}
|
||||
|
@ -984,6 +988,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');
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) = @_;
|
||||
|
||||
|
|
62
templates/webhooks.html.ep
Normal file
62
templates/webhooks.html.ep
Normal file
|
@ -0,0 +1,62 @@
|
|||
% if (my $invalid = stash('invalid')) {
|
||||
%= include '_invalid_input', invalid => $invalid
|
||||
% }
|
||||
|
||||
<h1>Web Hooks</h1>
|
||||
|
||||
<!-- -H "Authorization: Bearer ${TOKEN}" -->
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
<p>
|
||||
Die im Web Hook konfigurierte URL wird bei jedem Checkin und Checkout
|
||||
des ausgewählten Zuges aufgerufen. Falls ein Token eingetragen
|
||||
ist, wird er als Bearer Token verwendet.
|
||||
</p>
|
||||
<p>
|
||||
Events werden als JSON POST übertragen. Das JSON-Dokument besteht aus
|
||||
zwei Feldern: „reason“ gibt den Grund des API-Aufrufs an (checkin,
|
||||
checkout, undo), „status“ den <a href="/api">aktuellen Status</a>.
|
||||
</p>
|
||||
</div>
|
||||
%= form_for '/account/hooks' => (method => 'POST') => begin
|
||||
%= csrf_field
|
||||
<div class="col s12 center-align">
|
||||
<label>
|
||||
%= check_box enabled => 1
|
||||
<span>Aktiv</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="input-field col s12">
|
||||
<i class="material-icons prefix">link</i>
|
||||
%= text_field 'url', id => 'url', class => 'validate', maxlength => 1000
|
||||
<label for="url">URL</label>
|
||||
</div>
|
||||
<div class="input-field col s12">
|
||||
<i class="material-icons prefix">lock</i>
|
||||
%= text_field 'token', id => 'token', class => 'validate', maxlength => 250
|
||||
<label for="token">Token</label>
|
||||
</div>
|
||||
<div class="col s12">
|
||||
% if ($hook->{latest_run}->epoch) {
|
||||
Zuletzt ausgeführt: <%= $hook->{latest_run} %><br/>
|
||||
% if ($hook->{errored}) {
|
||||
<i class="material-icons left">error</i>
|
||||
Status: <%= $hook->{output} %>
|
||||
% }
|
||||
% else {
|
||||
<i class="material-icons left">check</i>
|
||||
Server-Antwort: <%= $hook->{output} %>
|
||||
% }
|
||||
% }
|
||||
% else {
|
||||
Noch nicht ausgeführt.
|
||||
% }
|
||||
</div>
|
||||
<div class="col s12 center-align">
|
||||
<button class="btn waves-effect waves-light" type="submit" name="action" value="save">
|
||||
Speichern
|
||||
<i class="material-icons right">send</i>
|
||||
</button>
|
||||
</div>
|
||||
%= end
|
||||
</div>
|
Loading…
Reference in a new issue