Skip to content

Instantly share code, notes, and snippets.

@tardisx
Created June 15, 2017 04:41
Show Gist options
  • Save tardisx/20d5930b02c7c55f1b998da9b863c1ae to your computer and use it in GitHub Desktop.
Save tardisx/20d5930b02c7c55f1b998da9b863c1ae to your computer and use it in GitHub Desktop.
< 200 line, single-file wiki implementation in Mojolicious::Lite
#!/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