diff --git a/Web/lib/MJB/Web.pm b/Web/lib/MJB/Web.pm index 7c765f9..dc6cb1d 100644 --- a/Web/lib/MJB/Web.pm +++ b/Web/lib/MJB/Web.pm @@ -68,13 +68,20 @@ sub startup ($self) { # Helper to redirect on errors, support setting the form and errors in a flash # if they exist in the stash. - $self->helper( redirect_error => sub ( $c, $redirect_to, $redirect_args = {} ) { + $self->helper( redirect_error => sub ( $c, $redirect_to, $redirect_args = {}, $errors = [] ) { + push @{$c->stash->{errors}}, @{$errors} if $errors; $c->flash( form => $c->stash->{form} ) if $c->stash->{form}; $c->flash( errors => $c->stash->{errors} ) if $c->stash->{errors}; $c->redirect_to( $c->url_for( $redirect_to, $redirect_args ) ); }); + # Helper to redirect on success, support setting a message and redirecting to a named route. + $self->helper( redirect_success => sub ( $c, $redirect_to, $success_message ) { + $c->flash( confirmation => $success_message ); + $c->redirect_to( $c->url_for( $redirect_to ) ); + }); + # Minion plugin & tasks $self->plugin( Minion => { Pg => $self->config->{database}->{minion} } ); diff --git a/Web/lib/MJB/Web/Controller/Admin.pm b/Web/lib/MJB/Web/Controller/Admin.pm index d5c3c24..23ca202 100644 --- a/Web/lib/MJB/Web/Controller/Admin.pm +++ b/Web/lib/MJB/Web/Controller/Admin.pm @@ -2,26 +2,30 @@ package MJB::Web::Controller::Admin; use Mojo::Base 'Mojolicious::Controller', -signatures; use Try::Tiny; +#== +# GET /admin | show_admin +# +# Redirect to the people listing page. +#== sub index ( $c ) { $c->redirect_to( 'show_admin_people' ); } -# POST /admin +#== +# POST /admin | do_admin_become +# uid | A user id, that the admin would like to become +# bid | A blog id, that the admin will go to the manage page for +# url | A URL to return to when the admin logs out of the user account # # An admin may inpersonate any other user for technical support purposes, # this code is called to become another user. Sign out to become your origional # user again. # -# INPUT: -# uid | A user id -# bid | A blog id belonging to the user -# url | A URL to return to when the admin logs out of the user account -# # When given a uid, become that user and go to the user's dashboard. # # When given a uid and a bid that the user owns, become that user # and go to the blog's dashboard. -# +#== sub do_admin_become ( $c ) { my ( $uid, $bid, $url ) = ( $c->param('uid'), $c->param('bid'), $c->param('url') ); @@ -38,15 +42,31 @@ sub do_admin_become ( $c ) { } } +#== +# GET /admin/people | show_admin_people +# +# This route shows people, users, who exist on this system. +#== sub people ( $c ) { my $people = $c->stash->{people} = [ $c->db->people->all ]; } +#== +# GET /admin/person/:id | show_admin_person, { id => ? } +# +# This route shows a given person, their blogs, and notes about them. +#== sub person ( $c ) { my $profile = $c->stash->{profile} = $c->db->person( $c->param('id') ); my $notes = $c->stash->{notes} = [ $profile->person_note_people->all ]; } +#== +# POST /admin/person/:id/note | do_admin_person_note { id => person.id } +# content | The content of the message about this user. +# +# This route makes a note about a person on their profile page. +#== sub do_person_note ( $c ) { my $profile = $c->db->person( $c->param('id') ); my $content = $c->param('content'); @@ -58,24 +78,35 @@ sub do_person_note ( $c ) { $c->flash( confirmation => "Added note to this account." ); $c->redirect_to( $c->url_for( 'show_admin_person', { id => $profile->id } ) ); - } +#== +# GET /admin/blogs | show_admin_blogs +# +# This route lists blogs that are hosted on this system. +#== sub blogs ( $c ) { my $blogs = $c->stash->{blogs} = [ $c->db->blogs->all ]; } -sub servers ( $c ) { - my $servers = $c->stash->{servers} = [ $c->db->servers->all ]; - -} - +#== +# GET /admin/invites | show_admin_invites +# +# This route shows the invites that currently exist and can be used. +#== sub invites ( $c ) { my $invites = $c->stash->{invites} = [ $c->db->invites->all ]; } +#== +# POST /admin/invite | do_admin_invite +# code | The invite code, this is case-sensitive. +# is_multi_use | When true, the code may be used more than once. +# +# This route adds an invite code. +#== sub do_invite ( $c ) { my $code = $c->param('code'); my $is_multi_use = $c->param('is_multi_use' ); @@ -88,29 +119,50 @@ sub do_invite ( $c ) { }); }); } catch { - $c->flash( error_message => "Invite Code could not be created: $_" ); + push @{$c->stash->{errors}}, "Invite code could not be created: $_"; }; + + return $c->redirect_error( 'show_admin_invites' ) + if $c->stash->{errors}; - $c->redirect_to( $c->url_for( 'show_admin_invites' ) ); - return; + return $c->redirect_success( 'show_admin_invites', "Added $code to invites." ); } +#== +# POST /admin/invite/remove | do_admin_invite_remove +# iid | The id of the invite code to delete +# +# This route deletes an invite code. +#== sub do_invite_remove ( $c ) { my $invite = $c->db->invite($c->param('iid')); - if ( ! $invite ) { - $c->flash( error_message => "Invite could not be removed, because it doesn't exist?" ); - $c->redirect_to( $c->url_for( 'show_admin_invites' ) ); - return; - } + return $c->redirect_error( 'show_admin_invites', {}, [ 'The invite does not exist' ] ) + unless $invite; my $code = $invite->code; $invite->delete; - $c->flash( confirmation => "Removed $code from invite pool." ); - $c->redirect_to( $c->url_for( 'show_admin_invites' ) ); + return $c->redirect_success( 'show_admin_invites', "Removed $code from invite pool." ); +} + +#== +# GET /admin/servers | show_admin_servers +# +# This route shows servers that blogs are hosted on, and deployed to. +#== +sub servers ( $c ) { + my $servers = $c->stash->{servers} = [ $c->db->servers->all ]; } +#== +# POST /admin/server | do_admin_server +# server_fqdn | The domain name to use for the server, builder and certbot +# servers should have ssh access to these servers. +# +# This route adds a server to the pool for deployment. These servers are used by +# the certbot and builder servers to deploy ssl certs and blogs to. +#== sub do_server ( $c ) { my $fqdn = $c->param('server_fqdn'); @@ -119,36 +171,53 @@ sub do_server ( $c ) { $c->db->servers->create({ hostname => $fqdn }); }); } catch { - $c->flash( error_message => "Server could not be created: $_" ); - $c->redirect_to( $c->url_for( 'show_admin_servers' ) ); - return; + push @{$c->stash->{errors}}, "Server could not be created: $_"; }; + + return $c->redirect_error( 'show_admin_servers' ) + if $c->stash->{errors}; - $c->flash( confirmation => "Added $fqdn to server pool." ); - $c->redirect_to( $c->url_for( 'show_admin_servers' ) ); + return $c->redirect_success( 'show_admin_servers', "Added $fqdn to server pool." ); } +#== +# POST /admin/server/remove | do_admin_server_remove +# sid | The id of the server to remove +# +# This route removes a server from the rotation used for deploying blogs/ssl certs. +#== sub do_server_remove ( $c ) { my $server = $c->db->server($c->param('sid')); - if ( ! $server ) { - $c->flash( error_message => "Server could not be removed, because it doesn't exist?" ); - $c->redirect_to( $c->url_for( 'show_admin_servers' ) ); - return; - } + return $c->redirect_error( 'show_admin_servers', {}, [ 'The server does not exist' ] ) + unless $server; my $hostname = $server->hostname; $server->delete; - $c->flash( confirmation => "Removed $hostname from server pool." ); - $c->redirect_to( $c->url_for( 'show_admin_servers' ) ); + return $c->redirect_success( 'show_admin_servers', "Removed $hostname to server pool." ); } +#== +# GET /admin/domains | show_admin_domains +# +# This route shows domains that users can host their blogs under. +#== sub domains ( $c ) { - my $domains = $c->stash->{domains} = [ $c->db->hosted_domains->all ]; - + $c->stash->{domains} = [ $c->db->hosted_domains->all ]; } +#== +# POST /admin/domain | do_admin_domain +# domain_fqdn | The fully qualified domain name we will use for hosting (i.e. foobar.net) +# ssl_challenge | The challenge type -- dns-linode or http to use for validating domains +# +# This route will add a domain to use for hosting. For example, if one adds foobar.net, then +# users will be able to host blogs like myblog.foobar.net. +# +# http challenges will use the certbot server and /.well-known/ forwarding for creation/updating w/ --standalone +# dns-linode challenges will use the --dns-linode plugin and credentials expected to be done with ansible. +#== sub do_domain ( $c ) { my $fqdn = $c->param('domain_fqdn'); my $ssl = $c->param('ssl_challenge'); @@ -158,35 +227,44 @@ sub do_domain ( $c ) { $c->db->hosted_domains->create({ name => $fqdn, letsencrypt_challenge => $ssl }); }); } catch { - $c->flash( error_message => "domain could not be created: $_" ); - $c->redirect_to( $c->url_for( 'show_admin_domains' ) ); - return; + push @{$c->stash->{errors}}, "Domain could not be created: $_"; }; + return $c->redirect_error( 'show_admin_domains' ) + if $c->stash->{errors}; + if ( $ssl eq 'dns-linode' ) { $c->minion->enqueue( 'mk_wildcard_ssl', [ $domain->id ], { queue => 'certbot' } ); } - $c->flash( confirmation => "Added $fqdn to domain pool." ); - $c->redirect_to( $c->url_for( 'show_admin_domains' ) ); + return $c->redirect_success( 'show_admin_domains', "Added $fqdn to domain pool." ); } +#== +# POST /admin/domain/remove | do_admin_domain_remove +# did | The ID for the domain to remove. +# +# This route will remove a hosted domain by its ID. +#== sub do_domain_remove ( $c ) { my $domain = $c->db->hosted_domain($c->param('did')); - if ( ! $domain ) { - $c->flash( error_message => "domain could not be removed, because it doesn't exist?" ); - $c->redirect_to( $c->url_for( 'show_admin_domains' ) ); - return; - } + return $c->redirect_error( 'show_admin_domains', {}, [ "That domain doesn't seem to exist." ] ) + unless $domain; my $hostname = $domain->name; $domain->delete; - $c->flash( confirmation => "Removed $hostname from domain pool." ); - $c->redirect_to( $c->url_for( 'show_admin_domains' ) ); + return $c->redirect_success( 'show_admin_domains', "Removed $hostname from domain pool." ); } +#== +# POST /admin/update_ssl | do_admin_update_ssl +# +# This route will schedule a job for update_ssl SSL certs on +# the certbot server and then sync them with the webserver. +# certbot server to the webservers. +#== sub do_update_ssl ( $c ) { my $id = $c->minion->enqueue( 'update_ssl_certs', [ ], { queue => 'certbot', @@ -194,10 +272,15 @@ sub do_update_ssl ( $c ) { }); $c->db->admin_jobs->create({ minion_job_id => $id }); - $c->flash( confirmation => "Scheduled job to update SSL certs." ); - $c->redirect_to( $c->url_for( 'show_admin_jobs' ) ); + return $c->redirect_success( 'show_admin_jobs', 'Scheduled job to update SSL certs.' ); } +#== +# POST /admin/sync_ssl | do_admin_sync_ssl +# +# This route will schedule a job for syncing SSL certs from the +# certbot server to the webservers. +#== sub do_sync_ssl ( $c ) { my $id = $c->minion->enqueue( 'sync_ssl_certs', [ ], { queue => 'certbot', @@ -205,58 +288,61 @@ sub do_sync_ssl ( $c ) { }); $c->db->admin_jobs->create({ minion_job_id => $id }); - $c->flash( confirmation => "Scheduled job to sync SSL certs." ); - $c->redirect_to( $c->url_for( 'show_admin_jobs' ) ); + return $c->redirect_success( 'show_admin_jobs', 'Scheduled job to sync SSL certs.' ); } +#== +# POST /admin/alert/read | do_admin_alert_read +# nid | The ID for the system_note +# +# This route will mark a system_note as read when given the note id. +#== sub do_alert_read ( $c ) { my $note = $c->db->system_note( $c->param('nid') ); - if ( ! $note ) { - $c->flash( error_message => "Note could not be marked as read, because it doesn't exist?" ); - $c->redirect_to( $c->url_for( 'show_admin_alerts' ) ); - return; - } + return $c->redirect_error( 'show_admin_alerts', {}, [ "That note doesn't seem to exist." ] ) + unless $note; $note->is_read( 1 ); $note->update; - $c->flash( confirmation => "Note marked as read." ); - $c->redirect_to( $c->url_for( 'show_admin_alerts' ) ); + return $c->redirect_success( 'show_admin_alerts', 'Note marked as read.' ); } +#== +# POST /admin/alert/unread | do_admin_alert_unread +# nid | The ID for the system_note +# +# This route will mark a system_note as unread when given the note id. +#== sub do_alert_unread ( $c ) { my $note = $c->db->system_note( $c->param('nid') ); - if ( ! $note ) { - $c->flash( error_message => "Note could not be marked as unread, because it doesn't exist?" ); - $c->redirect_to( $c->url_for( 'show_admin_alerts' ) ); - return; - } + return $c->redirect_error( 'show_admin_alerts', {}, [ "That note doesn't seem to exist." ] ) + unless $note; $note->is_read( 0 ); $note->update; - $c->flash( confirmation => "Note marked as read." ); - $c->redirect_to( $c->url_for( 'show_admin_alerts' ) ); - + return $c->redirect_success( 'show_admin_alerts', 'Note marked as unread.' ); } +#== +# POST /admin/alert/remove | do_admin_alert_remove +# nid | The ID for the system_note +# +# This route will delete a system_note when given the note id. +#== sub do_alert_remove ( $c ) { my $note = $c->db->system_note( $c->param('nid') ); - if ( ! $note ) { - $c->flash( error_message => "Note could not be removed, because it doesn't exist?" ); - $c->redirect_to( $c->url_for( 'show_admin_alerts' ) ); - return; - } + return $c->redirect_error( 'show_admin_alerts', {}, [ "That note doesn't seem to exist." ] ) + unless $note; $note->delete; - $c->flash( confirmation => "Note removed" ); - $c->redirect_to( $c->url_for( 'show_admin_alerts' ) ); - + return $c->redirect_success( 'show_admin_alerts', 'Note removed.' ); } 1;