You've already forked explain.depesz.com
mirror of
https://gitlab.com/depesz/explain.depesz.com.git
synced 2025-07-07 01:07:18 +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:
@ -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;
|
||||
|
||||
|
@ -34,12 +34,14 @@ sub register {
|
||||
}
|
||||
|
||||
# database (DBI) connection arguments
|
||||
$self->connection_args( [
|
||||
$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,45 +68,48 @@ 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;
|
||||
|
||||
@ -158,6 +163,7 @@ 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 => {} },
|
||||
|
@ -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">
|
||||
|
Reference in New Issue
Block a user