mirror of
https://gitlab.com/depesz/explain.depesz.com.git
synced 2024-11-24 08:42:27 +02:00
Make it possible to delete plans
Each plan will have now delete_key (random, 50 character string), which can be used with url like http://.../d/plan-id/delete-key to delete it. The delete key is shown once just after plan creation
This commit is contained in:
parent
adf37dfd65
commit
6a31c2e4a1
@ -32,6 +32,9 @@ sub startup {
|
||||
# route: 'show'
|
||||
$routes->route( '/s/:id' )->to( 'controller#show', id => '' )->name( 'show' );
|
||||
|
||||
# route: 'delete'
|
||||
$routes->route( '/d/:id/:key' )->to( 'controller#delete', id => '', key => '' )->name( 'delete' );
|
||||
|
||||
# route: 'history'
|
||||
$routes->route( '/history/:date' )->to( 'controller#history', date => '' )->name( 'history' );
|
||||
|
||||
|
@ -61,12 +61,36 @@ sub index {
|
||||
}
|
||||
|
||||
# save to database
|
||||
my $id = $self->database->save_with_random_name( $title, $plan, $is_public, $is_anon, );
|
||||
my ( $id, $delete_key ) = $self->database->save_with_random_name( $title, $plan, $is_public, $is_anon, );
|
||||
|
||||
# redirect to /show/:id
|
||||
$self->flash( delete_key => $delete_key );
|
||||
return $self->redirect_to( 'show', id => $id );
|
||||
}
|
||||
|
||||
sub delete {
|
||||
my $self = shift;
|
||||
|
||||
# value of "/:id" param
|
||||
my $id = defined $self->stash->{ id } ? $self->stash->{ id } : '';
|
||||
|
||||
# value of "/:key" param
|
||||
my $key = defined $self->stash->{ key } ? $self->stash->{ key } : '';
|
||||
|
||||
# missing or invalid
|
||||
return $self->redirect_to( 'new-explain' ) unless $id =~ m{\A[a-zA-Z0-9]+\z};
|
||||
return $self->redirect_to( 'new-explain' ) unless $key =~ m{\A[a-zA-Z0-9]+\z};
|
||||
|
||||
# delete plan in database
|
||||
my $delete_worked = $self->database->delete_plan( $id, $key );
|
||||
|
||||
# not found in database
|
||||
return $self->redirect_to( 'new-explain', status => 404 ) unless $delete_worked;
|
||||
|
||||
$self->flash( message => sprintf( 'Plan %s deleted.', $id ) );
|
||||
return $self->redirect_to( 'new-explain' );
|
||||
}
|
||||
|
||||
sub show {
|
||||
my $self = shift;
|
||||
|
||||
@ -109,8 +133,8 @@ sub show {
|
||||
push @elements, @{ $e->initplans } if $e->initplans;
|
||||
push @elements, @{ $e->subplans } if $e->subplans;
|
||||
|
||||
$stats->{'nodes'}->{ $e->type }->{'count'}++;
|
||||
$stats->{'nodes'}->{ $e->type }->{'time'}+=$e->total_exclusive_time if $e->total_exclusive_time;
|
||||
$stats->{ 'nodes' }->{ $e->type }->{ 'count' }++;
|
||||
$stats->{ 'nodes' }->{ $e->type }->{ 'time' } += $e->total_exclusive_time if $e->total_exclusive_time;
|
||||
|
||||
next unless $e->scan_on;
|
||||
next unless $e->scan_on->{ 'table_name' };
|
||||
|
@ -8,7 +8,7 @@ use Carp;
|
||||
use DBI;
|
||||
use Date::Simple;
|
||||
|
||||
has dbh => undef;
|
||||
has dbh => undef;
|
||||
has connection_args => sub { [] };
|
||||
|
||||
sub register {
|
||||
@ -21,7 +21,7 @@ sub register {
|
||||
unless ( $dsn ) {
|
||||
|
||||
# driver
|
||||
my $driver = $config->{ driver } || 'Pg';
|
||||
my $driver = $config->{ driver } || 'Pg';
|
||||
|
||||
# database name
|
||||
my $database = $config->{ database } || lc( $ENV{ MOJO_APP } );
|
||||
@ -34,12 +34,14 @@ sub register {
|
||||
}
|
||||
|
||||
# database (DBI) connection arguments
|
||||
$self->connection_args( [
|
||||
$config->{ dsn },
|
||||
$config->{ username },
|
||||
$config->{ password },
|
||||
$config->{ options } || {}
|
||||
] );
|
||||
$self->connection_args(
|
||||
[
|
||||
$config->{ dsn },
|
||||
$config->{ username },
|
||||
$config->{ password },
|
||||
$config->{ options } || {}
|
||||
]
|
||||
);
|
||||
|
||||
# log debug message
|
||||
$app->log->debug( 'Database connection args: ' . $app->dumper( $self->connection_args ) );
|
||||
@ -66,60 +68,63 @@ sub register {
|
||||
}
|
||||
|
||||
sub save_with_random_name {
|
||||
my ( $self, $title, $content, $is_public, $is_anon ) = @_;
|
||||
my $self = shift;
|
||||
my ( $title, $content, $is_public, $is_anon ) = @_;
|
||||
|
||||
# create statement handler
|
||||
my $sth = $self->dbh->prepare( 'SELECT register_plan(?, ?, ?, ?)' );
|
||||
my @row = $self->dbh->selectrow_array(
|
||||
'SELECT id, delete_key FROM register_plan(?, ?, ?, ?)',
|
||||
undef,
|
||||
$title, $content, $is_public, $is_anon,
|
||||
);
|
||||
|
||||
# execute
|
||||
$sth->execute( $title, $content, $is_public, $is_anon );
|
||||
|
||||
# register_plan returns plan id
|
||||
my @row = $sth->fetchrow_array;
|
||||
|
||||
# finish
|
||||
$sth->finish;
|
||||
|
||||
# return plan id
|
||||
return $row[ 0 ];
|
||||
# return id and delete_key
|
||||
return @row;
|
||||
}
|
||||
|
||||
# @depesz
|
||||
# - why you don't use selectrow_array (or similar)?
|
||||
sub get_plan {
|
||||
my ( $self, $plan_id ) = @_;
|
||||
my $self = shift;
|
||||
my ( $plan_id ) = @_;
|
||||
|
||||
# create statement handler
|
||||
my $sth = $self->dbh->prepare( 'SELECT plan, title FROM plans WHERE id = ?' );
|
||||
|
||||
# execute
|
||||
$sth->execute( $plan_id );
|
||||
|
||||
# fetch row
|
||||
my @row = $sth->fetchrow_array;
|
||||
|
||||
# finish
|
||||
$sth->finish;
|
||||
my @row = $self->dbh->selectrow_array(
|
||||
'SELECT plan, title FROM plans WHERE id = ? AND NOT is_deleted',
|
||||
undef,
|
||||
$plan_id,
|
||||
);
|
||||
|
||||
# return plan
|
||||
return @row;
|
||||
}
|
||||
|
||||
sub delete_plan {
|
||||
my $self = shift;
|
||||
my ( $plan_id, $delete_key ) = @_;
|
||||
my @row = $self->dbh->selectrow_array(
|
||||
'UPDATE plans SET plan = ?, title = ?, is_deleted = true, delete_key = NULL WHERE id = ? and delete_key = ? RETURNING 1',
|
||||
undef,
|
||||
'',
|
||||
'This plan has been deleted.',
|
||||
$plan_id,
|
||||
$delete_key
|
||||
);
|
||||
return 1 if $row[ 0 ];
|
||||
return;
|
||||
}
|
||||
|
||||
sub get_public_list {
|
||||
my $self = shift;
|
||||
|
||||
return $self->dbh->selectall_arrayref(
|
||||
'SELECT id, to_char( entered_on, ? ) as date FROM plans WHERE is_public ORDER BY entered_on DESC',
|
||||
{ Slice => { } },
|
||||
{ Slice => {} },
|
||||
'YYYY-MM-DD'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
sub get_public_list_paged {
|
||||
my $self = shift;
|
||||
|
||||
# param "date"
|
||||
my $date = defined( $_[0] ) ? $_[0] : '';
|
||||
my $date = defined( $_[ 0 ] ) ? $_[ 0 ] : '';
|
||||
|
||||
# trim
|
||||
trim $date;
|
||||
@ -158,9 +163,10 @@ sub get_public_list_paged {
|
||||
WHERE is_public
|
||||
AND entered_on > ?::date
|
||||
AND entered_on < ?::date
|
||||
AND NOT is_deleted
|
||||
ORDER BY entered_on
|
||||
DESC',
|
||||
{ Slice => { } },
|
||||
{ Slice => {} },
|
||||
'YYYY-MM-DD',
|
||||
$since,
|
||||
( $to + 1 )->as_str( '%Y-%m-%d' )
|
||||
@ -168,7 +174,7 @@ sub get_public_list_paged {
|
||||
|
||||
# next week
|
||||
my $next = $to + 7;
|
||||
$next = ( $next > $today ) ? undef : $next->as_str( '%Y-%m-%d' );
|
||||
$next = ( $next > $today ) ? undef : $next->as_str( '%Y-%m-%d' );
|
||||
|
||||
return {
|
||||
since => $since,
|
||||
|
@ -367,6 +367,10 @@ div.message p.message { font-weight: bold; font-size: 2em; color: #ff0; padding:
|
||||
div.message p.hint { color: #ff0; padding: 0; margin: 0; }
|
||||
div.message p.hint a { color: #ff0; }
|
||||
div.message p.hint a:hover { color: #ff0; }
|
||||
div.message p.message a,
|
||||
div.message p.message a:visited {color:#ff0;border: none;}
|
||||
div.messageNice { border: 2px solid #0f0; background: #060; }
|
||||
|
||||
div.plea { text-align: center; color: #666; padding-top: 1em; }
|
||||
|
||||
.result-stats h1 {color:#123}
|
||||
|
37
sql/patch-001.sql
Normal file
37
sql/patch-001.sql
Normal file
@ -0,0 +1,37 @@
|
||||
-- Patch that adds ability to delete plan
|
||||
|
||||
alter table plans add column delete_key text;
|
||||
alter table plans add column is_deleted bool NOT NULL DEFAULT false;
|
||||
|
||||
CREATE type register_plan_return as (
|
||||
id TEXT,
|
||||
delete_key TEXT
|
||||
);
|
||||
|
||||
DROP FUNCTION register_plan(in_plan text, in_is_public boolean);
|
||||
DROP FUNCTION register_plan(in_plan text, in_is_public boolean, in_is_anonymized boolean);
|
||||
|
||||
DROP FUNCTION register_plan(in_title text, in_plan text, in_is_public boolean, in_is_anonymized boolean);
|
||||
CREATE FUNCTION register_plan(in_title text, in_plan text, in_is_public boolean, in_is_anonymized boolean) RETURNS register_plan_return
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
use_hash_length int4 := 2;
|
||||
reply register_plan_return;
|
||||
BEGIN
|
||||
reply.delete_key := get_random_string( 50 );
|
||||
LOOP
|
||||
reply.id := get_random_string(use_hash_length);
|
||||
BEGIN
|
||||
INSERT INTO plans (id, title, plan, is_public, entered_on, is_anonymized, delete_key) values (reply.id, in_title, in_plan, in_is_public, now(), in_is_anonymized, reply.delete_key );
|
||||
RETURN reply;
|
||||
EXCEPTION WHEN unique_violation THEN
|
||||
-- do nothing
|
||||
END;
|
||||
use_hash_length := use_hash_length + 1;
|
||||
IF use_hash_length >= 30 THEN
|
||||
raise exception 'Random string of length == 30 requested. something''s wrong.';
|
||||
END IF;
|
||||
END LOOP;
|
||||
END;
|
||||
$$;
|
@ -12,6 +12,12 @@
|
||||
</div>
|
||||
% }
|
||||
|
||||
% if ( flash( 'message' ) ) {
|
||||
<div class="message">
|
||||
<p class="message"><%= flash( 'message' ) =%></p>
|
||||
</div>
|
||||
% }
|
||||
|
||||
<form id="new-explain" method="post" action="<%= url_for 'current' %>">
|
||||
|
||||
<div class="fe fe-first fe_plan">
|
||||
|
@ -296,6 +296,13 @@
|
||||
|
||||
<h1>Result: <%= $full_title %></h1>
|
||||
|
||||
% if ( flash( 'delete_key' ) ) {
|
||||
<div class="message messageNice">
|
||||
<p class="message">To delete this plan, you can use <a href="<%= url_for( 'delete', id => $id, key => flash( 'delete_key' ) )=%>">this link</a>.</p>
|
||||
<p class="hint">This link will not be shown any more, so you might want to bookmark it, just in case.</p>
|
||||
</div>
|
||||
% }
|
||||
|
||||
<div class="explain-form">
|
||||
|
||||
<form id="explain-form" class="hidden" method="get" action="<%= url_for 'current' %>" autocomplete="off">
|
||||
|
Loading…
Reference in New Issue
Block a user