2011-03-10 15:19:34 +00:00
package Explain::Plugin::Database ;
use Mojo::Base 'Mojolicious::Plugin' ;
2011-03-11 10:33:49 +00:00
use Mojo::Util 'trim' ;
2011-03-11 06:19:50 +00:00
2011-03-10 15:19:34 +00:00
use Carp ;
use DBI ;
2011-03-11 10:33:49 +00:00
use Date::Simple ;
2013-10-28 14:58:17 +01:00
use English qw( -no_match_vars ) ;
2011-03-10 15:19:34 +00:00
2013-10-30 12:47:28 +01:00
has dbh = > undef ;
2011-03-10 15:19:34 +00:00
has connection_args = > sub { [] } ;
2013-10-30 12:47:28 +01:00
has log = > undef ;
2011-03-10 15:19:34 +00:00
sub register {
my ( $ self , $ app , $ config ) = @ _ ;
# data source name
my $ dsn = $ config - > { dsn } ;
# if DSN not set directly
unless ( $ dsn ) {
# driver
2013-03-30 20:18:27 +01:00
my $ driver = $ config - > { driver } || 'Pg' ;
2011-03-10 15:19:34 +00:00
# database name
my $ database = $ config - > { database } || lc ( $ ENV { MOJO_APP } ) ;
# assemble
my $ dsn = sprintf 'dbi:%s:database=%s' , $ driver , $ database ;
$ dsn . = ';host=' . $ config - > { host } if $ config - > { host } ;
$ dsn . = ';port=' . $ config - > { port } if $ config - > { port } ;
}
# database (DBI) connection arguments
2013-03-30 20:18:27 +01:00
$ self - > connection_args (
[
$ config - > { dsn } ,
$ config - > { username } ,
$ config - > { password } ,
$ config - > { options } || { }
]
) ;
2011-03-10 15:19:34 +00:00
# log debug message
2011-03-14 13:37:48 +00:00
$ app - > log - > debug ( 'Database connection args: ' . $ app - > dumper ( $ self - > connection_args ) ) ;
2013-10-28 14:58:17 +01:00
$ self - > log ( $ app - > log ) ;
2011-03-10 15:19:34 +00:00
# register helper
$ app - > helper (
2011-03-14 13:37:48 +00:00
database = > sub {
# not conected yet
unless ( $ self - > dbh ) {
# connect
$ self - > dbh ( DBI - > connect ( @ { $ self - > connection_args } ) ) ;
# raise error (for case, when "RaiseError" option is not set)
confess qq|Can't connect database| unless $ self - > dbh ;
}
return $ self ;
}
2011-03-10 15:19:34 +00:00
) ;
return ;
}
2013-10-28 14:58:17 +01:00
sub user_login {
my $ self = shift ;
my ( $ username , $ password ) = @ _ ;
my @ row = $ self - > dbh - > selectrow_array (
2014-10-31 22:03:11 +01:00
'SELECT password, is_admin FROM users where username = ?' ,
2013-10-28 14:58:17 +01:00
undef ,
$ username ,
) ;
return if 0 == scalar @ row ;
2013-10-30 12:47:28 +01:00
my $ crypted = crypt ( $ password , $ row [ 0 ] ) ;
return if $ crypted ne $ row [ 0 ] ;
2014-10-31 22:03:11 +01:00
return { 'admin' = > $ row [ 1 ] } ;
2013-10-28 14:58:17 +01:00
}
sub user_change_password {
my $ self = shift ;
2013-10-30 12:47:28 +01:00
my ( $ username , $ old , $ new ) = @ _ ;
2013-10-28 14:58:17 +01:00
my @ row = $ self - > dbh - > selectrow_array (
'SELECT password FROM users where username = ?' ,
undef ,
$ username ,
) ;
return if 0 == scalar @ row ;
2013-10-30 12:47:28 +01:00
my $ crypted_old = crypt ( $ old , $ row [ 0 ] ) ;
2013-10-28 14:58:17 +01:00
my $ crypted_new = crypt ( $ new , $ self - > get_pw_salt ( ) ) ;
2013-10-30 12:47:28 +01:00
2013-10-28 14:58:17 +01:00
@ row = $ self - > dbh - > selectrow_array (
'UPDATE users SET password = ? WHERE ( username, password ) = ( ?, ? ) returning username' ,
undef ,
$ crypted_new , $ username , $ crypted_old ,
) ;
return 1 if 1 == scalar @ row ;
return ;
}
2013-10-29 14:35:04 +01:00
sub get_user_history {
my $ self = shift ;
my ( $ user , $ direction , $ marker ) = @ _ ;
my $ limit = 100 ;
2013-10-30 12:47:28 +01:00
2013-10-29 14:35:04 +01:00
$ direction = 'DESC' if ( $ direction // '' ) ne 'ASC' ;
my $ query = '' ;
2013-10-30 12:47:28 +01:00
my @ args = ( ) ;
2013-10-29 14:35:04 +01:00
if ( defined $ marker ) {
my $ comparison = $ direction eq 'DESC' ? '<' : '>' ;
$ query = "
SELECT p . id , p . entered_on:: date , p . is_public , p . is_anonymized , p . title
FROM plans p
WHERE p . added_by = ? and not p . is_deleted
AND ( p . entered_on , p . id ) $ comparison (
select x . entered_on , x . id
from plans x
where x . id = ?
)
ORDER BY p . entered_on $ direction , p . id $ direction LIMIT $ limit
" ;
@ args = ( $ user , $ marker ) ;
2013-10-30 12:47:28 +01:00
}
else {
2013-10-29 14:35:04 +01:00
$ query = "
SELECT p . id , p . entered_on:: date , p . is_public , p . is_anonymized , p . title
FROM plans p
WHERE p . added_by = ? and not p . is_deleted
ORDER BY p . entered_on DESC , p . id DESC LIMIT $ limit
" ;
@ args = ( $ user ) ;
}
my $ plans = $ self - > dbh - > selectall_arrayref ( $ query , { Slice = > { } } , @ args ) ;
# newest plans always first
$ plans = [ reverse @ { $ plans } ] if $ direction eq 'ASC' ;
return {
2013-10-30 12:47:28 +01:00
'list' = > [] ,
2013-10-29 14:35:04 +01:00
'earlier' = > 0 ,
2013-10-30 12:47:28 +01:00
'later' = > 0 ,
2013-10-29 14:35:04 +01:00
} if 0 == scalar @ { $ plans } ;
2013-10-30 12:47:28 +01:00
2013-10-29 14:35:04 +01:00
my @ later = $ self - > dbh - > selectrow_array (
'SELECT p.id FROM plans p where p.added_by = ? and not is_deleted and ( p.entered_on, p.id ) > ( select x.entered_on, x.id from plans x where x.id = ? ) limit 1' ,
undef ,
2013-10-30 12:47:28 +01:00
$ user , $ plans - > [ 0 ] - > { 'id' } ,
2013-10-29 14:35:04 +01:00
) ;
my @ earlier = $ self - > dbh - > selectrow_array (
'SELECT p.id FROM plans p where p.added_by = ? and not is_deleted and ( p.entered_on, p.id ) < ( select x.entered_on, x.id from plans x where x.id = ? ) limit 1' ,
undef ,
2013-10-30 12:47:28 +01:00
$ user , $ plans - > [ - 1 ] - > { 'id' } ,
2013-10-29 14:35:04 +01:00
) ;
return {
2013-10-30 12:47:28 +01:00
'list' = > $ plans ,
'later' = > scalar @ later ,
2013-10-29 14:35:04 +01:00
'earlier' = > scalar @ earlier ,
} ;
}
2013-10-28 14:58:17 +01:00
sub get_pw_salt {
2013-10-30 12:47:28 +01:00
my $ self = shift ;
my @ salt_chars = ( 'a' .. 'z' , 'A' .. 'Z' , 0 .. 9 , '.' , '/' ) ;
my $ salt = sprintf '$6$%s$' , join ( '' , map { $ salt_chars [ rand @ salt_chars ] } 1 .. 16 ) ;
2013-10-28 14:58:17 +01:00
return $ salt ;
}
sub user_register {
my $ self = shift ;
my ( $ username , $ password ) = @ _ ;
my $ crypted = crypt ( $ password , $ self - > get_pw_salt ( ) ) ;
2013-10-30 12:47:28 +01:00
eval { $ self - > dbh - > do ( 'INSERT INTO users (username, password, registered) values (?, ?, now())' , undef , $ username , $ crypted , ) ; } ;
2013-10-28 14:58:17 +01:00
return 1 unless $ EVAL_ERROR ;
$ self - > log - > error ( "user_register( $username ) => " . $ EVAL_ERROR ) ;
return ;
}
2013-10-29 14:35:04 +01:00
sub update_plan {
my $ self = shift ;
2013-10-30 12:47:28 +01:00
my ( $ id , $ changes ) = @ _ ;
2013-10-29 14:35:04 +01:00
my @ columns = keys % { $ changes } ;
my @ values = values % { $ changes } ;
eval {
$ self - > dbh - > do (
2013-10-30 12:47:28 +01:00
'UPDATE plans SET ' . join ( ', ' , map { "$_ = ?" } @ columns ) . ' WHERE id = ?' ,
2013-10-29 14:35:04 +01:00
undef ,
@ values , $ id
) ;
} ;
return 1 unless $ EVAL_ERROR ;
$ self - > log - > error ( "update_plan( $id ) => " . $ EVAL_ERROR ) ;
return ;
}
2011-03-10 15:19:34 +00:00
sub save_with_random_name {
2013-03-30 20:18:27 +01:00
my $ self = shift ;
2017-05-19 18:45:41 +02:00
my ( $ title , $ content , $ is_public , $ is_anon , $ username , $ optimization_for ) = @ _ ;
2011-03-10 15:19:34 +00:00
2013-03-30 20:18:27 +01:00
my @ row = $ self - > dbh - > selectrow_array (
2017-05-19 18:45:41 +02:00
'SELECT id, delete_key FROM register_plan(?, ?, ?, ?, ?, ?)' ,
2013-03-30 20:18:27 +01:00
undef ,
2017-05-19 18:45:41 +02:00
$ title , $ content , $ is_public , $ is_anon , $ username , $ optimization_for ,
2013-03-30 20:18:27 +01:00
) ;
2011-03-10 15:19:34 +00:00
2013-03-30 20:18:27 +01:00
# return id and delete_key
return @ row ;
2011-03-10 15:19:34 +00:00
}
2018-06-11 20:56:49 +02:00
sub ping {
my $ self = shift ;
return $ self - > dbh - > ping ( ) ;
}
2013-10-29 14:35:04 +01:00
sub get_plan_data {
my $ self = shift ;
my ( $ plan_id ) = @ _ ;
my $ rows = $ self - > dbh - > selectall_arrayref (
'SELECT * FROM plans WHERE id = ? AND NOT is_deleted' ,
{ Slice = > { } } ,
$ plan_id ,
) ;
return unless defined $ rows ;
return if 0 == scalar @ { $ rows } ;
2013-10-30 12:47:28 +01:00
return $ rows - > [ 0 ] ;
2013-10-29 14:35:04 +01:00
}
2011-03-10 15:19:34 +00:00
sub get_plan {
2013-03-30 20:18:27 +01:00
my $ self = shift ;
my ( $ plan_id ) = @ _ ;
2011-03-10 15:19:34 +00:00
2013-03-30 20:18:27 +01:00
my @ row = $ self - > dbh - > selectrow_array (
2017-05-19 18:45:41 +02:00
'SELECT plan, title, optimization_for FROM plans WHERE id = ? AND NOT is_deleted' ,
2013-03-30 20:18:27 +01:00
undef ,
$ plan_id ,
) ;
2011-03-10 15:19:34 +00:00
# return plan
2011-06-28 15:25:14 +02:00
return @ row ;
2011-03-10 15:19:34 +00:00
}
2017-05-19 18:45:41 +02:00
sub get_optimization_path {
my $ self = shift ;
my ( $ plan_id ) = @ _ ;
my $ rows = $ self - > dbh - > selectall_arrayref (
'
WITH RECURSIVE path AS (
SELECT id , title , optimization_for , 0 as level FROM plans WHERE id = ? and not is_deleted
union all
SELECT p . id , p . title , p . optimization_for , x . level + 1
FROM path x
join plans p on p . id = x . optimization_for
WHERE NOT p . is_deleted
AND x . optimization_for IS NOT NULL
)
SELECT
id , title
FROM
path
ORDER BY level desc ;
' ,
{ Slice = > { } } ,
$ plan_id ,
) ;
return if 0 == scalar @ { $ rows } ;
return if 1 == scalar @ { $ rows } ;
return $ rows ;
}
sub get_optimizations_for {
my $ self = shift ;
my ( $ plan_id ) = @ _ ;
my $ rows = $ self - > dbh - > selectall_arrayref (
'select id, title from plans where optimization_for = ? and not is_deleted' ,
{ Slice = > { } } ,
$ plan_id
) ;
return if 0 == scalar @ { $ rows } ;
return $ rows ;
}
sub plan_exists {
my $ self = shift ;
my ( $ plan_id ) = @ _ ;
my @ row = $ self - > dbh - > selectrow_array (
'SELECT 1 FROM plans WHERE id = ? AND NOT is_deleted' ,
undef ,
$ plan_id ,
) ;
return if 0 == scalar @ row ;
return 1 ;
}
2013-03-30 20:18:27 +01:00
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 ;
}
2011-03-10 15:19:34 +00:00
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' ,
2013-03-30 20:18:27 +01:00
{ Slice = > { } } ,
2011-03-10 15:19:34 +00:00
'YYYY-MM-DD'
) ;
2013-03-30 20:18:27 +01:00
}
2011-03-10 15:19:34 +00:00
sub get_public_list_paged {
my $ self = shift ;
2011-03-11 10:33:49 +00:00
# param "date"
2013-03-30 20:18:27 +01:00
my $ date = defined ( $ _ [ 0 ] ) ? $ _ [ 0 ] : '' ;
2011-03-10 15:19:34 +00:00
2011-03-11 10:33:49 +00:00
# trim
trim $ date ;
2011-03-10 15:19:34 +00:00
2011-03-11 10:33:49 +00:00
# today
my $ today = Date::Simple - > new ;
2011-03-10 15:19:34 +00:00
2011-03-11 10:33:49 +00:00
# scalar $date to Date::Simple
my $ to = eval { Date::Simple - > new ( $ date ) } ;
# error
unless ( $ to ) {
# invalid date, like: 2010-02-31
return { error = > qq|Invalid date "$date" given.| }
if $ date =~ /\A\d\d\d\d\-\d\d\-\d\d\z/ ;
# invalid format
return { error = > qq|Invalid value "$date" given.| }
if length $ date ;
# fallback
$ to = $ today ;
}
# time travel exception
return { error = > qq|Date "$date" is in future!| } if $ to > $ today ;
# since SCALAR value
my $ since = ( $ to - 7 ) - > as_str ( '%Y-%m-%d' ) ;
2011-03-10 15:19:34 +00:00
# select
my $ rows = $ self - > dbh - > selectall_arrayref (
2011-03-11 06:19:50 +00:00
' SELECT id , to_char ( entered_on , ? ) as date
FROM plans
WHERE is_public
2011-03-11 10:33:49 +00:00
AND entered_on > ? :: date
AND entered_on < ? :: date
2013-03-30 20:18:27 +01:00
AND NOT is_deleted
2011-03-11 06:19:50 +00:00
ORDER BY entered_on
DESC ' ,
2013-03-30 20:18:27 +01:00
{ Slice = > { } } ,
2011-03-11 06:19:50 +00:00
'YYYY-MM-DD' ,
2011-03-11 10:33:49 +00:00
$ since ,
( $ to + 1 ) - > as_str ( '%Y-%m-%d' )
2011-03-10 15:19:34 +00:00
) ;
2011-03-11 10:33:49 +00:00
# next week
my $ next = $ to + 7 ;
2013-03-30 20:18:27 +01:00
$ next = ( $ next > $ today ) ? undef : $ next - > as_str ( '%Y-%m-%d' ) ;
2011-03-10 15:19:34 +00:00
2011-03-11 10:33:49 +00:00
return {
since = > $ since ,
to = > $ to - > as_str ( '%Y-%m-%d' ) ,
rows = > $ rows || [] ,
next_date = > $ next ,
2011-03-14 11:07:08 +00:00
prev_date = > ( $ to - 8 ) - > as_str ( '%Y-%m-%d' )
2011-03-11 10:33:49 +00:00
} ;
2011-03-10 15:19:34 +00:00
}
sub DESTROY {
my $ self = shift ;
# nothing to do...
return unless $ self - > dbh ;
# rollback uncommited transactions
$ self - > dbh - > rollback unless $ self - > connection_args - > [ 3 ] - > { auto_commit } ;
# disconnect
$ self - > dbh - > disconnect ;
$ self - > dbh ( undef ) ;
return ;
}
1 ;