# /lib/MyApp/Controller/Root.pm package MyApp::Controller::Root; use Mojo::Base 'Mojolicious::Controller'; use Tools; use Cwd qw(abs_path getcwd); use Mojo::File 'path'; use HTML::Entities qw(encode_entities); use Mojo::Util qw(trim); # Controller for core application logic and utility pages. # Features: # - Main dashboard rendering # - Source code viewing utility (Restricted scope) # - Static page routing (Contact, Terms, Privacy) # - Admin Clipboard/Pastebin tool with notification integration # Integration points: # - Uses Tools.pm for date/age calculations # - Uses DB helpers for clipboard persistence and push notifications # Renders the main application dashboard. # Route: GET / # Parameters: None # Returns: # Rendered HTML template 'index' with client IP and visit stats sub index { my $c = shift; my $client_ip = $c->tx->remote_address; # Track session visit frequency my $last_visit = $c->session('last_visit') || time(); $c->session(last_visit => time()); $c->stash( client_ip => $client_ip, last_visit => $last_visit ); $c->render('index'); } # Renders a generic permission denied page. # Route: Internal redirect # Parameters: None # Returns: # Rendered HTML template 'noperm' sub no_permission { shift->render('noperm') } # Utility to view server-side source files via the browser. # Route: GET /source # Parameters: # f : Relative path to the file (e.g., 'lib/MyApp.pm') # Returns: # Raw text content of the file if allowed # 400/403/404 Error status codes otherwise # Security: # - Enforces strict directory whitelisting (public, templates, lib, script) sub view_source { my $c = shift; # Define project root my $base_dir = abs_path(path($c->app->home)->child('.')); my $file_param = $c->param('f'); return $c->render(text => "Invalid file.", status => 400) unless $file_param; # Resolve paths to absolute system paths my $requested_path = path($base_dir, $file_param)->to_string; my $real_path = abs_path($requested_path); return $c->render(text => "File not found.", status => 404) unless $real_path; # Define allowed directories for security whitelist my $public_dir = abs_path(path($base_dir, 'public')); my $templates_dir = abs_path(path($base_dir, 'templates')); my $lib_dir = abs_path(path($base_dir, 'lib')); my $script_path = abs_path(path($base_dir, 'mojo.pl')); # Check if requested file resides within allowed directories my $is_allowed = 0; if ($real_path) { $is_allowed = ($real_path eq $script_path) || ($real_path =~ m{^\Q$public_dir\E/}) || ($real_path =~ m{^\Q$templates_dir\E/}) || ($real_path =~ m{^\Q$lib_dir\E/}); } # Serve file content if security check passes if ($is_allowed && -f $real_path) { my $text = path($real_path)->slurp; $c->render(text => $text, format => 'txt'); } else { $c->render(text => "Access denied.", format => 'txt', status => 403); } } # Debug utility to display current working directory. # Route: GET /cwd # Returns: Plain text path sub cwd { shift->render(text => "CWD: " . getcwd()) } # JSON API providing age calculations for dashboard widgets. # Route: GET /api/age # Parameters: None # Returns: # JSON object containing age strings (years/months/days) for configured users sub age { my $c = shift; # Fetch DOB configuration my $dob = $c->db->dob(); # Calculate ages using Tools.pm helper my @andrea = howOld($dob->{andrea}->{dob}); my @nicky = howOld($dob->{nicky}->{dob}); $c->render( json => { andrea => $andrea[0], andreas => $andrea[1], nicky => $nicky[0], nickys => $nicky[1], } ); } # Static Page Renders sub t_page { shift->render('t') } sub p_page { shift->render('p') } sub sus { shift->render('sus') } # Renders the Contact page. # Route: GET /contact # Returns: # Rendered HTML template 'contact' with list of QR code images sub contact { my $c = shift; my @qr_images = qw(discord.png email.png line.jpg messenger.png); $c->render(template => 'contact', qr_images => \@qr_images); } # Renders the Admin Clipboard/Pastebin interface. # Route: GET /copy # Parameters: None # Returns: # Rendered HTML template 'copy/copy' with history sub copy_get { my $c = shift; # Enforce Admin Access Control return $c->redirect_to('/noperm') unless $c->is_admin; my @msgs = $c->db->get_pasted(); my $client_ip = $c->tx->remote_address; $c->stash( messages => \@msgs, client_ip => $client_ip, is_admin => $c->is_admin ); $c->render('copy/copy'); } # Saves a new item to the Admin Clipboard. # Route: POST /copy # Parameters: # paste : Text content or URL to save # Returns: # Redirects to clipboard page on success # Behavior: # - Encodes HTML entities for safety # - Validates URL format if input looks like a link # - Triggers external notifications (Pushover, Gotify) sub copy_post { my $c = shift; # Enforce Admin Access Control return $c->redirect_to('/noperm') unless $c->is_admin; my $text = trim($c->param('paste') // ''); $text = encode_entities($text); # Validate URL format to prevent malformed links if ($text =~ m{^https?://}i) { unless ($text =~ m{^https?://[\w\-]+(?:\.[\w\-]+)+(?:/[^\s]*)?$}i) { return $c->render_error('Invalid URL'); } } # Persist to database $c->db->paste($text); # Dispatch external notifications $c->db->push_over($text); $c->db->push_gotify($text); return $c->redirect_to('/copy'); } # Removes an item from the Admin Clipboard. # Route: POST /copy/delete # Parameters: # id : Unique ID of the message to delete # Returns: # Redirects to clipboard page sub remove_message { my $c = shift; # Enforce Admin Access Control return $c->redirect_to('/noperm') unless $c->is_admin; my $id = $c->param('id'); unless (defined $id && $id =~ /^\d+$/) { return $c->render_error('Invalid ID'); } $c->db->delete_message($id); $c->redirect_to('/copy'); } 1;