package unix_dog::Controller::Main; use strict; use warnings FATAL => 'all'; use experimental 'signatures'; use Mojo::Base 'Mojolicious::Controller', -signatures; 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) { $self->render(); } sub rules ($self) { $self->render(); } sub services ($self) { $self->render(); } my $cache = Mojo::Cache->new(max_keys => 1); $cache->set(users => []); Mojo::IOLoop->recurring(60 => sub { $cache->set(users => []); }); my $acache = Mojo::Cache->new(max_keys => 1); my $filewatcher; sub user_pages ($self) { my $config = $self->config; my $connStr = $config->{'ldap'}->{'uri'}; my $users = $cache->get('users'); if (scalar @{$users} != 0) { $self->stash(users => $users); return $self->render('main/usersList'); } return Mojo::IOLoop->subprocess->run_p(sub { my $ldap = Net::LDAPS->new($connStr, verify=>'none', version=>3) or die "$@"; my $mesg = $ldap->bind(); $mesg->code and die $mesg->error; $mesg = $ldap->search( base => 'OU=Dogs,DC=unix,DC=dog', filter => '(objectClass=posixAccount)', attrs => ['uid', 'createTimestamp'], ); $mesg->code and die $mesg->error; my @sorted = sort {$a->get_value('createTimestamp') cmp $b->get_value('createTimestamp')} $mesg->entries; for (@sorted) { push @{$users}, $_->get_value('uid'); } return $users; })->then(sub (@results) { $self->stash(users => (@results)); $cache->set(users => (@results)); $self->render('main/usersList'); }); } 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; } my @chosen = (sort { $b cmp $a } (keys %announcements))[$start_index..$end_index]; my @items; for my $key (@chosen) { push @items, $announcements{$key}; } $self->stash( next_page => $next_page, items => \@items, ); $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; } my $url = Mojo::URL->new("https://unix.dog/announcement/"); $url->path(basename($fullpath)); return { raw => $announce_content, text => $announce_text, date => $filestat->mtime, filename => basename($fullpath), title => $announce_title, url => $url, }; } sub get_stats($self) { return $self->render('main/statsPage'); } 1;