Created
June 15, 2017 04:41
-
-
Save tardisx/20d5930b02c7c55f1b998da9b863c1ae to your computer and use it in GitHub Desktop.
< 200 line, single-file wiki implementation in Mojolicious::Lite
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env perl | |
use Mojolicious::Lite; | |
use Mojo::Pg; | |
use Mojo::Pg::Migrations; | |
use Text::Markdown qw/markdown/; | |
my $wiki_link = qr{\b[A-Z][a-z]+[A-Z][A-Za-z]+\b}; | |
helper pg => sub { state $pg = Mojo::Pg->new('postgresql://localhost/wiki') }; | |
helper page_link => sub { | |
my ($c, $p) = @_; | |
my $url = $c->url_for('page-show', page_name => $p->{page_name}); | |
return "<a href=\"$url\">$p->{page_name}</a>"; | |
}; | |
helper markdown => sub { | |
my ($c, $content) = @_; | |
my $new = markdown($content); | |
$new =~ s/($wiki_link)/$c->page_link({ page_name => $1 })/ge; | |
return $new; | |
}; | |
get '/' => sub { my $c = shift; $c->redirect_to($c->url_for('page-show', page_name => 'HomePage')); }; | |
get '/list' => sub { | |
my $c = shift; | |
my $pages = $c->pg->db->select('pages', undef, { current => 1}, { -desc => 'ts'})->hashes; | |
$c->render(template => 'list', pages => $pages); | |
} => 'list'; | |
get '/:page_name' => [ page_name => $wiki_link ] => sub { | |
my $c = shift; | |
my $page_data = $c->pg->db->select('pages', undef, { current => 1, page_name => $c->param('page_name') })->hash; | |
# dummy data if it doesn't exist yet | |
$page_data = { page_name => $c->param('page_name'), content => 'Brand new page! Create me!' } | |
if (! $page_data); | |
$c->render(template => 'page', page_data => $page_data); | |
} => 'page-show'; | |
get '/:page_name/edit' => [ page_name => $wiki_link ] => sub { | |
my $c = shift; | |
my $page_data = $c->pg->db->select('pages', undef, { current => 1, page_name => $c->param('page_name') })->hash; | |
# dummy data if it doesn't exist yet | |
$page_data = { page_name => $c->param('page_name'), content => 'Brand new page! Create me!' } | |
if (! $page_data); | |
$c->render(template => 'page_edit', page_data => $page_data); | |
} => 'page-edit'; | |
post '/:page_name/edit' => [ page_name => $wiki_link ] => sub { | |
my $c = shift; | |
my $page_name = $c->param('page_name'); | |
my $content = $c->param('content'); | |
my $last_page = $c->pg->db->update('pages', { current => 0 }, { current => 1, page_name => $page_name }, { returning => 'page_version' })->hash; | |
my $last_ver = $last_page ? $last_page->{page_version} : 0; | |
$c->pg->db->insert('pages', { ip => $c->tx->remote_address, page_version => $last_ver + 1, content => $content, current => 1, page_name => $c->param('page_name') }); | |
$c->redirect_to($c->url_for('page-show', page_name => $c->param('page_name'))); | |
} => 'page-update'; | |
Mojo::Pg::Migrations->new(pg => app->pg)->from_data->migrate; | |
app->start; | |
__DATA__ | |
@@ migrations | |
-- 1 up | |
-- on UPDATE, make a new row with an incremented version number | |
CREATE TABLE pages ( | |
id SERIAL NOT NULL PRIMARY KEY, | |
page_name TEXT NOT NULL, | |
page_version INT NOT NULL DEFAULT 1, | |
content TEXT NOT NULL, | |
ts TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), | |
current BOOLEAN NOT NULL DEFAULT 't', | |
ip CIDR, | |
UNIQUE (page_name, page_version) | |
); | |
CREATE UNIQUE INDEX one_current on pages(page_name) where current = 't'; | |
INSERT INTO pages (page_name, content) VALUES ('HomePage', '# Home' ); | |
-- 1 down | |
DROP TABLE pages; | |
@@ index.html.ep | |
% layout 'default'; | |
% title 'Welcome'; | |
<h1>Welcome to the Mojolicious real-time web framework!</h1> | |
@@ list.html.ep | |
% layout 'default'; | |
% title 'All Pages'; | |
<h1>All pages</h1> | |
<table class="table table condensed small"> | |
<tr><th>page name</th><th>last change</th><th>version</th><th>ip</th></tr> | |
% foreach my $page (@$pages) { | |
<tr><td><%== page_link($page) %></td><td><%= $page->{ts} %></td><td><%= $page->{page_version} %></td><td><%= $page->{ip} %></td></tr> | |
% } | |
</table> | |
@@ page.html.ep | |
% layout 'default'; | |
% title 'Page - ' . ($page_data->{page_name} // '??'); | |
<h1><%= $page_data->{page_name} // '??' %></h1> | |
%== markdown($page_data->{content}) | |
<p><a href="<%= url_for('page-edit', page_name => $page_data->{page_name}) %>">Edit</a>.</p> | |
@@ page_edit.html.ep | |
% layout 'default'; | |
% title 'Page Edit - ' . ($page_data->{page_name} // '??'); | |
<h1><%= $page_data->{page_name} // '??' %></h1> | |
%= form_for url_for() => method => 'post' => begin | |
%= text_area 'content' => $page_data->{content}, rows => 30, cols => 80; | |
<br> | |
%= submit_button 'submit' | |
% end | |
@@ css/default.css | |
body { | |
padding-top: 50px; | |
} | |
.starter-template { | |
padding: 40px 15px; | |
text-align: center; | |
} | |
@@ include/navbar.html.ep | |
<nav class="navbar navbar-inverse navbar-fixed-top"> | |
<div class="container"> | |
<div class="navbar-header"> | |
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar"> | |
<span class="sr-only">Toggle navigation</span> | |
<span class="icon-bar"></span> | |
<span class="icon-bar"></span> | |
<span class="icon-bar"></span> | |
</button> | |
<a class="navbar-brand" href="/">MojoWiki</a> | |
</div> | |
<div id="navbar" class="collapse navbar-collapse"> | |
<ul class="nav navbar-nav"> | |
<li class="active"><a href="<%= url_for('page-show', page_name => 'HomePage') %>">Home</a></li> | |
<li><a href="<%= url_for('list') %>">List</a></li> | |
</ul> | |
</div><!--/.nav-collapse --> | |
</div> | |
</nav> | |
@@ layouts/default.html.ep | |
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<meta http-equiv="X-UA-Compatible" content="IE=edge"> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<title><%= title %></title> | |
<script src="https://code.jquery.com/jquery-3.2.1.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script> | |
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> | |
<!-- Optional theme --> | |
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous"> | |
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> | |
<link href="/css/default.css" rel="stylesheet"> | |
</head> | |
<body> | |
%= include 'include/navbar' | |
<div class="container"> | |
<%= content %> | |
</div> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment