Commit a6746f5e authored by Pierre Rudloff's avatar Pierre Rudloff

MySQL backend (fixes #9)

parent a3a50374
......@@ -2,6 +2,7 @@ image: hatsoftwares/test-ci:latest
stages:
- sqlite
- postgresql
- mysql
before_script:
- carton install
- rm -f *db
......@@ -39,3 +40,22 @@ postgresql:
tags:
- Debian
- Jessie
mysql:
stage: mysql
cache:
key: "$CI_BUILD_REF_NAME"
untracked: true
paths:
- local
script:
- service mysql restart
- sleep 10
- service mysql status
- mysql -u root < t/test_mysql.sql
- MOJO_CONFIG=t/mysql3.conf make minion &
- MOJO_CONFIG=t/mysql1.conf make test
- MOJO_CONFIG=t/mysql2.conf make test
- MOJO_CONFIG=t/mysql3.conf make test
tags:
- Debian
- Jessie
requires 'Mojolicious', '>= 4.63';
requires 'ORLite';
requires 'Mojo::mysql';
requires 'Mojo::Pg';
requires 'Data::Validate::URI';
requires 'Net::Domain::TLD', '>= 1.74';
......
This diff is collapsed.
......@@ -65,6 +65,9 @@ sub new {
} elsif ($dbtype eq 'postgresql') {
use Lstu::DB::Ban::Pg;
$c = Lstu::DB::Ban::Pg->new(@_);
} elsif ($dbtype eq 'mysql') {
use Lstu::DB::Ban::MySQL;
$c = Lstu::DB::Ban::MySQL->new(@_);
}
}
......
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lstu::DB::Ban::MySQL;
use Mojo::Base 'Lstu::DB::Ban';
has 'record' => 0;
sub new {
my $c = shift;
$c = $c->SUPER::new(@_);
$c = $c->_slurp if ($c->ip);
return $c;
}
sub is_banned {
my $c = shift;
my $ban_min_strike = shift;
return undef if $c->is_whitelisted;
my $h = $c->app->mysql->db->query('SELECT * FROM ban WHERE ip = ? AND until > ? AND strike >= ?', $c->ip, time, $ban_min_strike)->hashes;
if ($h->size) {
$c->until($h->first->{until});
$c->strike($h->first->{strike});
$c->record(1);
return $c;
} else {
return undef;
}
}
sub increment_ban_delay {
my $c = shift;
my $penalty = shift;
my $until = time + $penalty;
my $h = {
strike => 1
};
if ($c->record) {
$c->app->mysql->db->query('UPDATE ban SET until = ?, strike = strike + 1 WHERE ip = ?', $until, $c->ip);
$h = $c->app->mysql->db->query('SELECT strike FROM ban WHERE ip = ?', $c->ip)->hashes->first;
} else {
$c->app->mysql->db->query('INSERT INTO ban (ip, until, strike) VALUES (?, ?, 1)', $c->ip, $until);
$c->record(1);
}
$c->strike($h->{strike});
$c->until($until);
return $c;
}
sub clear {
my $c = shift;
$c->app->mysql->db->query('DELETE FROM ban WHERE until < ?', time);
}
sub delete_all {
my $c = shift;
$c->app->mysql->db->query('DELETE FROM ban');
}
sub _slurp {
my $c = shift;
my $h = $c->app->mysql->db->query('SELECT * FROM ban WHERE ip = ?', $c->ip)->hashes;
if ($h->size) {
$c->until($h->first->{until});
$c->strike($h->first->{strike});
$c->record(1);
}
return $c;
}
1;
......@@ -62,6 +62,9 @@ sub new {
} elsif ($dbtype eq 'postgresql') {
use Lstu::DB::Session::Pg;
$c = Lstu::DB::Session::Pg->new(@_);
} elsif ($dbtype eq 'mysql') {
use Lstu::DB::Session::MySQL;
$c = Lstu::DB::Session::MySQL->new(@_);
}
}
......
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lstu::DB::Session::MySQL;
use Mojo::Base 'Lstu::DB::Session';
has 'record' => 0;
sub new {
my $c = shift;
$c = $c->SUPER::new(@_);
$c = $c->_slurp if ($c->token);
return $c;
}
sub delete {
my $c = shift;
$c->app->mysql->db->query('DELETE FROM sessions WHERE token = ?', $c->token);
my $h = $c->app->mysql->db->query('SELECT * FROM sessions WHERE token = ?', $c->token)->hashes;
if ($h->size) {
$c = Lstu::DB::Session->new(app => $c->app);
}
return $h->size;
}
sub write {
my $c = shift;
if ($c->record) {
$c->app->mysql->db->query('UPDATE sessions SET until = ? WHERE token = ?', $c->until, $c->token);
} else {
$c->app->mysql->db->query('INSERT INTO sessions (token, until) VALUES (?, ?)', $c->token, $c->until);
$c->record(1);
}
return $c;
}
sub clear {
my $c = shift;
$c->app->mysql->db->query('DELETE FROM sessions WHERE until < ?', time);
}
sub delete_all {
my $c = shift;
$c->app->mysql->db->query('DELETE FROM sessions');
}
sub _slurp {
my $c = shift;
my $h = $c->app->mysql->db->query('SELECT * FROM sessions WHERE token = ?', $c->token)->hashes;
if ($h->size) {
$c->token($h->first->{token});
$c->until($h->first->{until});
$c->record(1);
}
return $c;
}
1;
......@@ -68,6 +68,9 @@ sub new {
} elsif ($dbtype eq 'postgresql') {
use Lstu::DB::URL::Pg;
$c = Lstu::DB::URL::Pg->new(@_);
} elsif ($dbtype eq 'mysql') {
use Lstu::DB::URL::MySQL;
$c = Lstu::DB::URL::MySQL->new(@_);
}
}
......
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lstu::DB::URL::MySQL;
use Mojo::Base 'Lstu::DB::URL';
has 'record' => 0;
sub new {
my $c = shift;
$c = $c->SUPER::new(@_);
$c = $c->_slurp if ($c->short || $c->url);
return $c;
}
sub increment_counter {
my $c = shift;
$c->app->mysql->db->query('UPDATE lstu SET counter = counter + 1 WHERE short = ?', $c->short);
my $h = $c->app->mysql->db->query('SELECT counter FROM lstu WHERE short = ?', $c->short)->hashes->first;
$c->counter($h->{counter});
return $c;
}
sub write {
my $c = shift;
if ($c->record) {
$c->app->mysql->db->query('UPDATE lstu SET url = ?, counter = ?, timestamp = ? WHERE short = ?', $c->url, $c->counter, $c->timestamp, $c->short);
} else {
$c->app->mysql->db->query('INSERT INTO lstu (short, url, counter, timestamp) VALUES (?, ?, ?, ?)', $c->short, $c->url, $c->counter, $c->timestamp);
$c->record(1);
}
return $c;
}
sub delete {
my $c = shift;
$c->app->mysql->db->query('DELETE FROM lstu WHERE short = ?', $c->short);
my $h = $c->app->mysql->db->query('SELECT * FROM lstu WHERE short = ?', $c->short)->hashes;
$c->app->debug($h->size);
if ($h->size) {
$c = Lstu::DB::URL->new(app => $c->app);
}
return $h->size;
}
sub exist {
my $c = shift;
my $short = shift;
return undef unless $short;
return $c->app->mysql->db->query('SELECT count(short) AS count FROM lstu WHERE short = ?', $short)->hashes->first->{count};
}
sub choose_empty {
my $c = shift;
my $h = $c->app->mysql->db->query('SELECT * FROM lstu WHERE url IS NULL LIMIT 1')->hashes;
if ($h->size) {
$c->short($h->first->{short});
$c->record(1);
return $c;
} else {
return undef;
}
}
sub count_empty {
my $c = shift;
return $c->app->mysql->db->query('SELECT count(short) AS count FROM lstu WHERE url IS NULL')->hashes->first->{count};
}
sub paginate {
my $c = shift;
my $page = shift;
my $page_offset = shift;
return @{$c->app->mysql->db->query('SELECT * FROM lstu WHERE url IS NOT NULL ORDER BY counter DESC LIMIT ? offset ?', $page_offset, $page * $page_offset)->hashes->to_array};
}
sub get_a_lot {
my $c = shift;
my $u = shift;
my $p = join ",", (('?') x @{$u});
return @{$c->app->mysql->db->query('SELECT * FROM lstu WHERE short IN ('.$p.') ORDER BY counter DESC', @{$u})->hashes->to_array};
}
sub total {
my $c = shift;
return $c->app->mysql->db->query('SELECT count(short) AS count FROM lstu WHERE url IS NOT NULL')->hashes->first->{count};
}
sub delete_all {
my $c = shift;
$c->app->mysql->db->query('DELETE FROM lstu');
}
sub _slurp {
my $c = shift;
my $h;
if ($c->short) {
$h = $c->app->mysql->db->query('SELECT * FROM lstu WHERE short = ?', $c->short)->hashes;
} else {
$h = $c->app->mysql->db->query('SELECT * FROM lstu WHERE url = ?', $c->url)->hashes;
}
if ($h->size) {
$c->url($h->first->{url});
$c->short($h->first->{short});
$c->counter($h->first->{counter});
$c->timestamp($h->first->{timestamp});
$c->record(1);
}
return $c;
}
1;
......@@ -20,6 +20,17 @@ sub register {
} else {
$migrations->from_file('utilities/migrations.sql')->migrate(1);
}
} elsif ($app->config('dbtype') eq 'mysql') {
use Mojo::mysql;
$app->helper(mysql => \&_mysql);
# Database migration
my $migrations = Mojo::mysql::Migrations->new(mysql => $app->mysql);
if ($app->mode eq 'development') {
$migrations->from_file('utilities/migrations_mysql.sql')->migrate(0)->migrate(1);
} else {
$migrations->from_file('utilities/migrations_mysql.sql')->migrate(1);
}
}
$app->helper(cache => \&_cache);
......@@ -45,6 +56,19 @@ sub _pg {
return $pg;
}
sub _mysql {
my $c = shift;
my $addr = 'mysql://';
$addr .= $c->config->{mysqldb}->{host};
$addr .= ':'.$c->config->{mysqldb}->{port} if defined $c->config->{pgdb}->{port};
$addr .= '/'.$c->config->{mysqldb}->{database};
state $mysql = Mojo::mysql->new($addr);
$mysql->password($c->config->{mysqldb}->{pwd});
$mysql->username($c->config->{mysqldb}->{user});
return $mysql;
}
sub _cache {
my $c = shift;
......
......@@ -67,7 +67,7 @@
#fixed_domain => 'example.org',
# choose what database you want to use
# valid choices are sqlite and postgresql (all lowercase)
# valid choices are sqlite, postgresql and mysql (all lowercase)
# optional, default is sqlite
#dbtype => 'sqlite',
......@@ -88,6 +88,16 @@
# #pwd => 'DBPASSWORD'
#},
# MySQL ONLY - only used if dbtype is set to mysql
# these are the credentials to access the MySQL database
# mandatory if you choosed mysql as dbtype
#mysqldb => {
# database => 'lstu',
# host => 'localhost',
# #user => 'DBUSER',
# #pwd => 'DBPASSWORD'
#},
# Rate-limiting for the API
# After ban_min_strike requests in a second, the IP address will be
# banned for one hour.
......
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
{
####################
# Hypnotoad settings
####################
# see http://mojolicio.us/perldoc/Mojo/Server/Hypnotoad for a full list of settings
hypnotoad => {
# array of IP addresses and ports you want to listen to
listen => ['http://127.0.0.1:8080'],
# if you use Lstu behind a reverse proxy like Nginx, you want to set proxy to 1
# if you use Lstu directly, let it commented
#proxy => 1,
},
# put a way to contact you here and uncomment it
# MANDATORY
contact => 'admin[at]example.com',
# array of random strings used to encrypt cookies
# optional, default is ['fdjsofjoihrei'], PLEASE, CHANGE IT
#secret => ['fdjsofjoihrei'],
# secret passphrase to access some admin features
# If you don't want to have a plain text password in configuration,
# use hashed_adminpwd instead
# optional, but you won't have access to admin /stats if not set and if hashed_adminpwd is not set either
adminpwd => 'toto',
# secret hashed passphrase to access some admin features
# Hash your password by issuing `echo -n s3cr3T | sha256sum` on your terminal
# optional, but you won't have access to admin /stats if not set and if adminpwd is not set either
#hashed_adminpwd => '94b2feede6ea5e2eec62f457ecb7d3f719b24d19c29d4e5466246a31908fc23b',
# choose a theme. See the available themes in `themes` directory
# optional, default is 'default'
#theme => 'default',
# number of URLs to be displayed per page in /stats
# optional, default is 10
#page_offset => 10,
# length of the random URL
# optional, default is 8
#length => 8,
# how many URLs will be provisioned in a batch ?
# optional, default is 5
#provis_step => 5,
# max number of URLs to be provisioned
# optional, default is 100
#provisioning => 100,
# URL sub-directory in which you want Lstu to be accessible
# example: you want to have Lstu under https://example.org/lstu/
# => set prefix to '/lstu' or to '/lstu/', it doesn't matter
# optional, defaut is /
#prefix => '/',
# array of authorized domains for API calls.
# if you want to authorize everyone to use the API: ['*']
# optional, no domains allowed by default
#allowed_domains => ['http://1.example.com', 'http://2.example.com'],
# if set, the shortened URLs will use this domain
# optional
#fixed_domain => 'example.org',
# choose what database you want to use
# valid choices are sqlite and postgresql (all lowercase)
# optional, default is sqlite
#dbtype => 'sqlite',
# SQLite ONLY - only used if dbtype is set to sqlite
# define a path to the SQLite database
# you can define it relative to lstu directory or set an absolute path
# remember that it has to be in a directory writable by Lstu user
# optional, default is lstu.db
#db_path => 'lstu.db',
# PostgreSQL ONLY - only used if dbtype is set to postgresql
# these are the credentials to access the PostgreSQL database
# mandatory if you choosed postgresql as dbtype
pgdb => {
database => 'lstu1',
host => 'localhost',
user => 'lstu',
pwd => 'lstu'
},
# Rate-limiting for the API
# After ban_min_strike requests in a second, the IP address will be
# banned for one hour.
# If it continues to query the API during this ban time at least
# ban_min_strike times, it will be banned for a month.
# optional, default is 3
#ban_min_strike => 3,
# Ban whitelist
# You can whitelist IP addresses to prevent you from being banned
# Be careful, the IP addresses are compared as string, not as IP addresses
# a network range will not work
# Example of valid input: ban_whitelist => ['198.51.100.42', '2001:0DB8::42'],¬
# optional, default is an empty array
#ban_whitelist => [],
# define an URL to the Piwik instance and the ID of a website to track
# set if you want to track views in Piwik
# optional, Piwik tracking is disabled by default
#piwik => {
# url => 'http://piwik.example.com',
# idsite => '1',
#},
# use Minion instead of directly increase counters
# need to launch a minion worker service if enabled
# optional, Minion is disabled by default
#minion => {
# enabled => 0,
# db_path => 'minion.db' # you can define it relative to lstu directory or set an absolute path
#},
# set `ldap` if you want that only authenticated users can shorten URLs
# please note that everybody can still use shortend URLs
# optional, no default
#ldap => {
# uri => 'ldaps://ldap.example.org',
# user_tree => 'ou=users,dc=example,dc=org',
# bind_dn => ',ou=users,dc=example,dc=org',
# bind_user => 'uid=ldap_user',
# bind_pwd => 'secr3t',
# user_filter => '!(uid=ldap_user)'
#},
# set `htpasswd` if you want to use an htpasswd file instead of ldap
# create the file with `htpasswd -c lstu.passwd user`, update it with `htpasswd lstu.passwd user2`
# make sure that lstu can read the file!
# optional, no default
#htpasswd => 'lstu.passwd',
# if you've set ldap or htpasswd above, the session will last `session_duration` seconds before
# the user needs to reauthenticate
# optional, default is 3600
#session_duration => 3600,
};
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
{
####################
# Hypnotoad settings
####################
# see http://mojolicio.us/perldoc/Mojo/Server/Hypnotoad for a full list of settings
hypnotoad => {
# array of IP addresses and ports you want to listen to
listen => ['http://127.0.0.1:8080'],
# if you use Lstu behind a reverse proxy like Nginx, you want to set proxy to 1
# if you use Lstu directly, let it commented
#proxy => 1,
},
# put a way to contact you here and uncomment it
# MANDATORY
contact => 'admin[at]example.com',
# array of random strings used to encrypt cookies
# optional, default is ['fdjsofjoihrei'], PLEASE, CHANGE IT
#secret => ['fdjsofjoihrei'],
# secret passphrase to access some admin features
# If you don't want to have a plain text password in configuration,
# use hashed_adminpwd instead
# optional, but you won't have access to admin /stats if not set and if hashed_adminpwd is not set either
#adminpwd => 's3cr3T',
# secret hashed passphrase to access some admin features
# Hash your password by issuing `echo -n s3cr3T | sha256sum` on your terminal
# optional, but you won't have access to admin /stats if not set and if adminpwd is not set either
hashed_adminpwd => '31f7a65e315586ac198bd798b6629ce4903d0899476d5741a9f32e2e521b6a66',
# choose a theme. See the available themes in `themes` directory
# optional, default is 'default'
#theme => 'default',
# number of URLs to be displayed per page in /stats
# optional, default is 10
#page_offset => 10,
# length of the random URL
# optional, default is 8
#length => 8,
# how many URLs will be provisioned in a batch ?
# optional, default is 5
#provis_step => 5,
# max number of URLs to be provisioned
# optional, default is 100
#provisioning => 100,
# URL sub-directory in which you want Lstu to be accessible
# example: you want to have Lstu under https://example.org/lstu/
# => set prefix to '/lstu' or to '/lstu/', it doesn't matter
# optional, defaut is /
#prefix => '/',
# array of authorized domains for API calls.
# if you want to authorize everyone to use the API: ['*']
# optional, no domains allowed by default
#allowed_domains => ['http://1.example.com', 'http://2.example.com'],
# if set, the shortened URLs will use this domain
# optional
#fixed_domain => 'example.org',
# choose what database you want to use
# valid choices are sqlite and postgresql (all lowercase)
# optional, default is sqlite
#dbtype => 'sqlite',
# SQLite ONLY - only used if dbtype is set to sqlite
# define a path to the SQLite database
# you can define it relative to lstu directory or set an absolute path
# remember that it has to be in a directory writable by Lstu user
# optional, default is lstu.db
#db_path => 'lstu.db',
# PostgreSQL ONLY - only used if dbtype is set to postgresql
# these are the credentials to access the PostgreSQL database
# mandatory if you choosed postgresql as dbtype
pgdb => {
database => 'lstu2',
host => 'localhost',
user => 'lstu',
pwd => 'lstu'
},
# Rate-limiting for the API
# After ban_min_strike requests in a second, the IP address will be
# banned for one hour.
# If it continues to query the API during this ban time at least
# ban_min_strike times, it will be banned for a month.
# optional, default is 3
#ban_min_strike => 3,
# Ban whitelist
# You can whitelist IP addresses to prevent you from being banned
# Be careful, the IP addresses are compared as string, not as IP addresses
# a network range will not work
# Example of valid input: ban_whitelist => ['198.51.100.42', '2001:0DB8::42'],¬
# optional, default is an empty array
#ban_whitelist => [],
# define an URL to the Piwik instance and the ID of a website to track
# set if you want to track views in Piwik
# optional, Piwik tracking is disabled by default
#piwik => {
# url => 'http://piwik.example.com',
# idsite => '1',
#},
# use Minion instead of directly increase counters
# need to launch a minion worker service if enabled
# optional, Minion is disabled by default
#minion => {
# enabled => 0,
# db_path => 'minion.db' # you can define it relative to lstu directory or set an absolute path
#},
# set `ldap` if you want that only authenticated users can shorten URLs
# please note that everybody can still use shortend URLs
# optional, no default
#ldap => {
# uri => 'ldaps://ldap.example.org',
# user_tree => 'ou=users,dc=example,dc=org',
# bind_dn => ',ou=users,dc=example,dc=org',
# bind_user => 'uid=ldap_user',
# bind_pwd => 'secr3t',
# user_filter => '!(uid=ldap_user)'
#},
# set `htpasswd` if you want to use an htpasswd file instead of ldap
# create the file with `htpasswd -c lstu.passwd user`, update it with `htpasswd lstu.passwd user2`
# make sure that lstu can read the file!
# optional, no default
#htpasswd => 'lstu.passwd',
# if you've set ldap or htpasswd above, the session will last `session_duration` seconds before
# the user needs to reauthenticate
# optional, default is 3600
#session_duration => 3600,
};
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
{
####################
# Hypnotoad settings
####################
# see http://mojolicio.us/perldoc/Mojo/Server/Hypnotoad for a full list of settings
hypnotoad => {
# array of IP addresses and ports you want to listen to
listen => ['http://127.0.0.1:8080'],
# if you use Lstu behind a reverse proxy like Nginx, you want to set proxy to 1
# if you use Lstu directly, let it commented
#proxy => 1,
},
# put a way to contact you here and uncomment it
# MANDATORY
contact => 'admin[at]example.com',
# array of random strings used to encrypt cookies
# optional, default is ['fdjsofjoihrei'], PLEASE, CHANGE IT
#secret => ['fdjsofjoihrei'],
# secret passphrase to access some admin features
# If you don't want to have a plain text password in configuration,
# use hashed_adminpwd instead
# optional, but you won't have access to admin /stats if not set and if hashed_adminpwd is not set either
#adminpwd => 's3cr3T',
# secret hashed passphrase to access some admin features
# Hash your password by issuing `echo -n s3cr3T | sha256sum` on your terminal
# optional, but you won't have access to admin /stats if not set and if adminpwd is not set either
hashed_adminpwd => '31f7a65e315586ac198bd798b6629ce4903d0899476d5741a9f32e2e521b6a66',
# choose a theme. See the available themes in `themes` directory
# optional, default is 'default'