Add announcement page

This commit is contained in:
Citlali del Rey 2022-11-03 10:42:42 -07:00
parent 91512b48be
commit 905e885041
Signed by: nullobsi
GPG Key ID: 933A1F44222C2634
9 changed files with 254 additions and 6 deletions

View File

@ -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>

View File

@ -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'}
); );

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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>

View File

@ -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>
<% } %>

View File

@ -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>

View File

@ -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/'