Add announcement page
This commit is contained in:
parent
91512b48be
commit
905e885041
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="TemplateDataLanguageMappings">
|
||||||
|
<file url="file://$PROJECT_DIR$/templates/main/announcements.rss.ep" dialect="XML" />
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -6,9 +6,10 @@ use ExtUtils::MakeMaker;
|
||||||
WriteMakefile(
|
WriteMakefile(
|
||||||
VERSION => '0.01',
|
VERSION => '0.01',
|
||||||
PREREQ_PM => {
|
PREREQ_PM => {
|
||||||
'Mojolicious' => '9.27',
|
'Mojolicious' => '9.27',
|
||||||
'Email::MIME' => '1.952',
|
'Email::MIME' => '1.952',
|
||||||
'Email::Sender::Simple' => '2.500'
|
'Email::Sender::Simple' => '2.500',
|
||||||
|
'File::ChangeNotify' => '0.31',
|
||||||
},
|
},
|
||||||
test => {TESTS => 't/*.t'}
|
test => {TESTS => 't/*.t'}
|
||||||
);
|
);
|
||||||
|
|
|
@ -32,6 +32,10 @@ sub startup ($self) {
|
||||||
$r->post('/account')->to('Account#update_account');
|
$r->post('/account')->to('Account#update_account');
|
||||||
|
|
||||||
$r->get('/users')->to('Main#user_pages');
|
$r->get('/users')->to('Main#user_pages');
|
||||||
|
|
||||||
|
$r->get('/announcements')->to('Main#announcements');
|
||||||
|
$r->get('/announcements.rss')->to('Main#announcement_feed');
|
||||||
|
$r->get('/announcement/#filename')->to('Main#get_announcement');
|
||||||
}
|
}
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
|
|
@ -5,6 +5,11 @@ use experimental 'signatures';
|
||||||
|
|
||||||
use Mojo::Base 'Mojolicious::Controller', -signatures;
|
use Mojo::Base 'Mojolicious::Controller', -signatures;
|
||||||
use Net::LDAPS;
|
use Net::LDAPS;
|
||||||
|
use File::ChangeNotify;
|
||||||
|
use File::stat;
|
||||||
|
use File::Basename;
|
||||||
|
use Time::Piece;
|
||||||
|
use Scalar::Util qw(looks_like_number);
|
||||||
|
|
||||||
sub index ($self) {
|
sub index ($self) {
|
||||||
$self->render();
|
$self->render();
|
||||||
|
@ -25,6 +30,9 @@ Mojo::IOLoop->recurring(60 => sub {
|
||||||
$cache->set(users => []);
|
$cache->set(users => []);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
my $acache = Mojo::Cache->new(max_keys => 1);
|
||||||
|
my $filewatcher;
|
||||||
|
|
||||||
sub user_pages ($self) {
|
sub user_pages ($self) {
|
||||||
my $config = $self->config;
|
my $config = $self->config;
|
||||||
my $connStr = $config->{'ldap'}->{'uri'};
|
my $connStr = $config->{'ldap'}->{'uri'};
|
||||||
|
@ -60,4 +68,166 @@ sub user_pages ($self) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sub announcements ($self) {
|
||||||
|
if (!defined $acache->get('announcements')) {
|
||||||
|
setup_filewatch($self->config->{'announce'}->{'path'});
|
||||||
|
} else {
|
||||||
|
check_files();
|
||||||
|
}
|
||||||
|
|
||||||
|
my %announcements = %{$acache->get('announcements')};
|
||||||
|
my $page = $self->param('page');
|
||||||
|
if (!defined $page) {
|
||||||
|
$page = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!looks_like_number($page) || $page < 0) {
|
||||||
|
return $self->reply->not_found;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
my $total_items = scalar(keys %announcements);
|
||||||
|
my $items_per_page = 5;
|
||||||
|
|
||||||
|
my $start_index = $page * $items_per_page;
|
||||||
|
my $end_index = $start_index + $items_per_page-1;
|
||||||
|
|
||||||
|
if ($start_index >= $total_items) {
|
||||||
|
return $self->reply->not_found;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $next_page = -1;
|
||||||
|
if ($end_index >= $total_items) {
|
||||||
|
$end_index = $total_items-1;
|
||||||
|
} elsif ($end_index != $total_items-1){
|
||||||
|
$next_page = $page + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$self->stash(
|
||||||
|
announcements => \%announcements,
|
||||||
|
total_items => $total_items,
|
||||||
|
items_per_page => $items_per_page,
|
||||||
|
page => $page,
|
||||||
|
start_index => $start_index,
|
||||||
|
end_index => $end_index,
|
||||||
|
next_page => $next_page,
|
||||||
|
);
|
||||||
|
$self->render();
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get_announcement ($self) {
|
||||||
|
if (!defined $acache->get('announcements')) {
|
||||||
|
setup_filewatch($self->config->{'announce'}->{'path'});
|
||||||
|
} else {
|
||||||
|
check_files();
|
||||||
|
}
|
||||||
|
|
||||||
|
my $announcements = $acache->get('announcements');
|
||||||
|
my $filename = $self->param('filename');
|
||||||
|
if (!defined $announcements->{$filename}) {
|
||||||
|
return $self->reply->not_found;
|
||||||
|
}
|
||||||
|
my $entry = $announcements->{$filename};
|
||||||
|
$self->render(text => $entry->{'raw'}, format => 'txt');
|
||||||
|
}
|
||||||
|
|
||||||
|
sub announcement_feed ($self) {
|
||||||
|
if (!defined $acache->get('announcements')) {
|
||||||
|
setup_filewatch($self->config->{'announce'}->{'path'});
|
||||||
|
} else {
|
||||||
|
check_files();
|
||||||
|
}
|
||||||
|
|
||||||
|
my $announcements = $acache->get('announcements');
|
||||||
|
my @chosen = sort (keys %$announcements);
|
||||||
|
|
||||||
|
if (scalar(@chosen) > 5) {
|
||||||
|
@chosen = @chosen[0..4];
|
||||||
|
}
|
||||||
|
|
||||||
|
my @elements;
|
||||||
|
|
||||||
|
for my $key (@chosen) {
|
||||||
|
push @elements, $announcements->{$key};
|
||||||
|
}
|
||||||
|
|
||||||
|
$self->stash(items => \@elements);
|
||||||
|
$self->render('main/announcements', format => 'rss');
|
||||||
|
}
|
||||||
|
|
||||||
|
sub setup_filewatch($path) {
|
||||||
|
opendir my $dir, $path or die "Could not open directory!";
|
||||||
|
my @files = readdir $dir;
|
||||||
|
closedir $dir;
|
||||||
|
|
||||||
|
my %announcements;
|
||||||
|
|
||||||
|
for my $filename (@files) {
|
||||||
|
if ($filename eq "." || $filename eq "..") {
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $entry = get_entry($path.$filename);
|
||||||
|
if (!defined $entry) {
|
||||||
|
say "Invalid entry at $filename";
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
|
||||||
|
$announcements{$filename} = $entry;
|
||||||
|
}
|
||||||
|
$acache->set(announcements => \%announcements);
|
||||||
|
|
||||||
|
$filewatcher = File::ChangeNotify->instantiate_watcher(
|
||||||
|
directories => [$path],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub check_files {
|
||||||
|
for my $event ($filewatcher->new_events) {
|
||||||
|
if ($event->path =~ /\.txt$/) {
|
||||||
|
my $filename = basename($event->path);
|
||||||
|
if ($event->type eq 'create' || $event->type eq 'modify') {
|
||||||
|
my $entry = get_entry($event->path);
|
||||||
|
if (!defined $entry) {
|
||||||
|
say "Invalid entry at $filename";
|
||||||
|
delete $acache->get('announcements')->{$filename};
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
$acache->get('announcements')->{$filename} = get_entry($event->path);
|
||||||
|
} elsif ($event->type eq 'delete') {
|
||||||
|
delete $acache->get('announcements')->{$filename};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get_entry($fullpath) {
|
||||||
|
open FH, '<', $fullpath or die "Could not open file! $!";
|
||||||
|
read FH, my $announce_content, -s FH;
|
||||||
|
close FH;
|
||||||
|
my $filestat = stat($fullpath);
|
||||||
|
my $announce_text;
|
||||||
|
my $announce_title;
|
||||||
|
if ($announce_content =~ /-----BEGIN PGP SIGNED MESSAGE-----\nHash: \w+\n\n(.+?)\n\n(.*)\n-----BEGIN PGP SIGNATURE-----/s) {
|
||||||
|
$announce_title = $1;
|
||||||
|
$announce_text = $2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!defined $announce_title || !defined $announce_content || length $announce_title == 0 || length $announce_content == 0) {
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
raw => $announce_content,
|
||||||
|
text => $announce_text,
|
||||||
|
date => $filestat->mtime,
|
||||||
|
filename => basename($fullpath),
|
||||||
|
title => $announce_title,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
|
|
@ -140,6 +140,10 @@ textarea {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
#header-content a:link {
|
#header-content a:link {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: black;
|
color: black;
|
||||||
|
|
|
@ -3,8 +3,9 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title><%= title %></title>
|
<title><%= title %></title>
|
||||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
||||||
<link rel="stylesheet" href="css/main.css">
|
<link rel="stylesheet" href="/css/main.css">
|
||||||
|
<link rel="alternate" type="application/rss+xml" title="UNIX.dog Announcements" href="https://unix.dog/announcements.rss" >
|
||||||
|
|
||||||
<meta property="og:title" content="<%= title %>">
|
<meta property="og:title" content="<%= title %>">
|
||||||
<meta property="og:type" content="webpage">
|
<meta property="og:type" content="webpage">
|
||||||
|
@ -19,11 +20,12 @@
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<div id="header-content">
|
<div id="header-content">
|
||||||
<img src="favicon.ico" width="32" height="32" alt="UNIX.dog"/>
|
<img src="/favicon.ico" width="32" height="32" alt="UNIX.dog"/>
|
||||||
<h1><a href="/">UNIX.dog</a></h1>
|
<h1><a href="/">UNIX.dog</a></h1>
|
||||||
<div id="nav">
|
<div id="nav">
|
||||||
<a href="/users">Users</a>
|
<a href="/users">Users</a>
|
||||||
<a href="/account">Account</a>
|
<a href="/account">Account</a>
|
||||||
|
<a href="/announcements">Announcements</a>
|
||||||
<a href="/services">Services</a>
|
<a href="/services">Services</a>
|
||||||
<a href="/register">Register</a>
|
<a href="/register">Register</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
% layout 'default';
|
||||||
|
% title 'UNIX.dog Announcements';
|
||||||
|
% use Time::Piece;
|
||||||
|
<h1>
|
||||||
|
Announcements
|
||||||
|
</h1>
|
||||||
|
<p>
|
||||||
|
These announcements are also avaiable over RSS. Just add .rss to the path here.
|
||||||
|
<br>
|
||||||
|
Also, all announcements are signed with the following PGP key:
|
||||||
|
</p>
|
||||||
|
<pre>67A3 9437 8618 B72E B4D5 CC69 B0EB E117 49B9 9367 alpha@unix.dog</pre>
|
||||||
|
<p>
|
||||||
|
so you can verify their authenticity. When a new announcement is out, you should
|
||||||
|
also recieve an email if you're registered.
|
||||||
|
</p>
|
||||||
|
<% my @sorted = sort (keys %$announcements); %>
|
||||||
|
<% foreach my $key (@sorted[$start_index..$end_index]) { %>
|
||||||
|
<% my $value = $announcements->{$key}; %>
|
||||||
|
<article>
|
||||||
|
<h2><%= $value->{"title"} %></h2>
|
||||||
|
<p>
|
||||||
|
Posted <%= localtime($value->{"date"})->strftime() %>.
|
||||||
|
<a href="/announcement/<%= $value->{"filename"} %>">Download PGP signature.</a>
|
||||||
|
<p>
|
||||||
|
<pre><%= $value->{"text"} %></pre>
|
||||||
|
</article>
|
||||||
|
<% } %>
|
||||||
|
<% if ($next_page != -1) { %>
|
||||||
|
<a href="/announcements?page=<%= $next_page %>">Next page...</a>
|
||||||
|
<% } %>
|
|
@ -0,0 +1,26 @@
|
||||||
|
% use Time::Piece;
|
||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
||||||
|
<channel>
|
||||||
|
<title>UNIX.dog Announcements</title>
|
||||||
|
<link>https://unix.dog/announcements</link>
|
||||||
|
<atom:link href="https://unix.dog/announcements.rss" rel="self" type="application/rss+xml"/>
|
||||||
|
<description>UNIX.dog Announcements</description>
|
||||||
|
<language>en-us</language>
|
||||||
|
<image>
|
||||||
|
<title>UNIX.dog Announcements</title>
|
||||||
|
<url>https://unix.dog/favicon.ico</url>
|
||||||
|
<link>https://unix.dog/announcements</link>
|
||||||
|
</image>
|
||||||
|
% foreach my $item (@$items) {
|
||||||
|
<item>
|
||||||
|
<title><![CDATA[<%= $item->{'title'} %>]]></title>
|
||||||
|
<link>https://unix.dog/announcement/<%= $item->{'filename'} %></link>
|
||||||
|
<guid isPermaLink="true">https://unix.dog/announcement/<%= $item->{'filename'} %></guid>
|
||||||
|
<description><![CDATA[<%== $item->{'text'} %>]]></description>
|
||||||
|
<author>Alpha</author>
|
||||||
|
<pubDate><%= localtime($item->{'date'})->strftime("%a, %d %b %Y %H:%M:%S %z") %></pubDate>
|
||||||
|
</item>
|
||||||
|
% }
|
||||||
|
</channel>
|
||||||
|
</rss>
|
|
@ -12,3 +12,7 @@ ldap:
|
||||||
uri: 'ldaps://127.0.0.1'
|
uri: 'ldaps://127.0.0.1'
|
||||||
bindDN: 'here'
|
bindDN: 'here'
|
||||||
password: 'here'
|
password: 'here'
|
||||||
|
|
||||||
|
# Leading slash
|
||||||
|
announce:
|
||||||
|
path: '/srv/announce/'
|
||||||
|
|
Loading…
Reference in New Issue