Database support #1

Merged
alpha6 merged 8 commits from database_support into master 2017-07-31 10:19:57 +03:00
25 changed files with 483 additions and 1 deletions
Showing only changes of commit bd5cafcba1 - Show all commits

View file

@ -1,4 +1,4 @@
{
password => '',
db_file => 'sql/fotostore.db',
invite_code => 'very_secure_invite_code',
}

39
fotostore.pl Normal file → Executable file
View file

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

67
lib/FotoStore/DB.pm Normal file
View file

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

9
public/css/main.css Normal file
View file

@ -0,0 +1,9 @@
.foto-block {
/* border: 1px solid black; */
}
.foto-block .image {
padding: 5px;
}
.foto-block .foto-notes {
padding: 5px;
}

3
public/file_uploader/.gitignore vendored Executable file
View file

@ -0,0 +1,3 @@
.DS_Store
*.pyc
node_modules

81
public/file_uploader/.jshintrc Executable file
View file

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

20
public/file_uploader/.npmignore Executable file
View file

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

View file

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

14
sql/deploy/albums.sql Normal file
View file

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

View file

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

View file

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

View file

@ -0,0 +1,7 @@
-- Revert fotostore:album_images from sqlite
BEGIN;
DROP TABLE album_images;
COMMIT;

7
sql/revert/albums.sql Normal file
View file

@ -0,0 +1,7 @@
-- Revert fotostore:albums from sqlite
BEGIN;
DROP TABLE albums;
COMMIT;

View file

@ -0,0 +1,7 @@
-- Revert fotostore:user_albums from sqlite
BEGIN;
DROP TABLE user_albums;
COMMIT;

View file

@ -0,0 +1,7 @@
-- Revert fotostore:user_images from sqlite
BEGIN;
DROP TABLE user_images;
COMMIT;

View file

@ -0,0 +1,7 @@
-- Verify fotostore:album_images on sqlite
BEGIN;
select record_id, album_id, image_id from album_images where 0;
ROLLBACK;

7
sql/verify/albums.sql Normal file
View file

@ -0,0 +1,7 @@
-- Verify fotostore:albums on sqlite
BEGIN;
select album_id, name, description, created, modified, deleted from albums where 0;
ROLLBACK;

View file

@ -0,0 +1,7 @@
-- Verify fotostore:user_albums on sqlite
BEGIN;
select record_id, album_id, user_id from user_albums where 0;
ROLLBACK;

View file

@ -0,0 +1,7 @@
-- Verify fotostore:user_images on sqlite
BEGIN;
select record_id, user_id, image_id from user_images where 0;
ROLLBACK;

10
templates/error.html.ep Normal file
View file

@ -0,0 +1,10 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" >
<title>Error</title>
</head>
<body>
<%= $message %>
</body>
</html>

View file

@ -0,0 +1,54 @@
<div class="container">
<div class="logout"><a href="/logout">Logout</a></div>
<div class"upload-form">
<input id="fileupload" type="file" name="image" data-url="/upload" multiple>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="/file_uploader/js/vendor/jquery.ui.widget.js"></script>
<script src="/file_uploader/js/jquery.iframe-transport.js"></script>
<script src="/file_uploader/js/jquery.fileupload.js"></script>
<script>
$(function () {
$('#fileupload').fileupload({
dataType: 'json',
done: function (e, data) {
$.each(data.result.files, function (index, file) {
$('<p/>').text(file.name).appendTo('#lastUploadLog');
});
},
sequentialUploads: true,
progressall: function (e, data) {
var progress = parseInt(data.loaded / data.total * 100, 10);
$('#progress .bar').css(
'width',
progress + '%'
);
}
});
});
</script>
</div>
<div id="progress">
<div class="bar" style="width: 0%;"></div>
</div>
<div id="lastUploadLog"></div>
</div>
<!-- display images from server -->
<div class="container">
<div class="row">
<% foreach my $image (@$images) { %>
<div class="foto-block col-md-3">
<div class="image">
<img src="<%= "/$image_base/$user_id/$thumbs_size/$image" %>">
</div>
<div class="foto-notes">
<a href='<%= "/$image_base/$user_id/$orig/$image" %>'>Image original</a>
<% for my $scale (@$scales) { %>
<a href='<%= "/$image_base/$user_id/$scale/$image" %>'><%= $scale %></a>
<% } %>
</div>
</div>
<% } %>
</div>
</div>

18
templates/index.html.ep Normal file
View file

@ -0,0 +1,18 @@
% layout 'base';
<h1>Rough, Slow, Stupid, Contrary Photohosting</h1>
<% if (is_user_authenticated()) { %>
%= include 'images_list'
<% } else { %>
<div class="login-form">
<form method="post" action="<%= url_for('login') %>" >
<input type="text" name="username" >
<input type="password" name="password">
<input type="submit" value="Login">
</form>
</div>
<% } %>

View file

@ -0,0 +1,29 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" >
<title>Rough, Slow, Stupid, Contrary Photohosting</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Bootstrap styles -->
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
<!-- Generic page styles -->
<link rel="stylesheet" href="/file_uploader/css/style.css">
<!-- blueimp Gallery styles -->
<link rel="stylesheet" href="//blueimp.github.io/Gallery/css/blueimp-gallery.min.css">
<!-- CSS to style the file input field as button and adjust the Bootstrap progress bars -->
<link rel="stylesheet" href="/file_uploader/css/jquery.fileupload.css">
<link rel="stylesheet" href="/file_uploader/css/jquery.fileupload-ui.css">
<link rel="stylesheet" href="/css/main.css">
<!-- CSS adjustments for browsers with JavaScript disabled -->
<noscript><link rel="stylesheet" href="/file_uploader/css/jquery.fileupload-noscript.css"></noscript>
<noscript><link rel="stylesheet" href="/file_uploader/css/jquery.fileupload-ui-noscript.css"></noscript>
<style>
.bar {
height: 18px;
background: green;
}
</style>
</head>
<body>
<%= content %>
</body>
</html>

View file

@ -0,0 +1,17 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" >
<title>Rough, Slow, Stupid, Contrary Photohosting</title>
</head>
<body>
<h1>Rough, Slow, Stupid, Contrary Photohosting</h1>
<form method="post" action="<%= url_for('login') %>" >
<div>
<input type="text" name="username" >
<input type="password" name="password">
<input type="submit" value="Login">
</div>
</form>
</body>
</html>

View file

@ -0,0 +1,25 @@
% layout 'base';
<div class="container">
<div class="register">
<form method="post" action="<%= url_for('register') %>" >
<div>
<p>
Login:
<input type="text" name="username" >
</p><p>
Fullname:
<input type="text" name="Fullname" >
</p><p>
Password:
<input type="password" name="password">
</p><p>
Invite code:
<input type="text" name="invite" >
</p><p>
<input type="submit" value="Register">
</p>
</div>
</form>
</div>
</div>