Created
July 10, 2025 09:47
-
-
Save ledger123/fcbe2a5abdd29741a0b68bef0c360d86 to your computer and use it in GitHub Desktop.
Revolut integration with SQL-Ledger
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env perl | |
| use Mojolicious::Lite -signatures; | |
| use Mojo::UserAgent; | |
| use Crypt::X509; | |
| use Crypt::JWT ':all'; | |
| use Crypt::PK::RSA; | |
| use JSON::XS; | |
| use HTML::Table; | |
| use Data::Format::Pretty::JSON qw(format_pretty); | |
| use CGI::FormBuilder; | |
| use DBI; | |
| use DBD::Pg; | |
| use DBIx::Simple; | |
| use Number::Format; | |
| $Data::Dumper::Indent = 1; | |
| # TODO populate revolut_accounts table on setup/initialization only. | |
| helper dbs => sub { | |
| my ( $c, $dbname ) = @_; | |
| state $dbs; | |
| if ($dbs) { | |
| return $dbs; | |
| } | |
| my $dbh = DBI->connect( "dbi:Pg:dbname=$dbname", 'sql-ledger', '' ) or die $DBI::errstr; | |
| $dbs = DBIx::Simple->connect($dbh); | |
| unless ( $dbs->query('SELECT 1 FROM revolut_accounts LIMIT 1')->list ) { | |
| $dbs->query( | |
| q{ | |
| CREATE TABLE revolut_accounts ( | |
| id text, | |
| curr character varying(3), | |
| name text, | |
| balance numeric(12,2) | |
| ) | |
| } | |
| ); | |
| } | |
| unless ( $dbs->query('SELECT transjson FROM gl LIMIT 1')->list ) { | |
| $dbs->query(q{ALTER TABLE gl ADD COLUMN transjson JSON}); | |
| } | |
| unless ( $dbs->query('SELECT transjson FROM gl_log LIMIT 1')->list ) { | |
| $dbs->query(q{ALTER TABLE gl_log ADD COLUMN transjson JSON}); | |
| } | |
| unless ( $dbs->query('SELECT transjson FROM gl_log_deleted LIMIT 1')->list ) { | |
| $dbs->query(q{ALTER TABLE gl_log_deleted ADD COLUMN transjson JSON}); | |
| } | |
| return $dbs; | |
| }; | |
| helper nf => sub { | |
| return my $nf = new Number::Format( -int_curr_symbol => '' ); | |
| }; | |
| sub _refresh_session { | |
| my ( $c, $dbname, $defaults ) = @_; | |
| my $dbs = $c->dbs( $c->session->{myconfig}->{dbname} ); | |
| my %defaults = $dbs->query("SELECT fldname, fldvalue FROM defaults")->map; | |
| my $ua = Mojo::UserAgent->new; | |
| my $apicall = "$defaults{revolut_api_url}/auth/token"; | |
| my $res; | |
| $res = $ua->post( | |
| $apicall => form => { | |
| grant_type => 'refresh_token', | |
| client_id => $defaults->{revolut_client_id}, | |
| refresh_token => $defaults->{revolut_refresh_token}, | |
| client_assertion_type => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', | |
| client_assertion => $defaults->{revolut_jwt_token}, | |
| } | |
| )->result; | |
| my $code = $res->code; | |
| my $body = $res->body; | |
| my $hash = decode_json($body); | |
| $c->session->{access_token} = $hash->{access_token}; | |
| $c->session->{dbname} = $dbname; | |
| } | |
| get '/access/:dbname' => sub ($c) { | |
| my $params = $c->req->params->to_hash; | |
| my $dbname = $c->param('dbname'); | |
| my $dbs = $c->dbs($dbname); | |
| my %defaults = $dbs->query("SELECT fldname, fldvalue FROM defaults")->map; | |
| my $jwt_header = q|{"alg": "RS256", "typ": "JWT"}|; | |
| my $payload = { | |
| iss => $defaults{revolut_jwt_domain}, | |
| sub => $defaults{revolut_client_id}, | |
| aud => "https://revolut.com", | |
| exp => time + ( 90 * 24 * 60 * 60 ), | |
| }; | |
| my $jwt_token = encode_jwt( payload => $payload, alg => 'RS256', key => \$defaults{revolut_private_key} ); | |
| my $ua = Mojo::UserAgent->new; | |
| my $apicall = "$defaults{revolut_api_url}/auth/token"; | |
| my $msg; | |
| my $res; | |
| if ( $params->{code} ) { | |
| $res = $ua->post( | |
| $apicall => form => { | |
| grant_type => 'authorization_code', | |
| code => $params->{code}, | |
| client_id => $defaults{revolut_client_id}, | |
| client_assertion_type => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', | |
| client_assertion => $jwt_token | |
| } | |
| )->result; | |
| } | |
| my $code = $res->code; | |
| my $body = $res->body; | |
| my $hash = decode_json($body); | |
| if ( $code eq '200' ) { | |
| $c->session( expiration => 86400 ); | |
| $c->session->{jwt_token} = $jwt_token; | |
| $c->session->{access_token} = $hash->{access_token}; | |
| $c->session->{refresh_token} = $hash->{refresh_token}; | |
| $c->session->{dbname} = $dbname; | |
| $dbs->query("DELETE FROM defaults WHERE fldname IN ('revolut_access_token', 'revolut_refresh_token', 'revolut_jwt_token')"); | |
| $dbs->query( "INSERT INTO defaults (fldname, fldvalue) VALUES (?, ?)", 'revolut_jwt_token', $jwt_token ); | |
| $dbs->query( "INSERT INTO defaults (fldname, fldvalue) VALUES (?, ?)", 'revolut_access_token', $hash->{access_token} ); | |
| $dbs->query( "INSERT INTO defaults (fldname, fldvalue) VALUES (?, ?)", 'revolut_refresh_token', $hash->{refresh_token} ); | |
| $msg = "$code: Access granted."; | |
| } else { | |
| $msg = "$code: $hash->{error_description}."; | |
| } | |
| $c->render( template => 'index', msg => $msg, dbname => $dbname, defaults => \%defaults ); | |
| }; | |
| get 'accounts' => sub ($c) { | |
| our %myconfig; | |
| my $params = $c->req->params->to_hash; | |
| my $login = $params->{login}; | |
| eval { require "./users/$login.conf" }; | |
| if ($@) { die "cannot load user config for $login: $@" } | |
| $c->session->{myconfig} = \%myconfig; | |
| $c->session->{myconfig}->{login} = $login; | |
| my $dbs = $c->dbs( $c->session->{myconfig}->{dbname} ); | |
| my %defaults = $dbs->query("SELECT fldname, fldvalue FROM defaults")->map; | |
| my $ua = Mojo::UserAgent->new; | |
| my $access_token = $c->session->{access_token}; | |
| my $apicall = "$defaults{revolut_api_url}/accounts"; | |
| my $res = $ua->get( $apicall => { "Authorization" => "Bearer $access_token" } )->result; | |
| if ( $res->is_error ) { | |
| &_refresh_session( $c, $c->session->{myconfig}->{dbname}, \%defaults ); | |
| $access_token = $c->session->{access_token}; | |
| $res = $ua->get( $apicall => { "Authorization" => "Bearer $access_token" } )->result; | |
| } | |
| my $hash = $res->json; | |
| my $table_data = HTML::Table->new( -head => [qw/transactions currency name balance state public/], ); | |
| $table_data->setRowClass( 1, 'listheading' ); | |
| # $dbs->query("DELETE FROM revolut_accounts"); | |
| for my $item ( @{$hash} ) { | |
| if ( $item->{balance} ) { | |
| my $rownum = $table_data->addRow( | |
| "<a href=" . $c->url_for('/transactions')->query( account => $item->{id} ) . ">Transactions</a>", | |
| $item->{currency}, $item->{name}, $c->nf->format_price( $item->{balance}, 2 ), | |
| $item->{state}, $item->{public}, | |
| ); | |
| $table_data->setRowClass( $rownum, 'listrow0' ); | |
| # $dbs->query( "INSERT INTO revolut_accounts (id, curr, name, balance) VALUES (?,?,?,?)", $item->{id}, $item->{currency}, $item->{name}, $item->{balance} ); | |
| # $dbs->commit; | |
| } | |
| } | |
| my $tablehtml = $table_data; | |
| my $hash_pretty = format_pretty( $hash, { linum => 1 } ); | |
| $c->render( template => 'accounts', hash_pretty => '', tablehtml => $tablehtml, defaults => \%defaults ); | |
| }; | |
| any 'transactions' => sub ($c) { | |
| my $params = $c->req->params->to_hash; | |
| my $dbs = $c->dbs( $c->session->{myconfig}->{dbname} ); | |
| $params->{from_date} = $dbs->query("SELECT MAX(transdate) FROM gl WHERE transjson IS NOT NULL")->list if !$params->{from_date}; | |
| $params->{to_date} = $dbs->query("SELECT current_date")->list if !$params->{to_date}; | |
| $params->{account} = 'bbe762b6-e590-4880-bb30-f6940060cb57' if !$params->{account}; | |
| if ( !$c->session->{dbname} ) { | |
| $c->render( text => 'Session timed out' ); | |
| return; | |
| } | |
| if ($params->{bank_account}){ | |
| $dbs->query("UPDATE revolut_accounts SET chart_id = ? WHERE id = ?", $params->{bank_account}, $params->{account}); | |
| $dbs->commit; | |
| } else { | |
| my $bank_account = $dbs->query("SELECT chart_id FROM revolut_accounts WHERE id = ?", $params->{account})->list; | |
| $c->param(bank_account => $bank_account); | |
| } | |
| my %defaults = $dbs->query("SELECT fldname, fldvalue FROM defaults")->map; | |
| my $ua = Mojo::UserAgent->new; | |
| my $access_token = $c->session->{access_token}; | |
| my $selectedaccount = $dbs->query("SELECT fldvalue FROM defaults WHERE fldname='selectedaccount'")->list; | |
| my @accounts = $dbs->query("SELECT curr, id FROM revolut_accounts ORDER BY curr")->arrays; | |
| my @chart1 = $dbs->query("SELECT accno || '--' || description, id AS accno FROM chart WHERE link LIKE '%_paid%' ORDER BY 2")->arrays; | |
| my @chart2 = $dbs->query( "SELECT accno || '--' || description, id AS accno FROM chart WHERE accno LIKE ? ORDER BY 2", $selectedaccount )->arrays; | |
| my $from_date = $params->{from_date}; | |
| my $to_date = $params->{to_date}; | |
| my $apicall = "$defaults{revolut_api_url}/transactions?"; | |
| $apicall .= "account=$params->{account}&from=$from_date&to=$to_date"; | |
| my $res = $ua->get( $apicall => { "Authorization" => "Bearer $access_token" } )->result; | |
| my $code = $res->code; | |
| if ( $code eq '500' ) { | |
| $c->render( text => "<pre>API call: $apicall\n\n" . $c->dumper($res) ); | |
| return; | |
| } | |
| my $body = $res->{content}->{asset}->{content}; | |
| my $hash = decode_json($body); | |
| if ( !ref($hash) ) { | |
| $c->render("Unknow error"); | |
| return; | |
| } | |
| my $table_data = HTML::Table->new( -head => [qw/date type amount fee balance currency description state card_number merchant_name/], ); | |
| $table_data->setRowClass( 1, 'listheading' ); | |
| my $msg; | |
| for my $item ( @{$hash} ) { | |
| my $transdate = substr( $item->{created_at}, 0, 10 ); | |
| my $rownum = $table_data->addRow( | |
| $transdate, $item->{type}, | |
| $c->nf->format_price( $item->{legs}->[0]->{amount}, 2 ), $c->nf->format_price( $item->{legs}->[0]->{fee}, 2 ), | |
| $c->nf->format_price( $item->{legs}->[0]->{balance}, 2 ), $item->{legs}->[0]->{currency}, | |
| $item->{legs}->[0]->{description}, $item->{state}, | |
| $item->{card}->{card_number}, $item->{merchant}->{name}, | |
| ); | |
| $table_data->setRowClass( $rownum, 'listrow0' ); | |
| if ( $params->{import} ) { | |
| my ( $exists, $reference ) = $dbs->query( "SELECT id, reference FROM gl WHERE reference = ?", $item->{id} )->list; | |
| if ($exists) { | |
| $dbs->query( "DELETE FROM acc_trans WHERE trans_id = ?", $exists ); | |
| $dbs->query( "DELETE FROM gl WHERE id = ?", $exists ); | |
| $msg .= "Updating $reference ...<br/>"; | |
| } else { | |
| $reference = $item->{id}; | |
| $msg .= "Adding $reference ...<br/>"; | |
| } | |
| my $department_id = $dbs->query("SELECT id FROM department LIMIT 1")->list; | |
| $department_id *= 1; | |
| my $curr = $item->{legs}->[0]->{currency}; | |
| my $exchangerate = $dbs->query( " | |
| SELECT buy | |
| FROM exchangerate | |
| WHERE curr = ? | |
| AND transdate = ?", $curr, $transdate )->list; | |
| $exchangerate *= 1; | |
| $exchangerate = 1 if !$exchangerate; | |
| my $transjson = encode_json($item); | |
| my $other_account; | |
| my $tax_chart_id; | |
| if ( $item->{type} eq 'topup' ) { | |
| ( $other_account, $tax_chart_id ) = $dbs->query( " | |
| SELECT chart_id, tax_chart_id | |
| FROM revolut_rules | |
| WHERE type = ? | |
| LIMIT 1", | |
| $item->{type}, | |
| )->list; | |
| } else { | |
| ( $other_account, $tax_chart_id ) = $dbs->query( " | |
| SELECT chart_id, tax_chart_id | |
| FROM revolut_rules | |
| WHERE merchant_name = ? | |
| AND merchant_city = ? | |
| AND merchant_country = ? | |
| AND category_code = ? | |
| LIMIT 1", | |
| $item->{merchant}->{name}, | |
| $item->{merchant}->{city}, | |
| $item->{merchant}->{country}, | |
| $item->{merchant}->{category_code} )->list; | |
| } | |
| $other_account = $params->{clearing_account} if !$other_account; | |
| my $tax; | |
| if ($tax_chart_id) { | |
| $tax = $dbs->query( "SELECT accno || '--' || description FROM chart WHERE id = ?", $tax_chart_id )->list; | |
| } | |
| $dbs->query( " | |
| INSERT INTO gl(reference, description, notes, transdate, department_id, curr, exchangerate, transjson) | |
| VALUES (?, ?, ?, ?, ?, ?, ?, ?)", | |
| $item->{id}, $item->{legs}->[0]->{description}, "$item->{type} -- $item->{merchant}->{category_code}", $transdate, $department_id, $curr, $exchangerate, $transjson ) | |
| or die $dbs->error; | |
| my $id = $dbs->query( "SELECT id FROM gl WHERE reference = ?", $item->{id} )->list; | |
| if ($id) { | |
| $item->{legs}->[0]->{fee} *= 1; | |
| $dbs->query( " | |
| INSERT INTO acc_trans(trans_id, transdate, chart_id, amount) VALUES (?, ?, ?, ?)", | |
| $id, $transdate, $params->{bank_account}, ( $item->{legs}->[0]->{amount} + $item->{legs}->[0]->{fee} ) * -1 ) | |
| or die $dbs->error; | |
| $dbs->query( " | |
| INSERT INTO acc_trans(trans_id, transdate, chart_id, amount, tax_chart_id, tax) VALUES (?, ?, ?, ?, ?, ?)", | |
| $id, $transdate, $other_account, $item->{legs}->[0]->{amount} + $item->{legs}->[0]->{fee}, $tax_chart_id, $tax ) | |
| or die $dbs->error; | |
| $dbs->commit; | |
| } | |
| } | |
| } | |
| my $tablehtml = $table_data; | |
| my $hash_pretty = format_pretty( $hash, { linum => 1 } ); | |
| $c->render( | |
| template => 'transactions', | |
| msg => $msg, | |
| defaults => \%defaults, | |
| account => $params->{account}, | |
| params => $params, | |
| accounts => \@accounts, | |
| chart1 => \@chart1, | |
| chart2 => \@chart2, | |
| tablehtml => $tablehtml, | |
| hash_pretty => $hash_pretty, | |
| ); | |
| }; | |
| any 'counterparties' => sub ($c) { | |
| if ( !$c->session->{dbname} ) { | |
| $c->render( text => 'Session timed out' ); | |
| return; | |
| } | |
| my $dbs = $c->dbs( $c->session->{myconfig}->{dbname} ); | |
| my %defaults = $dbs->query("SELECT fldname, fldvalue FROM defaults")->map; | |
| my $ua = Mojo::UserAgent->new; | |
| my $access_token = $c->session->{access_token}; | |
| my $params = $c->req->params->to_hash; | |
| my $apicall = "$defaults{revolut_api_url}/counterparties"; | |
| my $res = $ua->get( $apicall => { "Authorization" => "Bearer $access_token" } )->result; | |
| my $code = $res->code; | |
| if ( $code eq '500' ) { | |
| $c->render( text => "<pre>API call: $apicall\n\n" . $c->dumper($res) ); | |
| return; | |
| } | |
| my $body = $res->{content}->{asset}->{content}; | |
| my $hash = decode_json($body); | |
| my $hash_pretty = format_pretty( $hash, { linum => 1 } ); | |
| $c->render( | |
| template => 'counterparties', | |
| defaults => \%defaults, | |
| hash_pretty => $hash_pretty | |
| ); | |
| }; | |
| app->start; | |
| __DATA__ | |
| @@ index.html.ep | |
| % layout 'activate'; | |
| % title 'Home'; | |
| <h1>Revolut - SQL-Ledger Integration!</h1> | |
| <h2>Database: <%= $dbname %></h2> | |
| <br/> | |
| <h3><%= $msg %></h3> | |
| <br/> | |
| To manage your revolut connection visit: <a href="https://business.revolut.com/settings/api">https://business.revolut.com/settings/api</a> | |
| @@ accounts.html.ep | |
| % layout 'default'; | |
| % title 'Accounts List'; | |
| <div class="pricing-header p-3 pb-md-4 mx-auto text-center"> | |
| <div class="listtop">Accounts List</div> | |
| </div> | |
| <%== $tablehtml %> | |
| <pre> | |
| <%== $hash_pretty %> | |
| </pre> | |
| @@ transactions.html.ep | |
| % layout 'default'; | |
| % title 'Transactions List'; | |
| <div class="listtop">Transactions List</div> | |
| <div><%== $msg %></div> | |
| <br/> | |
| <%= form_for 'transactions' => method => 'POST' => begin %> | |
| <table width="100%"> | |
| <tr> | |
| <th align="right">Period</th> | |
| <td> | |
| <%= select_field 'month', ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], class => "form-select" %> | |
| <%= select_field 'year', [ 2007 .. 2023 ], class => "form-select" %> | |
| <br> | |
| <%= radio_button 'interval', 0, checked => 'checked' %> <%= label_for 'interval', 'Current' %> | |
| <%= radio_button 'interval', 1 %> <%= label_for 'interval', 'Month' %> | |
| <%= radio_button 'interval', 3 %> <%= label_for 'interval', 'Quarter' %> | |
| <%= radio_button 'interval', 12 %> <%= label_for 'interval', 'Year' %> | |
| </td> | |
| </tr> | |
| <tr> | |
| <td colspan="2"> </td> | |
| </tr> | |
| <tr> | |
| <th align="right">Account</th> | |
| <td> | |
| <%= select_field 'account', $accounts, class=>"form-select" %> | |
| </td> | |
| </tr> | |
| <tr> | |
| <th align="right">From Date</th> | |
| <td> | |
| %= date_field 'from_date', class => 'datepicker', value => $params->{from_date}, class=>"form-control" | |
| </td> | |
| </tr> | |
| <tr> | |
| <th align="right">To Date</th> | |
| <td> | |
| %= date_field 'to_date', class => 'datepicker', value => $params->{to_date}, class=>"form-control" | |
| </td> | |
| </tr> | |
| <tr> | |
| <th align="right">Bank Account</th> | |
| <td><%= select_field 'bank_account', $chart1, class=>"form-select" %></td> | |
| </tr> | |
| <tr> | |
| <th align="right">Clearing Account</th> | |
| <td><%= select_field 'clearing_account', $chart2, class=>"form-select" %></td> | |
| </tr> | |
| <tr> | |
| <th align="right">Import?</th> | |
| <td> | |
| <%= check_box 'import' => 1, class=>"form-check" %> | |
| </td> | |
| </tr> | |
| <tr> | |
| <td colspan="2"> | |
| <hr/> | |
| <%= submit_button 'Submit', class=>"submit" %> | |
| </td> | |
| </tr> | |
| </table> | |
| <% end %> | |
| <br/> | |
| <%== $tablehtml %> | |
| <pre> | |
| <%== $hash_pretty %> | |
| </pre> | |
| @@ counterparties.html.ep | |
| % layout 'default'; | |
| % title 'Counter Parties'; | |
| <div class="pricing-header p-3 pb-md-4 mx-auto text-center"> | |
| <h1 class="display-4 fw-normal">Counter Parties</h1> | |
| <p class="fs-5 text-muted">Counter Parties</p> | |
| </div> | |
| <pre> | |
| <%== $hash_pretty %> | |
| </pre> | |
| @@ layouts/default.html.ep | |
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <title><%= title %></title> | |
| <link rel="stylesheet" href="//code.jquery.com/ui/1.13.0/themes/base/jquery-ui.css"> | |
| <!-- <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet"> --> | |
| % my $css_path = $c->url_for('css/sql-ledger.css')->to_abs; | |
| % $css_path =~ s/\/revolut\.pl\//\//; | |
| <link rel="stylesheet" type="text/css" href="<%= $css_path %>"> | |
| <script src="//code.jquery.com/jquery-3.6.0.min.js"></script> | |
| <script src="//code.jquery.com/ui/1.13.0/jquery-ui.min.js"></script> | |
| <script> | |
| $(document).ready(function() { | |
| $('.datepicker').datepicker({ | |
| dateFormat: 'dd/mm/yy' | |
| }); | |
| }); | |
| </script> | |
| </head> | |
| <body> | |
| <main class="container-fluid"> | |
| <%= content %> | |
| </main> | |
| <footer class="mt-4 py-3 bg-light"> | |
| <div class="container text-center"> | |
| <hr> | |
| <p>To manage your Revolut connection, visit: <a href="https://business.revolut.com/settings/api">https://business.revolut.com/settings/api</a></p> | |
| % my $debug = 0; | |
| <% if ($debug) { %> | |
| <h2 class="listheading">Session:</h2> | |
| <pre><%= dumper($self->session) %></pre> | |
| <h2 class="listheading">Request Parameters</h2> | |
| <pre><%= dumper($self->req->params->to_hash) %></pre> | |
| <h2 class="listheading">Controller</h2> | |
| <pre><%= dumper($self->stash) %></pre> | |
| <% } %> | |
| </div> | |
| </footer> | |
| <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script> | |
| </body> | |
| </html> | |
| @@ layouts/activate.html.ep | |
| <!doctype html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous"> | |
| <title><%= title %></title> | |
| </head> | |
| <body> | |
| <div class="container-fluid"> | |
| <header> | |
| <div class="d-flex flex-column flex-md-row align-items-center pb-3 mb-4 border-bottom"> | |
| <a href="/" class="d-flex align-items-center text-dark text-decoration-none"> | |
| <span class="fs-4">Revolut - SQL-Ledger Integration</span> | |
| </a> | |
| </div> | |
| </header> | |
| <%= content %> | |
| </div> | |
| % my $debug = 0; | |
| % if ($debug){ | |
| <h2 class='listheading'>Session:</h2> | |
| <pre> | |
| <%= dumper($self->session) %> | |
| </pre> | |
| <h2 class='listheading'>Request Parameters</h2> | |
| <pre> | |
| <%= dumper($self->req->params->to_hash) %> | |
| </pre> | |
| <h2 class='listheading'>Controller</h2> | |
| <pre> | |
| <%= dumper($self->stash) %> | |
| </pre> | |
| % } | |
| </body> | |
| </html> | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment