Commit cafcd6d9 authored by Luc Didry's avatar Luc Didry

Fixed #3 : Internationalization

I had to transform Lstu into a full Mojolicious application to make I18N
working.
parent 937b8dbb
#!/usr/bin/env perl
package Model;
# Create database
use ORLite {
file => 'lstu.db',
unicode => 1,
create => sub {
my $dbh = shift;
$dbh->do(
'CREATE TABLE lstu (
short TEXT PRIMARY KEY,
url TEXT,
counter INTEGER,
timestamp INTEGER)'
);
return 1;
}
};
package Lstu;
use Mojolicious::Lite;
use Mojolicious::Plugin::DefaultHelpers;
use Data::Validate::URI qw(is_uri);
my $config = plugin 'Config';
# Default values
$config->{provisionning} = 100 unless (defined($config->{provisionning}));
$config->{provis_step} = 5 unless (defined($config->{provis_step}));
$config->{length} = 8 unless (defined($config->{length}));
$config->{secret} = 'hfudsifdsih' unless (defined($config->{secret}));
app->secret($config->{secret});
app->helper(
provisionning => sub {
my $c = shift;
# Create some short patterns for provisionning
if (Model::Lstu->count('WHERE url IS NULL') < $c->config->{provisionning}) {
for (my $i = 0; $i < $c->config->{provis_step}; $i++) {
my $short;
do {
$short= $c->shortener($c->config->{length});
} while (Model::Lstu->count('WHERE short = ?', $short));
Model::Lstu->create(
short => $short
);
}
}
}
);
app->helper(
shortener => sub {
my $c = shift;
my $length = shift;
my @chars = ('a'..'z','A'..'Z','0'..'9');
my $result = '';
foreach (1..$length) {
$result .= $chars[rand scalar(@chars)];
}
return $result;
}
);
# For the first launch (after, this isn't really useful)
app->provisionning();
get '/' => sub {
my $c = shift;
$c->render(template => 'index');
# Check provisionning
$c->on(finish => sub {
shift->provisionning();
});
} => 'index';
get '/:short' => sub {
my $c = shift;
my $short = $c->param('short');
my @urls = Model::Lstu->select('WHERE short = ?', $short);
if (scalar(@urls)) {
$c->res->code(301);
$c->redirect_to($urls[0]->url);
# Update counter and check provisionning
$c->on(finish => sub {
my $counter = $urls[0]->counter + 1;
$urls[0]->update (counter => $counter);
shift->provisionning();
});
} else {
$c->flash(
msg => $c->url_for('/')->to_abs.$short.' not found.'
);
$c->redirect_to('/');
}
} => 'short';
my $add = sub {
my $c = shift;
my $url = $c->param('lsturl');
if (is_uri($url)) {
my $short;
my @keys = $c->param;
my @params;
foreach my $key (sort @keys) {
push @params, $key.'='.$c->param($key) unless ($key eq 'lsturl');
}
$url .= '?'.join('&', @params) if (scalar(@params));
my @records = Model::Lstu->select('WHERE url = ?', $url);
if (scalar(@records)) {
$c->flash(
short => $records[0]->short,
url => $url
);
} else {
@records = Model::Lstu->select('WHERE url IS NULL LIMIT 1');
if (scalar(@records)) {
$records[0]->update(
url => $url,
counter => 0,
timestamp => time()
);
$c->flash(
short => $records[0]->short,
url => $url
);
} else {
$c->flash(
msg => 'No shortened URL available. Please retry or contact the administrator at '.$c->config->{contact}
);
}
}
} else {
$c->flash(
msg => $url.' is not a valid URL'
);
}
$c->redirect_to('/');
# Check provisionning
$c->on(finish => sub {
shift->provisionning();
});
};
post '/a/' => $add => 'add' ;
get '/a/*lsturl' => $add => 'addurl';
return app->start;
1;
__DATA__
@@ index.html.ep
% layout 'default';
% title 'Lstu';
<h1>Let's Shorten That Url</h1>
</p><small>&copy; 2013 <%= link_to 'http://www.fiat-tux.fr' => begin %>Luc Didry<% end %> — License : <%= link_to 'http://www.wtfpl.net/' => begin %>WTFPL<% end %><%= link_to 'https://github.com/ldidry/lstu' => begin %>About<% end %></small></p>
<form class="form-inline" role="form" method="POST" action="<%== url_for 'add' %>">
<div class="form-group">
<label class="sr-only" for="lsturl">Url to shorten</label>
<input type="url" name="lsturl" id="lsturl" class="form-control" placeholder="Url to shorten" required></input>
</div>
<%= submit_button 'Go', class => 'btn btn-default btn-primary' %>
</form>
% if (defined(flash('short')) && defined(flash('url'))) {
<h3 class="alert alert-success">
<%= link_to flash('url') => begin %><%== flash('url') %><%= end %></a> =&gt;
<%= link_to url_for('/')->to_abs.flash('short') => begin %><%= url_for('/')->to_abs.flash('short') %><%= end %>
</h3>
% }
% if (defined(flash('msg'))) {
<h3 class="alert alert-danger"><%== flash('msg') %><h3>
% }
@@ layouts/default.html.ep
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
%= stylesheet begin
/*!
* Bootstrap v3.0.0
*
* Copyright 2013 Twitter, Inc
* Licensed under the Apache License v2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Designed and built with all the love in the world @twitter by @mdo and @fat.
*/
article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block;}
audio,canvas,video{display:inline-block;}
audio:not([controls]){display:none;height:0;}
[hidden]{display:none;}
html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;}
body{margin:0;}
a:focus{outline:thin dotted;}
a:active,a:hover{outline:0;}
h1{font-size:2em;margin:0.67em 0;}
abbr[title]{border-bottom:1px dotted;}
b,strong{font-weight:bold;}
dfn{font-style:italic;}
hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0;}
mark{background:#ff0;color:#000;}
code,kbd,pre,samp{font-family:monospace, serif;font-size:1em;}
pre{white-space:pre-wrap;}
q{quotes:"\201C" "\201D" "\2018" "\2019";}
small{font-size:80%;}
sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;}
sup{top:-0.5em;}
sub{bottom:-0.25em;}
img{border:0;}
svg:not(:root){overflow:hidden;}
figure{margin:0;}
fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em;}
legend{border:0;padding:0;}
button,input,select,textarea{font-family:inherit;font-size:100%;margin:0;}
button,input{line-height:normal;}
button,select{text-transform:none;}
button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;}
button[disabled],html input[disabled]{cursor:default;}
input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0;}
input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;}
input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none;}
button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;}
textarea{overflow:auto;vertical-align:top;}
table{border-collapse:collapse;border-spacing:0;}
*,*:before,*:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;}
html{font-size:62.5%;-webkit-tap-highlight-color:rgba(0, 0, 0, 0);}
body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.428571429;color:#333333;background-color:#ffffff;}
input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit;}
button,input,select[multiple],textarea{background-image:none;}
a{color:#428bca;text-decoration:none;}a:hover,a:focus{color:#2a6496;text-decoration:underline;}
a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;}
img{vertical-align:middle;}
.img-responsive{display:block;max-width:100%;height:auto;}
.img-rounded{border-radius:6px;}
.img-thumbnail{padding:4px;line-height:1.428571429;background-color:#ffffff;border:1px solid #dddddd;border-radius:4px;-webkit-transition:all 0.2s ease-in-out;transition:all 0.2s ease-in-out;display:inline-block;max-width:100%;height:auto;}
.img-circle{border-radius:50%;}
hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eeeeee;}
.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);border:0;}
p{margin:0 0 10px;}
.lead{margin-bottom:20px;font-size:16.099999999999998px;font-weight:200;line-height:1.4;}@media (min-width:768px){.lead{font-size:21px;}}
small{font-size:85%;}
cite{font-style:normal;}
.text-muted{color:#999999;}
.text-primary{color:#428bca;}
.text-warning{color:#c09853;}
.text-danger{color:#b94a48;}
.text-success{color:#468847;}
.text-info{color:#3a87ad;}
.text-left{text-align:left;}
.text-right{text-align:right;}
.text-center{text-align:center;}
h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:500;line-height:1.1;}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small{font-weight:normal;line-height:1;color:#999999;}
h1,h2,h3{margin-top:20px;margin-bottom:10px;}
h4,h5,h6{margin-top:10px;margin-bottom:10px;}
h1,.h1{font-size:36px;}
h2,.h2{font-size:30px;}
h3,.h3{font-size:24px;}
h4,.h4{font-size:18px;}
h5,.h5{font-size:14px;}
h6,.h6{font-size:12px;}
h1 small,.h1 small{font-size:24px;}
h2 small,.h2 small{font-size:18px;}
h3 small,.h3 small,h4 small,.h4 small{font-size:14px;}
.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eeeeee;}
ul,ol{margin-top:0;margin-bottom:10px;}ul ul,ol ul,ul ol,ol ol{margin-bottom:0;}
.list-unstyled{padding-left:0;list-style:none;}
.list-inline{padding-left:0;list-style:none;}.list-inline>li{display:inline-block;padding-left:5px;padding-right:5px;}
dl{margin-bottom:20px;}
dt,dd{line-height:1.428571429;}
dt{font-weight:bold;}
dd{margin-left:0;}
@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;} .dl-horizontal dd{margin-left:180px;}.dl-horizontal dd:before,.dl-horizontal dd:after{content:" ";display:table;} .dl-horizontal dd:after{clear:both;}}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999999;}
abbr.initialism{font-size:90%;text-transform:uppercase;}
blockquote{padding:10px 20px;margin:0 0 20px;border-left:5px solid #eeeeee;}blockquote p{font-size:17.5px;font-weight:300;line-height:1.25;}
blockquote p:last-child{margin-bottom:0;}
blockquote small{display:block;line-height:1.428571429;color:#999999;}blockquote small:before{content:'\2014 \00A0';}
blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #eeeeee;border-left:0;}blockquote.pull-right p,blockquote.pull-right small{text-align:right;}
blockquote.pull-right small:before{content:'';}
blockquote.pull-right small:after{content:'\00A0 \2014';}
q:before,q:after,blockquote:before,blockquote:after{content:"";}
address{display:block;margin-bottom:20px;font-style:normal;line-height:1.428571429;}
.container{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px;}.container:before,.container:after{content:" ";display:table;}
.container:after{clear:both;}
.row{margin-left:-15px;margin-right:-15px;}.row:before,.row:after{content:" ";display:table;}
.row:after{clear:both;}
.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12,.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12,.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12,.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12{position:relative;min-height:1px;padding-left:15px;padding-right:15px;}
.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11{float:left;}
.col-xs-1{width:8.333333333333332%;}
.col-xs-2{width:16.666666666666664%;}
.col-xs-3{width:25%;}
.col-xs-4{width:33.33333333333333%;}
.col-xs-5{width:41.66666666666667%;}
.col-xs-6{width:50%;}
.col-xs-7{width:58.333333333333336%;}
.col-xs-8{width:66.66666666666666%;}
.col-xs-9{width:75%;}
.col-xs-10{width:83.33333333333334%;}
.col-xs-11{width:91.66666666666666%;}
.col-xs-12{width:100%;}
@media (min-width:768px){.container{max-width:750px;} .col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11{float:left;} .col-sm-1{width:8.333333333333332%;} .col-sm-2{width:16.666666666666664%;} .col-sm-3{width:25%;} .col-sm-4{width:33.33333333333333%;} .col-sm-5{width:41.66666666666667%;} .col-sm-6{width:50%;} .col-sm-7{width:58.333333333333336%;} .col-sm-8{width:66.66666666666666%;} .col-sm-9{width:75%;} .col-sm-10{width:83.33333333333334%;} .col-sm-11{width:91.66666666666666%;} .col-sm-12{width:100%;} .col-sm-push-1{left:8.333333333333332%;} .col-sm-push-2{left:16.666666666666664%;} .col-sm-push-3{left:25%;} .col-sm-push-4{left:33.33333333333333%;} .col-sm-push-5{left:41.66666666666667%;} .col-sm-push-6{left:50%;} .col-sm-push-7{left:58.333333333333336%;} .col-sm-push-8{left:66.66666666666666%;} .col-sm-push-9{left:75%;} .col-sm-push-10{left:83.33333333333334%;} .col-sm-push-11{left:91.66666666666666%;} .col-sm-pull-1{right:8.333333333333332%;} .col-sm-pull-2{right:16.666666666666664%;} .col-sm-pull-3{right:25%;} .col-sm-pull-4{right:33.33333333333333%;} .col-sm-pull-5{right:41.66666666666667%;} .col-sm-pull-6{right:50%;} .col-sm-pull-7{right:58.333333333333336%;} .col-sm-pull-8{right:66.66666666666666%;} .col-sm-pull-9{right:75%;} .col-sm-pull-10{right:83.33333333333334%;} .col-sm-pull-11{right:91.66666666666666%;} .col-sm-offset-1{margin-left:8.333333333333332%;} .col-sm-offset-2{margin-left:16.666666666666664%;} .col-sm-offset-3{margin-left:25%;} .col-sm-offset-4{margin-left:33.33333333333333%;} .col-sm-offset-5{margin-left:41.66666666666667%;} .col-sm-offset-6{margin-left:50%;} .col-sm-offset-7{margin-left:58.333333333333336%;} .col-sm-offset-8{margin-left:66.66666666666666%;} .col-sm-offset-9{margin-left:75%;} .col-sm-offset-10{margin-left:83.33333333333334%;} .col-sm-offset-11{margin-left:91.66666666666666%;}}@media (min-width:992px){.container{max-width:970px;} .col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11{float:left;} .col-md-1{width:8.333333333333332%;} .col-md-2{width:16.666666666666664%;} .col-md-3{width:25%;} .col-md-4{width:33.33333333333333%;} .col-md-5{width:41.66666666666667%;} .col-md-6{width:50%;} .col-md-7{width:58.333333333333336%;} .col-md-8{width:66.66666666666666%;} .col-md-9{width:75%;} .col-md-10{width:83.33333333333334%;} .col-md-11{width:91.66666666666666%;} .col-md-12{width:100%;} .col-md-push-0{left:auto;} .col-md-push-1{left:8.333333333333332%;} .col-md-push-2{left:16.666666666666664%;} .col-md-push-3{left:25%;} .col-md-push-4{left:33.33333333333333%;} .col-md-push-5{left:41.66666666666667%;} .col-md-push-6{left:50%;} .col-md-push-7{left:58.333333333333336%;} .col-md-push-8{left:66.66666666666666%;} .col-md-push-9{left:75%;} .col-md-push-10{left:83.33333333333334%;} .col-md-push-11{left:91.66666666666666%;} .col-md-pull-0{right:auto;} .col-md-pull-1{right:8.333333333333332%;} .col-md-pull-2{right:16.666666666666664%;} .col-md-pull-3{right:25%;} .col-md-pull-4{right:33.33333333333333%;} .col-md-pull-5{right:41.66666666666667%;} .col-md-pull-6{right:50%;} .col-md-pull-7{right:58.333333333333336%;} .col-md-pull-8{right:66.66666666666666%;} .col-md-pull-9{right:75%;} .col-md-pull-10{right:83.33333333333334%;} .col-md-pull-11{right:91.66666666666666%;} .col-md-offset-0{margin-left:0;} .col-md-offset-1{margin-left:8.333333333333332%;} .col-md-offset-2{margin-left:16.666666666666664%;} .col-md-offset-3{margin-left:25%;} .col-md-offset-4{margin-left:33.33333333333333%;} .col-md-offset-5{margin-left:41.66666666666667%;} .col-md-offset-6{margin-left:50%;} .col-md-offset-7{margin-left:58.333333333333336%;} .col-md-offset-8{margin-left:66.66666666666666%;} .col-md-offset-9{margin-left:75%;} .col-md-offset-10{margin-left:83.33333333333334%;} .col-md-offset-11{margin-left:91.66666666666666%;}}@media (min-width:1200px){.container{max-width:1170px;} .col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11{float:left;} .col-lg-1{width:8.333333333333332%;} .col-lg-2{width:16.666666666666664%;} .col-lg-3{width:25%;} .col-lg-4{width:33.33333333333333%;} .col-lg-5{width:41.66666666666667%;} .col-lg-6{width:50%;} .col-lg-7{width:58.333333333333336%;} .col-lg-8{width:66.66666666666666%;} .col-lg-9{width:75%;} .col-lg-10{width:83.33333333333334%;} .col-lg-11{width:91.66666666666666%;} .col-lg-12{width:100%;} .col-lg-push-0{left:auto;} .col-lg-push-1{left:8.333333333333332%;} .col-lg-push-2{left:16.666666666666664%;} .col-lg-push-3{left:25%;} .col-lg-push-4{left:33.33333333333333%;} .col-lg-push-5{left:41.66666666666667%;} .col-lg-push-6{left:50%;} .col-lg-push-7{left:58.333333333333336%;} .col-lg-push-8{left:66.66666666666666%;} .col-lg-push-9{left:75%;} .col-lg-push-10{left:83.33333333333334%;} .col-lg-push-11{left:91.66666666666666%;} .col-lg-pull-0{right:auto;} .col-lg-pull-1{right:8.333333333333332%;} .col-lg-pull-2{right:16.666666666666664%;} .col-lg-pull-3{right:25%;} .col-lg-pull-4{right:33.33333333333333%;} .col-lg-pull-5{right:41.66666666666667%;} .col-lg-pull-6{right:50%;} .col-lg-pull-7{right:58.333333333333336%;} .col-lg-pull-8{right:66.66666666666666%;} .col-lg-pull-9{right:75%;} .col-lg-pull-10{right:83.33333333333334%;} .col-lg-pull-11{right:91.66666666666666%;} .col-lg-offset-0{margin-left:0;} .col-lg-offset-1{margin-left:8.333333333333332%;} .col-lg-offset-2{margin-left:16.666666666666664%;} .col-lg-offset-3{margin-left:25%;} .col-lg-offset-4{margin-left:33.33333333333333%;} .col-lg-offset-5{margin-left:41.66666666666667%;} .col-lg-offset-6{margin-left:50%;} .col-lg-offset-7{margin-left:58.333333333333336%;} .col-lg-offset-8{margin-left:66.66666666666666%;} .col-lg-offset-9{margin-left:75%;} .col-lg-offset-10{margin-left:83.33333333333334%;} .col-lg-offset-11{margin-left:91.66666666666666%;}}fieldset{padding:0;margin:0;border:0;}
legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333333;border:0;border-bottom:1px solid #e5e5e5;}
label{display:inline-block;margin-bottom:5px;font-weight:bold;}
input[type="search"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;}
input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;line-height:normal;}
input[type="file"]{display:block;}
select[multiple],select[size]{height:auto;}
select optgroup{font-size:inherit;font-style:inherit;font-family:inherit;}
input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;}
input[type="number"]::-webkit-outer-spin-button,input[type="number"]::-webkit-inner-spin-button{height:auto;}
.form-control:-moz-placeholder{color:#999999;}
.form-control::-moz-placeholder{color:#999999;}
.form-control:-ms-input-placeholder{color:#999999;}
.form-control::-webkit-input-placeholder{color:#999999;}
.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.428571429;color:#555555;vertical-align:middle;background-color:#ffffff;border:1px solid #cccccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-webkit-transition:border-color ease-in-out .15s, box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s, box-shadow ease-in-out .15s;}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);}
.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#eeeeee;}
textarea.form-control{height:auto;}
.form-group{margin-bottom:15px;}
.radio,.checkbox{display:block;min-height:20px;margin-top:10px;margin-bottom:10px;padding-left:20px;vertical-align:middle;}.radio label,.checkbox label{display:inline;margin-bottom:0;font-weight:normal;cursor:pointer;}
.radio input[type="radio"],.radio-inline input[type="radio"],.checkbox input[type="checkbox"],.checkbox-inline input[type="checkbox"]{float:left;margin-left:-20px;}
.radio+.radio,.checkbox+.checkbox{margin-top:-5px;}
.radio-inline,.checkbox-inline{display:inline-block;padding-left:20px;margin-bottom:0;vertical-align:middle;font-weight:normal;cursor:pointer;}
.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px;}
input[type="radio"][disabled],input[type="checkbox"][disabled],.radio[disabled],.radio-inline[disabled],.checkbox[disabled],.checkbox-inline[disabled],fieldset[disabled] input[type="radio"],fieldset[disabled] input[type="checkbox"],fieldset[disabled] .radio,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox,fieldset[disabled] .checkbox-inline{cursor:not-allowed;}
.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px;}select.input-sm{height:30px;line-height:30px;}
textarea.input-sm{height:auto;}
.input-lg{height:45px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px;}select.input-lg{height:45px;line-height:45px;}
textarea.input-lg{height:auto;}
.has-warning .help-block,.has-warning .control-label{color:#c09853;}
.has-warning .form-control{border-color:#c09853;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);}.has-warning .form-control:focus{border-color:#a47e3c;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #dbc59e;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #dbc59e;}
.has-warning .input-group-addon{color:#c09853;border-color:#c09853;background-color:#fcf8e3;}
.has-error .help-block,.has-error .control-label{color:#b94a48;}
.has-error .form-control{border-color:#b94a48;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);}.has-error .form-control:focus{border-color:#953b39;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #d59392;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #d59392;}
.has-error .input-group-addon{color:#b94a48;border-color:#b94a48;background-color:#f2dede;}
.has-success .help-block,.has-success .control-label{color:#468847;}
.has-success .form-control{border-color:#468847;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);}.has-success .form-control:focus{border-color:#356635;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #7aba7b;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #7aba7b;}
.has-success .input-group-addon{color:#468847;border-color:#468847;background-color:#dff0d8;}
.form-control-static{margin-bottom:0;padding-top:7px;}
.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373;}
@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle;} .form-inline .form-control{display:inline-block;} .form-inline .radio,.form-inline .checkbox{display:inline-block;margin-top:0;margin-bottom:0;padding-left:0;} .form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:none;margin-left:0;}}
.form-horizontal .control-label,.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{margin-top:0;margin-bottom:0;padding-top:7px;}
.form-horizontal .form-group{margin-left:-15px;margin-right:-15px;}.form-horizontal .form-group:before,.form-horizontal .form-group:after{content:" ";display:table;}
.form-horizontal .form-group:after{clear:both;}
@media (min-width:768px){.form-horizontal .control-label{text-align:right;}}
.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:normal;line-height:1.428571429;text-align:center;vertical-align:middle;cursor:pointer;border:1px solid transparent;border-radius:4px;white-space:nowrap;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none;}.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;}
.btn:hover,.btn:focus{color:#333333;text-decoration:none;}
.btn:active,.btn.active{outline:0;background-image:none;-webkit-box-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);box-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);}
.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;pointer-events:none;opacity:0.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;}
.btn-default{color:#333333;background-color:#ffffff;border-color:#cccccc;}.btn-default:hover,.btn-default:focus,.btn-default:active,.btn-default.active,.open .dropdown-toggle.btn-default{color:#333333;background-color:#ebebeb;border-color:#adadad;}
.btn-default:active,.btn-default.active,.open .dropdown-toggle.btn-default{background-image:none;}
.btn-default.disabled,.btn-default[disabled],fieldset[disabled] .btn-default,.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled:active,.btn-default[disabled]:active,fieldset[disabled] .btn-default:active,.btn-default.disabled.active,.btn-default[disabled].active,fieldset[disabled] .btn-default.active{background-color:#ffffff;border-color:#cccccc;}
.btn-primary{color:#ffffff;background-color:#428bca;border-color:#357ebd;}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.open .dropdown-toggle.btn-primary{color:#ffffff;background-color:#3276b1;border-color:#285e8e;}
.btn-primary:active,.btn-primary.active,.open .dropdown-toggle.btn-primary{background-image:none;}
.btn-primary.disabled,.btn-primary[disabled],fieldset[disabled] .btn-primary,.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled:active,.btn-primary[disabled]:active,fieldset[disabled] .btn-primary:active,.btn-primary.disabled.active,.btn-primary[disabled].active,fieldset[disabled] .btn-primary.active{background-color:#428bca;border-color:#357ebd;}
.btn-warning{color:#ffffff;background-color:#f0ad4e;border-color:#eea236;}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.open .dropdown-toggle.btn-warning{color:#ffffff;background-color:#ed9c28;border-color:#d58512;}
.btn-warning:active,.btn-warning.active,.open .dropdown-toggle.btn-warning{background-image:none;}
.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-warning,.btn-warning.disabled:hover,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled:active,.btn-warning[disabled]:active,fieldset[disabled] .btn-warning:active,.btn-warning.disabled.active,.btn-warning[disabled].active,fieldset[disabled] .btn-warning.active{background-color:#f0ad4e;border-color:#eea236;}
.btn-danger{color:#ffffff;background-color:#d9534f;border-color:#d43f3a;}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.open .dropdown-toggle.btn-danger{color:#ffffff;background-color:#d2322d;border-color:#ac2925;}
.btn-danger:active,.btn-danger.active,.open .dropdown-toggle.btn-danger{background-image:none;}
.btn-danger.disabled,.btn-danger[disabled],fieldset[disabled] .btn-danger,.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabled]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled:active,.btn-danger[disabled]:active,fieldset[disabled] .btn-danger:active,.btn-danger.disabled.active,.btn-danger[disabled].active,fieldset[disabled] .btn-danger.active{background-color:#d9534f;border-color:#d43f3a;}
.btn-success{color:#ffffff;background-color:#5cb85c;border-color:#4cae4c;}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.open .dropdown-toggle.btn-success{color:#ffffff;background-color:#47a447;border-color:#398439;}
.btn-success:active,.btn-success.active,.open .dropdown-toggle.btn-success{background-image:none;}
.btn-success.disabled,.btn-success[disabled],fieldset[disabled] .btn-success,.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled:active,.btn-success[disabled]:active,fieldset[disabled] .btn-success:active,.btn-success.disabled.active,.btn-success[disabled].active,fieldset[disabled] .btn-success.active{background-color:#5cb85c;border-color:#4cae4c;}
.btn-info{color:#ffffff;background-color:#5bc0de;border-color:#46b8da;}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.open .dropdown-toggle.btn-info{color:#ffffff;background-color:#39b3d7;border-color:#269abc;}
.btn-info:active,.btn-info.active,.open .dropdown-toggle.btn-info{background-image:none;}
.btn-info.disabled,.btn-info[disabled],fieldset[disabled] .btn-info,.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled:active,.btn-info[disabled]:active,fieldset[disabled] .btn-info:active,.btn-info.disabled.active,.btn-info[disabled].active,fieldset[disabled] .btn-info.active{background-color:#5bc0de;border-color:#46b8da;}
.btn-link{color:#428bca;font-weight:normal;cursor:pointer;border-radius:0;}.btn-link,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none;}
.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent;}
.btn-link:hover,.btn-link:focus{color:#2a6496;text-decoration:underline;background-color:transparent;}
.btn-link[disabled]:hover,fieldset[disabled] .btn-link:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:focus{color:#999999;text-decoration:none;}
.btn-lg{padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px;}
.btn-sm,.btn-xs{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px;}
.btn-xs{padding:1px 5px;}
.btn-block{display:block;width:100%;padding-left:0;padding-right:0;}
.btn-block+.btn-block{margin-top:5px;}
input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%;}
.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px;}.alert h4{margin-top:0;color:inherit;}
.alert .alert-link{font-weight:bold;}
.alert>p,.alert>ul{margin-bottom:0;}
.alert>p+p{margin-top:5px;}
.alert-dismissable{padding-right:35px;}.alert-dismissable .close{position:relative;top:-2px;right:-21px;color:inherit;}
.alert-success{background-color:#dff0d8;border-color:#d6e9c6;color:#468847;}.alert-success hr{border-top-color:#c9e2b3;}
.alert-success .alert-link{color:#356635;}
.alert-info{background-color:#d9edf7;border-color:#bce8f1;color:#3a87ad;}.alert-info hr{border-top-color:#a6e1ec;}
.alert-info .alert-link{color:#2d6987;}
.alert-warning{background-color:#fcf8e3;border-color:#fbeed5;color:#c09853;}.alert-warning hr{border-top-color:#f8e5be;}
.alert-warning .alert-link{color:#a47e3c;}
.alert-danger{background-color:#f2dede;border-color:#eed3d7;color:#b94a48;}.alert-danger hr{border-top-color:#e6c1c7;}
.alert-danger .alert-link{color:#953b39;}
% end
%= stylesheet begin
body {
padding-top: 40px;
padding-bottom: 40px;
background-color: #eee;
}
.container {
padding: 15px;
margin: 0 auto;
}
% end
</head>
<body>
<div class="container">
<%= content %>
</div>
</body>
</html>
requires 'Mojolicious';
requires 'ORLite';
requires 'Data::Validate::URI';
requires 'Mojolicious::Plugin::I18N';
......@@ -264,6 +264,16 @@ DISTRIBUTIONS
ojo undef
requirements:
ExtUtils::MakeMaker 0
Mojolicious-Plugin-I18N-1.0
pathname: S/SH/SHARIFULN/Mojolicious-Plugin-I18N-1.0.tar.gz
provides:
Mojolicious::Plugin::I18N 1.0
requirements:
I18N::LangTags 0.35
Module::Build 0.36
Mojolicious 3
Test::More 0
perl 5.010001
Net-Domain-TLD-1.70
pathname: A/AL/ALEXP/Net-Domain-TLD-1.70.tar.gz
provides:
......
package Lstu;
use Mojo::Base 'Mojolicious';
use LstuModel;
use Data::Validate::URI qw(is_uri);
# This method will run once at server start
sub startup {
my $self = shift;
my $config = $self->plugin('Config');
# Default values
$config->{provisionning} = 100 unless (defined($config->{provisionning}));
$config->{provis_step} = 5 unless (defined($config->{provis_step}));
$config->{length} = 8 unless (defined($config->{length}));
$config->{secret} = 'hfudsifdsih' unless (defined($config->{secret}));
$self->plugin('I18N');
$self->secret($config->{secret});
$self->helper(
provisionning => sub {
my $c = shift;
# Create some short patterns for provisionning
if (LstuModel::Lstu->count('WHERE url IS NULL') < $c->config->{provisionning}) {
for (my $i = 0; $i < $c->config->{provis_step}; $i++) {
my $short;
do {
$short= $c->shortener($c->config->{length});
} while (LstuModel::Lstu->count('WHERE short = ?', $short));
LstuModel::Lstu->create(
short => $short
);
}
}
}
);
$self->helper(
shortener => sub {
my $c = shift;
my $length = shift;
my @chars = ('a'..'z','A'..'Z','0'..'9');
my $result = '';
foreach (1..$length) {
$result .= $chars[rand scalar(@chars)];
}
return $result;
}
);
# For the first launch (after, this isn't really useful)
$self->provisionning();
# Router
my $r = $self->routes;
# Normal route to controller
$r->get('/' => sub {
my $c = shift;
$c->render(template => 'index');
# Check provisionning
$c->on(finish => sub {
shift->provisionning();
});
})->name('index');
$r->get('/:short' => sub {
my $c = shift;
my $short = $c->param('short');
my @urls = LstuModel::Lstu->select('WHERE short = ?', $short);
if (scalar(@urls)) {
$c->res->code(301);
$c->redirect_to($urls[0]->url);
# Update counter and check provisionning
$c->on(finish => sub {
my $counter = $urls[0]->counter + 1;
$urls[0]->update (counter => $counter);
shift->provisionning();
});
} else {
$c->flash(
msg => $c->l('url_not_found', $c->url_for('/')->to_abs.$short)
);
$c->redirect_to('/');
}
})->name('short');
my $add = sub {
my $c = shift;
my $url = $c->param('lsturl');
if (is_uri($url)) {
my $short;
my @keys = $c->param;
my @params;
foreach my $key (sort @keys) {
push @params, $key.'='.$c->param($key) unless ($key eq 'lsturl');
}
$url .= '?'.join('&', @params) if (scalar(@params));
my @records = LstuModel::Lstu->select('WHERE url = ?', $url);
if (scalar(@records)) {
# Already got this URL
$c->flash(
short => $records[0]->short,
url => $url
);
} else {
@records = LstuModel::Lstu->select('WHERE url IS NULL LIMIT 1');
if (scalar(@records)) {
$records[0]->update(
url => $url,
counter => 0,
timestamp => time()
);
$c->flash(
short => $records[0]->short,
url => $url
);
} else {
# Houston, we have a problem
$c->flash(
msg => $c->l('no_more_short', $c->config->{contact}, $url)
);
}
}
} else {
$c->app->log->debug($c->l('no_valid_url', $url));
$c->flash(
msg => $c->l('no_valid_url', $url)
);
}
$c->redirect_to('/');
# Check provisionning
$c->on(finish => sub {
shift->provisionning();
});
};
$r->post('/a/' => $add)->name('add');
$r->get('/a/*lsturl' => $add)->name('addurl');
}
1;
package Lstu::I18N::en;
use Mojo::Base 'Lstu::I18N';
our %Lexicon = (
'about' => 'About',
'go' => 'Go !',
'license' => 'License:',
'no_more_short' => 'No shortened URL available. Please retry or contact the administrator at [_1]. Your URL to shorten: [_2]',
'no_valid_url' => '[_1] is not a valid URL.',
'url_not_found' => 'The shortened URL [_1] doesn\'t exist.',
'url_to_shorten' => 'URL to shorten',
);
1;
package Lstu::I18N::fr;
use Mojo::Base 'Lstu::I18N';
our %Lexicon = (
'about' => 'À propos',
'go' => 'Allons-y !',
'license' => 'Licence : ',
'no_more_short' => 'Il n\'y a plus d\'URL raccourcie disponible. Veuillez réessayer ou contactez l\'administrateur sur [_1]. Rappel de l\'URL à raccourcir : [_2]',
'no_valid_url' => '[_1] n\'est pas une URL valide.',
'url_not_found' => 'L\'URL raccourcie [_1] n\'existe pas.',
'url_to_shorten' => 'URL à raccourcir',
);
1;
package LstuModel;
# Create database
use ORLite {
file => 'lstu.db',
unicode => 1,
create => sub {
my $dbh = shift;
$dbh->do(
'CREATE TABLE lstu (
short TEXT PRIMARY KEY,
url TEXT,
counter INTEGER,
timestamp INTEGER)'
);
return 1;
}
};
1;
/*!
* Bootstrap v3.0.0
*
* Copyright 2013 Twitter, Inc
* Licensed under the Apache License v2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Designed and built with all the love in the world @twitter by @mdo and @fat.
*/
article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block;}
audio,canvas,video{display:inline-block;}
audio:not([controls]){display:none;height:0;}
[hidden]{display:none;}
html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;}
body{margin:0;}
a:focus{outline:thin dotted;}
a:active,a:hover{outline:0;}
h1{font-size:2em;margin:0.67em 0;}
abbr[title]{border-bottom:1px dotted;}
b,strong{font-weight:bold;}
dfn{font-style:italic;}
hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0;}
mark{background:#ff0;color:#000;}
code,kbd,pre,samp{font-family:monospace, serif;font-size:1em;}
pre{white-space:pre-wrap;}
q{quotes:"\201C" "\201D" "\2018" "\2019";}
small{font-size:80%;}
sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;}
sup{top:-0.5em;}
sub{bottom:-0.25em;}
img{border:0;}
svg:not(:root){overflow:hidden;}
figure{margin:0;}
fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em;}
legend{border:0;padding:0;}
button,input,select,textarea{font-family:inherit;font-size:100%;margin:0;}
button,input{line-height:normal;}
button,select{text-transform:none;}
button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;}
button[disabled],html input[disabled]{cursor:default;}
input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0;}
input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;}
input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none;}
button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;}
textarea{overflow:auto;vertical-align:top;}
table{border-collapse:collapse;border-spacing:0;}
*,*:before,*:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;}
html{font-size:62.5%;-webkit-tap-highlight-color:rgba(0, 0, 0, 0);}
body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.428571429;color:#333333;background-color:#ffffff;}
input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit;}
button,input,select[multiple],textarea{background-image:none;}
a{color:#428bca;text-decoration:none;}a:hover,a:focus{color:#2a6496;text-decoration:underline;}
a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;}
img{vertical-align:middle;}
.img-responsive{display:block;max-width:100%;height:auto;}
.img-rounded{border-radius:6px;}
.img-thumbnail{padding:4px;line-height:1.428571429;background-color:#ffffff;border:1px solid #dddddd;border-radius:4px;-webkit-transition:all 0.2s ease-in-out;transition:all 0.2s ease-in-out;display:inline-block;max-width:100%;height:auto;}
.img-circle{border-radius:50%;}
hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eeeeee;}
.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);border:0;}
p{margin:0 0 10px;}
.lead{margin-bottom:20px;font-size:16.099999999999998px;font-weight:200;line-height:1.4;}@media (min-width:768px){.lead{font-size:21px;}}
small{font-size:85%;}
cite{font-style:normal;}
.text-muted{color:#999999;}
.text-primary{color:#428bca;}
.text-warning{color:#c09853;}
.text-danger{color:#b94a48;}
.text-success{color:#468847;}
.text-info{color:#3a87ad;}
.text-left{text-align:left;}
.text-right{text-align:right;}
.text-center{text-align:center;}
h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:500;line-height:1.1;}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small{font-weight:normal;line-height:1;color:#999999;}
h1,h2,h3{margin-top:20px;margin-bottom:10px;}
h4,h5,h6{margin-top:10px;margin-bottom:10px;}
h1,.h1{font-size:36px;}
h2,.h2{font-size:30px;}