From 4cd7cbef491f1af48cbdd61d168e79a96409ca37 Mon Sep 17 00:00:00 2001 From: Denis Fedoseev Date: Sat, 22 Jul 2017 16:04:57 +0900 Subject: [PATCH 1/8] Add users table. --- deploy/users.sql | 13 +++++++++++++ revert/users.sql | 7 +++++++ sqitch.plan | 1 + verify/users.sql | 9 +++++++++ 4 files changed, 30 insertions(+) create mode 100644 deploy/users.sql create mode 100644 revert/users.sql create mode 100644 verify/users.sql diff --git a/deploy/users.sql b/deploy/users.sql new file mode 100644 index 0000000..f713308 --- /dev/null +++ b/deploy/users.sql @@ -0,0 +1,13 @@ +-- Deploy fotostore:users to sqlite + +BEGIN; + +CREATE TABLE users ( + nickname TEXT PRIMARY KEY, + password TEXT NOT NULL, + fullname TEXT NOT NULL, + twitter TEXT NOT NULL, + timestamp DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +COMMIT; diff --git a/revert/users.sql b/revert/users.sql new file mode 100644 index 0000000..cb3357a --- /dev/null +++ b/revert/users.sql @@ -0,0 +1,7 @@ +-- Revert fotostore:users from sqlite + +BEGIN; + +DROP TABLE users; + +COMMIT; diff --git a/sqitch.plan b/sqitch.plan index f44c4cc..a1fe3fb 100644 --- a/sqitch.plan +++ b/sqitch.plan @@ -2,3 +2,4 @@ %project=fotostore %uri=https://rsscp.ru/ +users 2017-07-22T06:53:03Z Denis Fedoseev # Creates table to track our users. diff --git a/verify/users.sql b/verify/users.sql new file mode 100644 index 0000000..f5d4aa8 --- /dev/null +++ b/verify/users.sql @@ -0,0 +1,9 @@ +-- Verify fotostore:users on sqlite + +BEGIN; + +SELECT nickname, password, fullname, twitter + FROM users + WHERE 0; + +ROLLBACK; -- 2.43.0 From 2b5267594c1ddd6128d62267778fd0a11887aabf Mon Sep 17 00:00:00 2001 From: Denis Fedoseev Date: Sat, 22 Jul 2017 16:08:59 +0900 Subject: [PATCH 2/8] Set default target and always verify. --- sqitch.conf | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sqitch.conf b/sqitch.conf index e310fca..8b84c82 100644 --- a/sqitch.conf +++ b/sqitch.conf @@ -6,3 +6,11 @@ # target = db:sqlite: # registry = sqitch # client = sqlite3 +[target "foto_test"] + uri = db:sqlite:rsscp_test.db +[engine "sqlite"] + target = foto_test +[deploy] + verify = true +[rebase] + verify = true -- 2.43.0 From 5469ed0e68101d111ba99d928dfc0a2890f58371 Mon Sep 17 00:00:00 2001 From: Denis Fedoseev Date: Sat, 22 Jul 2017 17:07:48 +0900 Subject: [PATCH 3/8] gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 6812b45..61439d2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ *.db -*.conf +application.conf -- 2.43.0 From 8961893b43a68ede2980c5bd00b16fcc6fb2f57b Mon Sep 17 00:00:00 2001 From: Denis Fedoseev Date: Sat, 22 Jul 2017 17:08:15 +0900 Subject: [PATCH 4/8] squitch data was moved to sql folder --- fotostore.pl | 128 ++----------------------------- sql/deploy/images.sql | 17 ++++ {deploy => sql/deploy}/users.sql | 0 sql/revert/images.sql | 7 ++ {revert => sql/revert}/users.sql | 0 sqitch.conf => sql/sqitch.conf | 0 sqitch.plan => sql/sqitch.plan | 1 + sql/verify/images.sql | 7 ++ {verify => sql/verify}/users.sql | 0 9 files changed, 38 insertions(+), 122 deletions(-) create mode 100644 sql/deploy/images.sql rename {deploy => sql/deploy}/users.sql (100%) create mode 100644 sql/revert/images.sql rename {revert => sql/revert}/users.sql (100%) rename sqitch.conf => sql/sqitch.conf (100%) rename sqitch.plan => sql/sqitch.plan (60%) create mode 100644 sql/verify/images.sql rename {verify => sql/verify}/users.sql (100%) diff --git a/fotostore.pl b/fotostore.pl index 77df8b2..dec749b 100644 --- a/fotostore.pl +++ b/fotostore.pl @@ -10,13 +10,17 @@ use File::Spec 'catfile'; use Cwd; use Imager; +use DBI; +use Digest::SHA; my $config = plugin 'Config'=> {file => 'application.conf'};; my $predefined_user = 'alpha6'; my $predefined_password = $config->{'password'}; -die "No user password defined!" unless($predefined_password); +die 'No user password defined!' unless($predefined_password); + +my $dbh = DBI->connect(sprintf('dbi:SQLite:dbname=%s', $config->{'db_file'}),"",""); # Image base URL my $IMAGE_BASE = 'images'; @@ -219,124 +223,4 @@ sub create_filename { return $name; } -app->start; - -__DATA__ - -@@ error.html.ep - - - - Error - - - <%= $message %> - - - -@@ no_logged.html.ep - - - - Rough, Slow, Stupid, Contrary Photohosting - - -

Rough, Slow, Stupid, Contrary Photohosting

-
-
- - - -
-
- - - -@@ index.html.ep - - - - Rough, Slow, Stupid, Contrary Photohosting - - - - - - - - - - - - - - - - -

Rough, Slow, Stupid, Contrary Photohosting

- <% if (is_user_authenticated()) { %> - -
- - - - - - -
-
-
-
- -
-<% foreach my $image (@$images) { %> -
-
-
- Image original - <% for my $scale (@$scales) { %> - <%= $scale %> - <% } %> -
-
- "> -
-
-<% } %> -
- <% } else { %> -
-
- - - -
-
- <% } %> - - - +app->start; \ No newline at end of file diff --git a/sql/deploy/images.sql b/sql/deploy/images.sql new file mode 100644 index 0000000..00b577a --- /dev/null +++ b/sql/deploy/images.sql @@ -0,0 +1,17 @@ +-- Deploy fotostore:images to sqlite + +BEGIN; + +CREATE TABLE images ( + file_id INTEGER PRIMARY KEY AUTOINCREMENT, + owner_id INTEGER NOT NULL, + file_name TEXT NOT NULL, + created_time DATETIME NOT NULL + DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY ( + owner_id + ) + REFERENCES users (user_id) ON DELETE CASCADE +); + +COMMIT; diff --git a/deploy/users.sql b/sql/deploy/users.sql similarity index 100% rename from deploy/users.sql rename to sql/deploy/users.sql diff --git a/sql/revert/images.sql b/sql/revert/images.sql new file mode 100644 index 0000000..f4eeb8a --- /dev/null +++ b/sql/revert/images.sql @@ -0,0 +1,7 @@ +-- Revert fotostore:images from sqlite + +BEGIN; + +DROP TABLE images; + +COMMIT; diff --git a/revert/users.sql b/sql/revert/users.sql similarity index 100% rename from revert/users.sql rename to sql/revert/users.sql diff --git a/sqitch.conf b/sql/sqitch.conf similarity index 100% rename from sqitch.conf rename to sql/sqitch.conf diff --git a/sqitch.plan b/sql/sqitch.plan similarity index 60% rename from sqitch.plan rename to sql/sqitch.plan index a1fe3fb..686b260 100644 --- a/sqitch.plan +++ b/sql/sqitch.plan @@ -3,3 +3,4 @@ %uri=https://rsscp.ru/ users 2017-07-22T06:53:03Z Denis Fedoseev # Creates table to track our users. +images 2017-07-22T07:17:52Z Denis Fedoseev # Creates table to track users images. diff --git a/sql/verify/images.sql b/sql/verify/images.sql new file mode 100644 index 0000000..0ea514f --- /dev/null +++ b/sql/verify/images.sql @@ -0,0 +1,7 @@ +-- Verify fotostore:images on sqlite + +BEGIN; + +select file_id, owner_id, file_name, created_time from images where 0; + +ROLLBACK; diff --git a/verify/users.sql b/sql/verify/users.sql similarity index 100% rename from verify/users.sql rename to sql/verify/users.sql -- 2.43.0 From 0030adabab078b6cf929436c33ada3d4792eb3e8 Mon Sep 17 00:00:00 2001 From: Denis Fedoseev Date: Sat, 22 Jul 2017 18:33:04 +0900 Subject: [PATCH 5/8] Database changes --- sql/deploy/users.sql | 11 ++++++----- sql/sqitch.plan | 3 +++ sql/verify/users.sql | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/sql/deploy/users.sql b/sql/deploy/users.sql index f713308..a296ced 100644 --- a/sql/deploy/users.sql +++ b/sql/deploy/users.sql @@ -3,11 +3,12 @@ BEGIN; CREATE TABLE users ( - nickname TEXT PRIMARY KEY, - password TEXT NOT NULL, - fullname TEXT NOT NULL, - twitter TEXT NOT NULL, - timestamp DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP + nickname TEXT, + password TEXT NOT NULL, + fullname TEXT NOT NULL, + timestamp DATETIME NOT NULL + DEFAULT CURRENT_TIMESTAMP, + user_id INTEGER PRIMARY KEY AUTOINCREMENT ); COMMIT; diff --git a/sql/sqitch.plan b/sql/sqitch.plan index 686b260..a490023 100644 --- a/sql/sqitch.plan +++ b/sql/sqitch.plan @@ -4,3 +4,6 @@ users 2017-07-22T06:53:03Z Denis Fedoseev # Creates table to track our users. images 2017-07-22T07:17:52Z Denis Fedoseev # Creates table to track users images. +albums 2017-07-22T08:50:11Z Denis Fedoseev # Albums database init +user_images [users images] 2017-07-22T09:16:20Z Denis Fedoseev # +user_images table +album_images [images albums] 2017-07-22T09:21:13Z Denis Fedoseev # +images to album mapping diff --git a/sql/verify/users.sql b/sql/verify/users.sql index f5d4aa8..9e0aef0 100644 --- a/sql/verify/users.sql +++ b/sql/verify/users.sql @@ -2,7 +2,7 @@ BEGIN; -SELECT nickname, password, fullname, twitter +SELECT user_id, nickname, password, fullname, timestamp FROM users WHERE 0; -- 2.43.0 From d7f8f7dcd5ee17d301572e798c97ba0db110d5b7 Mon Sep 17 00:00:00 2001 From: Denis Fedoseev Date: Wed, 26 Jul 2017 14:16:18 +0900 Subject: [PATCH 6/8] Tidy --- fotostore.pl | 227 +++++++++++++++++++++++++++------------------------ 1 file changed, 119 insertions(+), 108 deletions(-) diff --git a/fotostore.pl b/fotostore.pl index dec749b..cfd4d64 100644 --- a/fotostore.pl +++ b/fotostore.pl @@ -2,7 +2,8 @@ use strict; use warnings; -use Mojolicious::Lite; # app, get, post is exported. +use lib 'lib'; +use Mojolicious::Lite; # app, get, post is exported. use File::Basename 'basename'; use File::Path 'mkpath'; @@ -13,66 +14,67 @@ use Imager; use DBI; use Digest::SHA; -my $config = plugin 'Config'=> {file => 'application.conf'};; +use FotoStore::DB; -my $predefined_user = 'alpha6'; -my $predefined_password = $config->{'password'}; +use Data::Dumper; +$Data::Dumper::Maxdepth = 3; -die 'No user password defined!' unless($predefined_password); +my $config = plugin 'Config' => { file => 'application.conf' }; -my $dbh = DBI->connect(sprintf('dbi:SQLite:dbname=%s', $config->{'db_file'}),"",""); +my $db = FotoStore::DB->new( $config->{'db_file'} ); # Image base URL my $IMAGE_BASE = 'images'; -my $ORIG_DIR = 'orig'; +my $ORIG_DIR = 'orig'; my $thumbs_size = 200; -my @scale_width = ($thumbs_size, 640, 800, 1024); +my @scale_width = ( $thumbs_size, 640, 800, 1024 ); + +my $sha = Digest::SHA->new('sha256'); # Directory to save image files # (app is Mojolicious object. static is MojoX::Dispatcher::Static object) -my $IMAGE_DIR = File::Spec->catfile(getcwd(), 'public', $IMAGE_BASE); +my $IMAGE_DIR = File::Spec->catfile( getcwd(), 'public', $IMAGE_BASE ); # Create directory if not exists -unless (-d $IMAGE_DIR) { +unless ( -d $IMAGE_DIR ) { mkpath $IMAGE_DIR or die "Cannot create directory: $IMAGE_DIR"; } -my $ORIG_PATH = File::Spec->catfile($IMAGE_DIR, $ORIG_DIR); -unless (-d $ORIG_PATH) { +my $ORIG_PATH = File::Spec->catfile( $IMAGE_DIR, $ORIG_DIR ); +unless ( -d $ORIG_PATH ) { mkpath $ORIG_PATH or die "Cannot create directory: $ORIG_PATH"; } for my $dir (@scale_width) { - my $scaled_dir_path = File::Spec->catfile($IMAGE_DIR, $dir); - unless (-d $scaled_dir_path) { - mkpath $scaled_dir_path or die "Cannot create directory: $scaled_dir_path"; - } + my $scaled_dir_path = File::Spec->catfile( $IMAGE_DIR, $dir ); + unless ( -d $scaled_dir_path ) { + mkpath $scaled_dir_path + or die "Cannot create directory: $scaled_dir_path"; + } } plugin 'authentication', { autoload_user => 1, - load_user => sub { + load_user => sub { my $self = shift; my $uid = shift; - - return { - 'username' => $predefined_user, - 'password' => $predefined_password, - 'name' => 'User Name' - } if ($uid eq 'userid' || $uid eq 'useridwithextradata'); - return undef; + + return $db->get_user($uid); }, validate_user => sub { - my $self = shift; - my $username = shift || ''; - my $password = shift || ''; + my $self = shift; + my $username = shift || ''; + my $password = shift || ''; my $extradata = shift || {}; - - # return 'useridwithextradata' if($username eq 'alpha6' && $password eq 'qwerty' && ( $extradata->{'ohnoes'} || '' ) eq 'itsameme'); - return 'userid' if($username eq $predefined_user && $password eq $predefined_password); - return undef; + + my $digest = $sha->add($password); + + my $user_id = $db->check_user( $username, $digest->hexdigest() ); + $self->app->log->debug("user id: [$user_id]"); + + return $user_id; }, }; @@ -80,56 +82,69 @@ post '/login' => sub { my $self = shift; my $u = $self->req->param('username'); my $p = $self->req->param('password'); - - if ($self->authenticate($u, $p)) { + + if ( $self->authenticate( $u, $p ) ) { $self->redirect_to('/'); - } else { - $self->render(text => 'Login failed :('); } - + else { + $self->render( text => 'Login failed :(' ); + } + }; get '/logout' => sub { my $self = shift; - + $self->logout(); - $self->render(text => 'bye'); + $self->render( text => 'bye' ); }; # Display top page get '/' => sub { my $self = shift; - - my $thumbs_dir = File::Spec->catfile($IMAGE_DIR, $thumbs_size); + + my $thumbs_dir = File::Spec->catfile( $IMAGE_DIR, $thumbs_size ); + # Get file names(Only base name) - my @images = map {basename($_)} glob("$thumbs_dir/*.jpg $thumbs_dir/*.gif $thumbs_dir/*.png"); - + my @images = + map { basename($_) } + glob("$thumbs_dir/*.jpg $thumbs_dir/*.gif $thumbs_dir/*.png"); + # Sort by new order - @images = sort {$b cmp $a} @images; - + @images = sort { $b cmp $a } @images; + # Render - return $self->render(images => \@images, image_base => $IMAGE_BASE, orig => $ORIG_DIR, thumbs_size => $thumbs_size, scales => \@scale_width); + return $self->render( + images => \@images, + image_base => $IMAGE_BASE, + orig => $ORIG_DIR, + thumbs_size => $thumbs_size, + scales => \@scale_width + ); } => 'index'; # Upload image file -post '/upload' => (authenticated => 1)=> sub { +post '/upload' => ( authenticated => 1 ) => sub { my $self = shift; # Uploaded image(Mojo::Upload object) my $image = $self->req->upload('image'); - + + my $user = $self->current_user(); + $self->app->log->debug( "user:" . Dumper($user) ); + # Not upload unless ($image) { return $self->render( - template => 'error', + template => 'error', message => "Upload fail. File is not specified." ); } - + # Upload max size #my $upload_max_size = 3 * 1024 * 1024; - + # Over max size #if ($image->size > $upload_max_size) { # return $self->render( @@ -137,90 +152,86 @@ post '/upload' => (authenticated => 1)=> sub { # message => "Upload fail. Image size is too large." # ); #} - + # Check file type my $image_type = $image->headers->content_type; - my %valid_types = map {$_ => 1} qw(image/gif image/jpeg image/png); - + my %valid_types = map { $_ => 1 } qw(image/gif image/jpeg image/png); + # Content type is wrong - unless ($valid_types{$image_type}) { + unless ( $valid_types{$image_type} ) { return $self->render( template => 'error', message => "Upload fail. Content type is wrong." ); } - + # Extention - my $exts = {'image/gif' => 'gif', 'image/jpeg' => 'jpg', - 'image/png' => 'png'}; + my $exts = { + 'image/gif' => 'gif', + 'image/jpeg' => 'jpg', + 'image/png' => 'png' + }; my $ext = $exts->{$image_type}; - + # Image file - my $filename = create_filename($ext); - my $image_file = File::Spec->catfile($ORIG_PATH, $filename); - - # If file is exists, Retry creating filename - while(-f $image_file){ - $filename = create_filename(); - $image_file = File::Spec->catfile($ORIG_PATH, $filename); - } - + my $filename = sprintf( '%s.%s', create_hash( $image->slurp() ), $ext ); + my $image_file = File::Spec->catfile( $ORIG_PATH, $filename ); + # Save to file $image->move_to($image_file); - + my $imager = Imager->new(); - $imager->read(file => $image_file) or die $imager->errstr; + $imager->read( file => $image_file ) or die $imager->errstr; #http://sylvana.net/jpegcrop/exif_orientation.html #http://myjaphoo.de/docs/exifidentifiers.html - my $rotation_angle = $imager->tags( name => "exif_orientation") || 1; - $self->app->log->info("Rotation angle [".$rotation_angle."] [".$image->filename."]"); + my $rotation_angle = $imager->tags( name => "exif_orientation" ) || 1; + $self->app->log->info( + "Rotation angle [" . $rotation_angle . "] [" . $image->filename . "]" ); - if ($rotation_angle == 3) { - $imager = $imager->rotate(degrees=>180); - } - elsif ($rotation_angle == 6) { - $imager = $imager->rotate(degrees=>90); - } + if ( $rotation_angle == 3 ) { + $imager = $imager->rotate( degrees => 180 ); + } + elsif ( $rotation_angle == 6 ) { + $imager = $imager->rotate( degrees => 90 ); + } for my $scale (@scale_width) { - my $scaled = $imager->scale(xpixels => $scale); - - $scaled->write(file => File::Spec->catfile($IMAGE_DIR, $scale, $filename)) or die $scaled->errstr; - } - + my $scaled = $imager->scale( xpixels => $scale ); - $self->render(json => {files => [ - { - name => $image->filename, - size => $image->size, - url => sprintf('/images/orig/%s', $filename), - thumbnailUrl => sprintf('/images/200/%s', $filename), - }] - }); + $scaled->write( + file => File::Spec->catfile( $IMAGE_DIR, $scale, $filename ) ) + or die $scaled->errstr; + } + + if ( !$db->add_file( $user->{'user_id'}, $filename ) ) { + + #TODO: Send error msg + } + + $self->render( + json => { + files => [ + { + name => $image->filename, + size => $image->size, + url => sprintf( '/images/orig/%s', $filename ), + thumbnailUrl => sprintf( '/images/200/%s', $filename ), + } + ] + } + ); # Redirect to top page # $self->redirect_to('index'); - + } => 'upload'; -sub create_filename { - my $ext = shift || 'jpg'; - - # Date and time - my ($sec, $min, $hour, $mday, $month, $year) = localtime; - $month = $month + 1; - $year = $year + 1900; - - # Random number(0 ~ 999999) - my $rand_num = int(rand 1000000); +sub create_hash { + my $data_to_hash = shift; - # Create file name form datatime and random number - # (like image-20091014051023-78973) - my $name = sprintf('image-%04s%02s%02s%02s%02s%02s-%06s.%s', - $year, $month, $mday, $hour, $min, $sec, $rand_num, $ext); - - return $name; + $sha->add($data_to_hash); + return $sha->hexdigest(); } -app->start; \ No newline at end of file +app->start; -- 2.43.0 From 072fbe8bf8a9769b0a728632f53e28a6ae18d60e Mon Sep 17 00:00:00 2001 From: Denis Fedoseev Date: Wed, 26 Jul 2017 15:50:45 +0900 Subject: [PATCH 7/8] Storing list of fotos in database --- README | 2 ++ application.conf.example | 1 + cpanfile | 5 +++- fotostore.pl | 49 ++++++++++++++++------------------------ sql/sqitch.plan | 1 + 5 files changed, 28 insertions(+), 30 deletions(-) diff --git a/README b/README index b8221dd..c5eea80 100644 --- a/README +++ b/README @@ -1,2 +1,4 @@ Small script for store images. Based on http://d.hatena.ne.jp/yukikimoto/20100212/1265989676 with small modifications. + +URL format: /images/// diff --git a/application.conf.example b/application.conf.example index 641690a..490b847 100644 --- a/application.conf.example +++ b/application.conf.example @@ -1,3 +1,4 @@ { password => '', + db_file => 'sql/fotostore.db', } \ No newline at end of file diff --git a/cpanfile b/cpanfile index c680ea1..265bb40 100644 --- a/cpanfile +++ b/cpanfile @@ -5,4 +5,7 @@ requires 'File::Basename'; requires 'File::Path'; requires 'File::Spec'; requires 'Cwd'; -requires 'Getopt::Long'; \ No newline at end of file +requires 'Getopt::Long'; +requires 'DBI'; +requires 'DBD::SQLite'; +requires 'Digest::SHA'; \ No newline at end of file diff --git a/fotostore.pl b/fotostore.pl index cfd4d64..543edb5 100644 --- a/fotostore.pl +++ b/fotostore.pl @@ -37,24 +37,6 @@ my $sha = Digest::SHA->new('sha256'); # (app is Mojolicious object. static is MojoX::Dispatcher::Static object) my $IMAGE_DIR = File::Spec->catfile( getcwd(), 'public', $IMAGE_BASE ); -# Create directory if not exists -unless ( -d $IMAGE_DIR ) { - mkpath $IMAGE_DIR or die "Cannot create directory: $IMAGE_DIR"; -} - -my $ORIG_PATH = File::Spec->catfile( $IMAGE_DIR, $ORIG_DIR ); -unless ( -d $ORIG_PATH ) { - mkpath $ORIG_PATH or die "Cannot create directory: $ORIG_PATH"; -} - -for my $dir (@scale_width) { - my $scaled_dir_path = File::Spec->catfile( $IMAGE_DIR, $dir ); - unless ( -d $scaled_dir_path ) { - mkpath $scaled_dir_path - or die "Cannot create directory: $scaled_dir_path"; - } -} - plugin 'authentication', { autoload_user => 1, load_user => sub { @@ -103,15 +85,13 @@ get '/logout' => sub { get '/' => sub { my $self = shift; - my $thumbs_dir = File::Spec->catfile( $IMAGE_DIR, $thumbs_size ); + my $current_user = $self->current_user; - # Get file names(Only base name) - my @images = - map { basename($_) } - glob("$thumbs_dir/*.jpg $thumbs_dir/*.gif $thumbs_dir/*.png"); - - # Sort by new order - @images = sort { $b cmp $a } @images; + my $files_list = $db->get_files($current_user->{'user_id'}, 20); + + my $thumbs_dir = File::Spec->catfile( $IMAGE_DIR, $current_user->{'user_id'}, $thumbs_size ); + + my @images = map { $_->{'file_name'} } @$files_list; # Render return $self->render( @@ -119,7 +99,8 @@ get '/' => sub { image_base => $IMAGE_BASE, orig => $ORIG_DIR, thumbs_size => $thumbs_size, - scales => \@scale_width + scales => \@scale_width, + user_id => $current_user->{'user_id'}, ); } => 'index'; @@ -132,6 +113,7 @@ post '/upload' => ( authenticated => 1 ) => sub { my $image = $self->req->upload('image'); my $user = $self->current_user(); + my $user_id = $user->{'user_id'}; $self->app->log->debug( "user:" . Dumper($user) ); # Not upload @@ -175,7 +157,7 @@ post '/upload' => ( authenticated => 1 ) => sub { # Image file my $filename = sprintf( '%s.%s', create_hash( $image->slurp() ), $ext ); - my $image_file = File::Spec->catfile( $ORIG_PATH, $filename ); + my $image_file = File::Spec->catfile( get_path($user_id, $ORIG_DIR), $filename ); # Save to file $image->move_to($image_file); @@ -200,7 +182,7 @@ post '/upload' => ( authenticated => 1 ) => sub { my $scaled = $imager->scale( xpixels => $scale ); $scaled->write( - file => File::Spec->catfile( $IMAGE_DIR, $scale, $filename ) ) + file => File::Spec->catfile( get_path($user_id, $scale), $filename ) ) or die $scaled->errstr; } @@ -234,4 +216,13 @@ sub create_hash { return $sha->hexdigest(); } +sub get_path { + my ($user_id, $size) = @_; + my $path = File::Spec->catfile( $IMAGE_DIR, $user_id, $size ); + unless (-d $path) { + mkpath $path or die "Cannot create directory: $path"; + } + return $path; +} + app->start; diff --git a/sql/sqitch.plan b/sql/sqitch.plan index a490023..1f26348 100644 --- a/sql/sqitch.plan +++ b/sql/sqitch.plan @@ -7,3 +7,4 @@ images 2017-07-22T07:17:52Z Denis Fedoseev # Creates albums 2017-07-22T08:50:11Z Denis Fedoseev # Albums database init user_images [users images] 2017-07-22T09:16:20Z Denis Fedoseev # +user_images table album_images [images albums] 2017-07-22T09:21:13Z Denis Fedoseev # +images to album mapping +user_albums [users albums] 2017-07-22T09:47:48Z Denis Fedoseev # Albums to users mapping -- 2.43.0 From bd5cafcba1d02a597b25238f0da3522384365a85 Mon Sep 17 00:00:00 2001 From: Denis Fedoseev Date: Mon, 31 Jul 2017 10:14:59 +0300 Subject: [PATCH 8/8] Database support --- application.conf.example | 2 +- fotostore.pl | 39 ++++++++++++++++ lib/FotoStore/DB.pm | 67 +++++++++++++++++++++++++++ public/css/main.css | 9 ++++ public/file_uploader/.gitignore | 3 ++ public/file_uploader/.jshintrc | 81 +++++++++++++++++++++++++++++++++ public/file_uploader/.npmignore | 20 ++++++++ sql/deploy/album_images.sql | 14 ++++++ sql/deploy/albums.sql | 14 ++++++ sql/deploy/user_albums.sql | 14 ++++++ sql/deploy/user_images.sql | 12 +++++ sql/revert/album_images.sql | 7 +++ sql/revert/albums.sql | 7 +++ sql/revert/user_albums.sql | 7 +++ sql/revert/user_images.sql | 7 +++ sql/verify/album_images.sql | 7 +++ sql/verify/albums.sql | 7 +++ sql/verify/user_albums.sql | 7 +++ sql/verify/user_images.sql | 7 +++ templates/error.html.ep | 10 ++++ templates/images_list.html.ep | 54 ++++++++++++++++++++++ templates/index.html.ep | 18 ++++++++ templates/layouts/base.html.ep | 29 ++++++++++++ templates/no_logged.html.ep | 17 +++++++ templates/register.html.ep | 25 ++++++++++ 25 files changed, 483 insertions(+), 1 deletion(-) mode change 100644 => 100755 fotostore.pl create mode 100644 lib/FotoStore/DB.pm create mode 100644 public/css/main.css create mode 100755 public/file_uploader/.gitignore create mode 100755 public/file_uploader/.jshintrc create mode 100755 public/file_uploader/.npmignore create mode 100644 sql/deploy/album_images.sql create mode 100644 sql/deploy/albums.sql create mode 100644 sql/deploy/user_albums.sql create mode 100644 sql/deploy/user_images.sql create mode 100644 sql/revert/album_images.sql create mode 100644 sql/revert/albums.sql create mode 100644 sql/revert/user_albums.sql create mode 100644 sql/revert/user_images.sql create mode 100644 sql/verify/album_images.sql create mode 100644 sql/verify/albums.sql create mode 100644 sql/verify/user_albums.sql create mode 100644 sql/verify/user_images.sql create mode 100644 templates/error.html.ep create mode 100644 templates/images_list.html.ep create mode 100644 templates/index.html.ep create mode 100644 templates/layouts/base.html.ep create mode 100644 templates/no_logged.html.ep create mode 100644 templates/register.html.ep diff --git a/application.conf.example b/application.conf.example index 490b847..078d6e5 100644 --- a/application.conf.example +++ b/application.conf.example @@ -1,4 +1,4 @@ { - password => '', db_file => 'sql/fotostore.db', + invite_code => 'very_secure_invite_code', } \ No newline at end of file diff --git a/fotostore.pl b/fotostore.pl old mode 100644 new mode 100755 index 543edb5..a3a559d --- a/fotostore.pl +++ b/fotostore.pl @@ -81,6 +81,45 @@ get '/logout' => sub { $self->render( text => 'bye' ); }; +get '/register' => ( authenticated => 0 ) => sub { + +}; + +post '/register' => ( authenticated => 0 ) => sub { + my $self = shift; + my $username = $self->req->param('username'); + my $password = $self->req->param('password'); + my $fullname = $self->req->param('fullname'); + my $invite = $self->req->param('invite'); + + if ($invite eq $config->{'invite_code'}) { + #chek that username is not taken + my $user = $db->get_user($username); + if ($user->{'user_id'} > 0) { + $self->render(template => 'error', message => 'Username already taken!'); + return 0; + } + + if ($fullname eq '') { + $fullname = $username; + } + + my $digest = $sha->add($password); + $db->add_user($username, $digest->hexdigest(), $fullname); + + #Authenticate user after add + if ( $self->authenticate( $username, $password ) ) { + $self->redirect_to('/'); + } + else { + $self->render( text => 'Login failed :(' ); + } + + } else { + $self->render(template => 'error', message => 'invalid invite code'); + } +}; + # Display top page get '/' => sub { my $self = shift; diff --git a/lib/FotoStore/DB.pm b/lib/FotoStore/DB.pm new file mode 100644 index 0000000..1ea57ed --- /dev/null +++ b/lib/FotoStore/DB.pm @@ -0,0 +1,67 @@ +package FotoStore::DB; + +use strict; +use warnings; + +use feature qw(signatures); +no warnings qw(experimental::signatures); + +sub new { + my $class = shift; + my $db_file = shift; + + my $dbh = DBI->connect(sprintf('dbi:SQLite:dbname=%s', $db_file),"",""); + my $self = { + dbh => $dbh, + }; + bless $self, $class; + return $self; +} + +sub check_user ($self, $nickname, $password) { + print STDERR "[$nickname][$password]"; + my ($user_id) = $self->{'dbh'}->selectrow_array(q~select user_id from users where nickname=? and password=?~, undef, ($nickname, $password)); + return $user_id; +} + +sub get_user ($self, $user_id) { + if ($user_id =~ /^\d+$/) { + return $self->_get_user_by_user_id($user_id); + } else { + return $self->_get_user_by_username($user_id); + } +} + +sub _get_user_by_user_id ($self, $user_id) { + my $user_data = $self->{'dbh'}->selectrow_hashref(q~select user_id, nickname, fullname, timestamp from users where user_id=?~, {}, ($user_id)); + return $user_data; +} + +sub _get_user_by_username($self, $username) { + my $user_data = $self->{'dbh'}->selectrow_hashref(q~select user_id, nickname, fullname, timestamp from users where nickname=?~, {}, ($username)); + return $user_data; +} + + +sub add_user($self, $username, $password, $fullname) { + my $rows = $self->{'dbh'}->do(q~insert into users (nickname, password, fullname) values (?, ?, ?)~, undef, ($username, $password, $fullname)); + if ($self->{'dbh'}->errstr) { + die $self->{'dbh'}->errstr; + } + return $rows; +} + + +sub add_file($self, $user_id, $filename) { + my $rows = $self->{'dbh'}->do(q~insert into images (owner_id, file_name) values (?, ?)~, undef, ($user_id, $filename)); + if ($self->{'dbh'}->errstr) { + die $self->{'dbh'}->errstr; + } + return $rows; +} + +sub get_files($self, $user_id, $count=20, $start_at=0) { + return $self->{'dbh'}->selectall_arrayref(q~select * from images where owner_id=? order by created_time desc~, { Slice => {} }, $user_id ); +} + +1; \ No newline at end of file diff --git a/public/css/main.css b/public/css/main.css new file mode 100644 index 0000000..58d8c9b --- /dev/null +++ b/public/css/main.css @@ -0,0 +1,9 @@ +.foto-block { + /* border: 1px solid black; */ +} +.foto-block .image { + padding: 5px; +} +.foto-block .foto-notes { + padding: 5px; +} \ No newline at end of file diff --git a/public/file_uploader/.gitignore b/public/file_uploader/.gitignore new file mode 100755 index 0000000..29a41a8 --- /dev/null +++ b/public/file_uploader/.gitignore @@ -0,0 +1,3 @@ +.DS_Store +*.pyc +node_modules diff --git a/public/file_uploader/.jshintrc b/public/file_uploader/.jshintrc new file mode 100755 index 0000000..4ad82e6 --- /dev/null +++ b/public/file_uploader/.jshintrc @@ -0,0 +1,81 @@ +{ + "bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.) + "camelcase" : true, // true: Identifiers must be in camelCase + "curly" : true, // true: Require {} for every new block or scope + "eqeqeq" : true, // true: Require triple equals (===) for comparison + "forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty() + "immed" : true, // true: Require immediate invocations to be wrapped in parens + // e.g. `(function () { } ());` + "indent" : 4, // {int} Number of spaces to use for indentation + "latedef" : true, // true: Require variables/functions to be defined before being used + "newcap" : true, // true: Require capitalization of all constructor functions e.g. `new F()` + "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee` + "noempty" : true, // true: Prohibit use of empty blocks + "nonew" : true, // true: Prohibit use of constructors for side-effects (without assignment) + "plusplus" : false, // true: Prohibit use of `++` & `--` + "quotmark" : "single", // Quotation mark consistency: + // false : do nothing (default) + // true : ensure whatever is used is consistent + // "single" : require single quotes + // "double" : require double quotes + "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks) + "unused" : true, // true: Require all defined variables be used + "strict" : true, // true: Requires all functions run in ES5 Strict Mode + "trailing" : true, // true: Prohibit trailing whitespaces + "maxparams" : false, // {int} Max number of formal params allowed per function + "maxdepth" : false, // {int} Max depth of nested blocks (within functions) + "maxstatements" : false, // {int} Max number statements per function + "maxcomplexity" : false, // {int} Max cyclomatic complexity per function + "maxlen" : false, // {int} Max number of characters per line + + // Relaxing + "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons) + "boss" : false, // true: Tolerate assignments where comparisons would be expected + "debug" : false, // true: Allow debugger statements e.g. browser breakpoints. + "eqnull" : false, // true: Tolerate use of `== null` + "es5" : false, // true: Allow ES5 syntax (ex: getters and setters) + "esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`) + "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features) + // (ex: `for each`, multiple try/catch, function expression…) + "evil" : false, // true: Tolerate use of `eval` and `new Function()` + "expr" : false, // true: Tolerate `ExpressionStatement` as Programs + "funcscope" : false, // true: Tolerate defining variables inside control statements" + "globalstrict" : false, // true: Allow global "use strict" (also enables 'strict') + "iterator" : false, // true: Tolerate using the `__iterator__` property + "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block + "laxbreak" : false, // true: Tolerate possibly unsafe line breakings + "laxcomma" : false, // true: Tolerate comma-first style coding + "loopfunc" : false, // true: Tolerate functions being defined in loops + "multistr" : false, // true: Tolerate multi-line strings + "proto" : false, // true: Tolerate using the `__proto__` property + "scripturl" : false, // true: Tolerate script-targeted URLs + "smarttabs" : false, // true: Tolerate mixed tabs/spaces when used for alignment + "shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;` + "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation + "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;` + "validthis" : false, // true: Tolerate using this in a non-constructor function + + // Environments + "browser" : false, // Web Browser (window, document, etc) + "couch" : false, // CouchDB + "devel" : false, // Development/debugging (alert, confirm, etc) + "dojo" : false, // Dojo Toolkit + "jquery" : false, // jQuery + "mootools" : false, // MooTools + "node" : false, // Node.js + "nonstandard" : false, // Widely adopted globals (escape, unescape, etc) + "prototypejs" : false, // Prototype and Scriptaculous + "rhino" : false, // Rhino + "worker" : false, // Web Workers + "wsh" : false, // Windows Scripting Host + "yui" : false, // Yahoo User Interface + + // Legacy + "nomen" : true, // true: Prohibit dangling `_` in variables + "onevar" : true, // true: Allow only one `var` statement per function + "passfail" : false, // true: Stop on first error + "white" : true, // true: Check against strict whitespace and indentation rules + + // Custom Globals + "globals" : {} // additional predefined global variables +} diff --git a/public/file_uploader/.npmignore b/public/file_uploader/.npmignore new file mode 100755 index 0000000..0530f5d --- /dev/null +++ b/public/file_uploader/.npmignore @@ -0,0 +1,20 @@ +* +!css/jquery.fileupload-noscript.css +!css/jquery.fileupload-ui-noscript.css +!css/jquery.fileupload-ui.css +!css/jquery.fileupload.css +!img/loading.gif +!img/progressbar.gif +!js/cors/jquery.postmessage-transport.js +!js/cors/jquery.xdr-transport.js +!js/vendor/jquery.ui.widget.js +!js/jquery.fileupload-angular.js +!js/jquery.fileupload-audio.js +!js/jquery.fileupload-image.js +!js/jquery.fileupload-jquery-ui.js +!js/jquery.fileupload-process.js +!js/jquery.fileupload-ui.js +!js/jquery.fileupload-validate.js +!js/jquery.fileupload-video.js +!js/jquery.fileupload.js +!js/jquery.iframe-transport.js diff --git a/sql/deploy/album_images.sql b/sql/deploy/album_images.sql new file mode 100644 index 0000000..f9a6765 --- /dev/null +++ b/sql/deploy/album_images.sql @@ -0,0 +1,14 @@ +-- Deploy fotostore:album_images to sqlite +-- requires: images +-- requires: albums + +BEGIN; + +CREATE TABLE album_images ( + record_id INTEGER PRIMARY KEY AUTOINCREMENT, + album_id INTEGER REFERENCES albums (album_id), + image_id INTEGER REFERENCES images (file_id) +); + + +COMMIT; diff --git a/sql/deploy/albums.sql b/sql/deploy/albums.sql new file mode 100644 index 0000000..7232617 --- /dev/null +++ b/sql/deploy/albums.sql @@ -0,0 +1,14 @@ +-- Deploy fotostore:albums to sqlite + +BEGIN; + +CREATE TABLE albums ( + album_id INTEGER PRIMARY KEY AUTOINCREMENT, + name STRING NOT NULL, + description TEXT, + created DATETIME DEFAULT (CURRENT_TIMESTAMP), + modified DATETIME DEFAULT (CURRENT_TIMESTAMP), + deleted BOOLEAN +); + +COMMIT; diff --git a/sql/deploy/user_albums.sql b/sql/deploy/user_albums.sql new file mode 100644 index 0000000..d20541d --- /dev/null +++ b/sql/deploy/user_albums.sql @@ -0,0 +1,14 @@ +-- Deploy fotostore:user_albums to sqlite +-- requires: users +-- requires: albums + +BEGIN; + +CREATE TABLE user_albums ( + record_id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER REFERENCES users (user_id), + album_id INTEGER REFERENCES albums (album_id) +); + + +COMMIT; diff --git a/sql/deploy/user_images.sql b/sql/deploy/user_images.sql new file mode 100644 index 0000000..098ecb3 --- /dev/null +++ b/sql/deploy/user_images.sql @@ -0,0 +1,12 @@ +-- Deploy fotostore:user_images to sqlite + +BEGIN; + +CREATE TABLE user_images ( + record_id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER REFERENCES users (user_id) ON DELETE CASCADE, + image_id INTEGER REFERENCES images (file_id) ON DELETE CASCADE +); + + +COMMIT; diff --git a/sql/revert/album_images.sql b/sql/revert/album_images.sql new file mode 100644 index 0000000..2771ccf --- /dev/null +++ b/sql/revert/album_images.sql @@ -0,0 +1,7 @@ +-- Revert fotostore:album_images from sqlite + +BEGIN; + +DROP TABLE album_images; + +COMMIT; diff --git a/sql/revert/albums.sql b/sql/revert/albums.sql new file mode 100644 index 0000000..53e8afa --- /dev/null +++ b/sql/revert/albums.sql @@ -0,0 +1,7 @@ +-- Revert fotostore:albums from sqlite + +BEGIN; + +DROP TABLE albums; + +COMMIT; diff --git a/sql/revert/user_albums.sql b/sql/revert/user_albums.sql new file mode 100644 index 0000000..d769c9b --- /dev/null +++ b/sql/revert/user_albums.sql @@ -0,0 +1,7 @@ +-- Revert fotostore:user_albums from sqlite + +BEGIN; + +DROP TABLE user_albums; + +COMMIT; diff --git a/sql/revert/user_images.sql b/sql/revert/user_images.sql new file mode 100644 index 0000000..8c29ff3 --- /dev/null +++ b/sql/revert/user_images.sql @@ -0,0 +1,7 @@ +-- Revert fotostore:user_images from sqlite + +BEGIN; + +DROP TABLE user_images; + +COMMIT; diff --git a/sql/verify/album_images.sql b/sql/verify/album_images.sql new file mode 100644 index 0000000..45067f5 --- /dev/null +++ b/sql/verify/album_images.sql @@ -0,0 +1,7 @@ +-- Verify fotostore:album_images on sqlite + +BEGIN; + +select record_id, album_id, image_id from album_images where 0; + +ROLLBACK; diff --git a/sql/verify/albums.sql b/sql/verify/albums.sql new file mode 100644 index 0000000..e45afb0 --- /dev/null +++ b/sql/verify/albums.sql @@ -0,0 +1,7 @@ +-- Verify fotostore:albums on sqlite + +BEGIN; + +select album_id, name, description, created, modified, deleted from albums where 0; + +ROLLBACK; diff --git a/sql/verify/user_albums.sql b/sql/verify/user_albums.sql new file mode 100644 index 0000000..241f528 --- /dev/null +++ b/sql/verify/user_albums.sql @@ -0,0 +1,7 @@ +-- Verify fotostore:user_albums on sqlite + +BEGIN; + +select record_id, album_id, user_id from user_albums where 0; + +ROLLBACK; diff --git a/sql/verify/user_images.sql b/sql/verify/user_images.sql new file mode 100644 index 0000000..50183a4 --- /dev/null +++ b/sql/verify/user_images.sql @@ -0,0 +1,7 @@ +-- Verify fotostore:user_images on sqlite + +BEGIN; + +select record_id, user_id, image_id from user_images where 0; + +ROLLBACK; diff --git a/templates/error.html.ep b/templates/error.html.ep new file mode 100644 index 0000000..a57d2e8 --- /dev/null +++ b/templates/error.html.ep @@ -0,0 +1,10 @@ + + + + Error + + + <%= $message %> + + + diff --git a/templates/images_list.html.ep b/templates/images_list.html.ep new file mode 100644 index 0000000..865ef6e --- /dev/null +++ b/templates/images_list.html.ep @@ -0,0 +1,54 @@ +
+ +
+ + + + + + +
+
+
+
+
+
+ + +
+
+<% foreach my $image (@$images) { %> +
+
+ "> +
+
+ Image original + <% for my $scale (@$scales) { %> + <%= $scale %> + <% } %> +
+ +
+<% } %> +
+
\ No newline at end of file diff --git a/templates/index.html.ep b/templates/index.html.ep new file mode 100644 index 0000000..bcf2fac --- /dev/null +++ b/templates/index.html.ep @@ -0,0 +1,18 @@ +% layout 'base'; +

Rough, Slow, Stupid, Contrary Photohosting

+ +<% if (is_user_authenticated()) { %> + %= include 'images_list' +<% } else { %> + + + +<% } %> + + diff --git a/templates/layouts/base.html.ep b/templates/layouts/base.html.ep new file mode 100644 index 0000000..a2dc11e --- /dev/null +++ b/templates/layouts/base.html.ep @@ -0,0 +1,29 @@ + + + + Rough, Slow, Stupid, Contrary Photohosting + + + + + + + + + + + + + + + + + + <%= content %> + + \ No newline at end of file diff --git a/templates/no_logged.html.ep b/templates/no_logged.html.ep new file mode 100644 index 0000000..a63909b --- /dev/null +++ b/templates/no_logged.html.ep @@ -0,0 +1,17 @@ + + + + Rough, Slow, Stupid, Contrary Photohosting + + +

Rough, Slow, Stupid, Contrary Photohosting

+
+
+ + + +
+
+ + + diff --git a/templates/register.html.ep b/templates/register.html.ep new file mode 100644 index 0000000..09f7823 --- /dev/null +++ b/templates/register.html.ep @@ -0,0 +1,25 @@ +% layout 'base'; + +
+
+
+
+

+ Login: + +

+ Fullname: + +

+ Password: + +

+ Invite code: + +

+ +

+
+
+
+
\ No newline at end of file -- 2.43.0