1
0
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:
Hubert depesz Lubaczewski 2013-03-30 20:18:27 +01:00
parent adf37dfd65
commit 6a31c2e4a1
7 changed files with 131 additions and 44 deletions

View File

@ -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' );

View File

@ -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' };

View File

@ -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,

View File

@ -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
View 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;
$$;

View File

@ -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">

View File

@ -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">