mirror of
https://github.com/symkat/MeshMage.git
synced 2024-09-20 06:46:04 +08:00
Make UserManagement like everything else.
This commit is contained in:
parent
7ca9fc0b5f
commit
5df89f5057
|
@ -43,18 +43,11 @@ sub startup ($self) {
|
|||
$self->plugin( 'MeshMage::Web::Plugin::MinionTasks' );
|
||||
$self->plugin( 'MeshMage::Web::Plugin::Helpers' );
|
||||
|
||||
# Router
|
||||
# Standard router.
|
||||
my $r = $self->routes;
|
||||
|
||||
# Handle user login & first account creation.
|
||||
# Routes:
|
||||
# /login GET view_login, POST post_login
|
||||
# /first GET view_first, POST post_first
|
||||
# /logout GET logout,
|
||||
$self->plugin( 'MeshMage::Web::Plugin::UserManagement' => { } );
|
||||
|
||||
# Ensure that only authenticated users can access routes under
|
||||
# $auth.
|
||||
|
||||
# Create a router chain that ensures the request is from an authenticated
|
||||
# user.
|
||||
my $auth = $r->under( '/' => sub ($c) {
|
||||
|
||||
# Login via session cookie.
|
||||
|
@ -65,14 +58,22 @@ sub startup ($self) {
|
|||
$c->stash->{person} = $person;
|
||||
return 1;
|
||||
}
|
||||
$c->redirect_to( $c->url_for( 'view_login' ) );
|
||||
$c->redirect_to( $c->url_for( 'auth_login' ) );
|
||||
return undef;
|
||||
}
|
||||
|
||||
$c->redirect_to( $c->url_for( 'view_login' ) );
|
||||
$c->redirect_to( $c->url_for( 'auth_login' ) );
|
||||
return undef;
|
||||
});
|
||||
|
||||
|
||||
# Controllers to handle initial user creation, login and logout.
|
||||
$r->get ('/auth/init' )->to('Auth#init' )->name('auth_init' );
|
||||
$r->post('/auth/init' )->to('Auth#create_init' )->name('create_auth_init' );
|
||||
$r->get ('/auth/login' )->to('Auth#login' )->name('auth_login' );
|
||||
$r->post('/auth/login' )->to('Auth#create_login')->name('create_auth_login');
|
||||
$r->get ('/auth/logout')->to('Auth#logout' )->name('logout' );
|
||||
|
||||
# The /minion stuff is handled here because we needed to place it under $auth.
|
||||
$self->plugin( 'Minion::Admin' => { route => $auth->under( '/minion' ) } );
|
||||
|
||||
# Send requests for / to the dashboard.
|
||||
|
|
76
Web/lib/MeshMage/Web/Controller/Auth.pm
Normal file
76
Web/lib/MeshMage/Web/Controller/Auth.pm
Normal file
|
@ -0,0 +1,76 @@
|
|||
package MeshMage::Web::Controller::Auth;
|
||||
use Mojo::Base 'Mojolicious::Controller', -signatures;
|
||||
use Try::Tiny;
|
||||
|
||||
sub init ($c) {
|
||||
my $user_count = $c->db->resultset('Person')->count;
|
||||
if ( $user_count >= 1 ) {
|
||||
$c->redirect_to( $c->url_for( 'auth_login' ) );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
sub create_init ($c) {
|
||||
# This code should only run when there are no user accounts, if
|
||||
# a user account exists, redirect the user to the login panel.
|
||||
my $user_count = $c->db->resultset('Person')->count;
|
||||
if ( $user_count >= 1 ) {
|
||||
$c->redirect_to( $c->url_for( 'view_login' ) );
|
||||
return;
|
||||
}
|
||||
|
||||
my $person = try {
|
||||
$c->db->storage->schema->txn_do( sub {
|
||||
my $person = $c->db->resultset('Person')->create({
|
||||
email => $c->param('email'),
|
||||
name => $c->param('name'),
|
||||
});
|
||||
$person->new_related('auth_password', {})->set_password($c->param('password'));
|
||||
return $person;
|
||||
});
|
||||
} catch {
|
||||
push @{$c->stash->{errors}}, "Account could not be created: $_";
|
||||
return;
|
||||
};
|
||||
|
||||
$c->session->{uid} = $person->id;
|
||||
|
||||
$c->redirect_to( $c->url_for( 'dashboard' ) );
|
||||
}
|
||||
|
||||
sub login ($c) {
|
||||
# If we have no user accounts, redirect the user to the
|
||||
# initial create user page.
|
||||
my $user_count = $c->db->resultset('Person')->count;
|
||||
if ( $user_count == 0 ) {
|
||||
$c->redirect_to( $c->url_for( 'auth_init' ) );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
sub create_login ($c) {
|
||||
my $email = $c->stash->{form_email} = $c->param('email');
|
||||
my $password = $c->stash->{form_password} = $c->param('password');
|
||||
|
||||
my $person = $c->db->resultset('Person')->find( { email => $email } )
|
||||
or push @{$c->stash->{errors}}, "Invalid email address or password.";
|
||||
|
||||
return if $c->stash->{errors};
|
||||
|
||||
$person->auth_password->check_password( $password )
|
||||
or push @{$c->stash->{errors}}, "Invalid email address or password.";
|
||||
|
||||
return if $c->stash->{errors};
|
||||
|
||||
$c->session->{uid} = $person->id;
|
||||
|
||||
$c->redirect_to( $c->url_for( 'dashboard' ) );
|
||||
}
|
||||
|
||||
sub logout ($c) {
|
||||
undef $c->session->{uid};
|
||||
$c->redirect_to( $c->url_for( 'auth_login' ) );
|
||||
|
||||
}
|
||||
|
||||
1;
|
|
@ -1,94 +0,0 @@
|
|||
package MeshMage::Web::Plugin::UserManagement;
|
||||
use Mojo::Base 'Mojolicious::Plugin', -signatures;
|
||||
use Try::Tiny;
|
||||
|
||||
sub register ( $self, $app, $config ) {
|
||||
|
||||
my $r = $config->{route} || $app->routes;
|
||||
|
||||
# User Management
|
||||
$r->get ( '/first' )->to( cb => sub ($c) {
|
||||
my $user_count = $c->db->resultset('Person')->count;
|
||||
if ( $user_count >= 1 ) {
|
||||
$c->redirect_to( $c->url_for( 'view_login' ) );
|
||||
return;
|
||||
}
|
||||
$c->render( template => 'first', format => 'html', handler => 'tx' );
|
||||
})->name( 'view_first' );
|
||||
|
||||
$r->post( '/first' )->to( cb => sub ($c) {
|
||||
|
||||
# This code should only run when there are no user accounts, if
|
||||
# a user account exists, redirect the user to the login panel.
|
||||
my $user_count = $c->db->resultset('Person')->count;
|
||||
if ( $user_count >= 1 ) {
|
||||
$c->redirect_to( $c->url_for( 'view_login' ) );
|
||||
return;
|
||||
}
|
||||
|
||||
my $person = try {
|
||||
$c->db->storage->schema->txn_do( sub {
|
||||
my $person = $c->db->resultset('Person')->create({
|
||||
email => $c->param('email'),
|
||||
name => $c->param('name'),
|
||||
});
|
||||
$person->new_related('auth_password', {})->set_password($c->param('password'));
|
||||
return $person;
|
||||
});
|
||||
} catch {
|
||||
push @{$c->stash->{errors}}, "Account could not be created: $_";
|
||||
$c->render( template => 'first', format => 'html', handler => 'tx' );
|
||||
return;
|
||||
};
|
||||
|
||||
$c->session->{uid} = $person->id;
|
||||
|
||||
$c->redirect_to( $c->url_for( 'dashboard' ) );
|
||||
|
||||
})->name( 'post_first' );
|
||||
|
||||
$r->get ( '/login' )->to( cb => sub ($c) {
|
||||
|
||||
# If we have no user accounts, redirect the user to the
|
||||
# initial create user page.
|
||||
my $user_count = $c->db->resultset('Person')->count;
|
||||
if ( $user_count == 0 ) {
|
||||
$c->redirect_to( $c->url_for( 'view_first' ) );
|
||||
return;
|
||||
}
|
||||
|
||||
$c->render( template => 'login', format => 'html', handler => 'tx' );
|
||||
})->name( 'view_login' );
|
||||
|
||||
$r->post( '/login' )->to( cb => sub ($c) {
|
||||
my $email = $c->stash->{form_email} = $c->param('email');
|
||||
my $password = $c->stash->{form_password} = $c->param('password');
|
||||
|
||||
my $person = $c->db->resultset('Person')->find( { email => $email } )
|
||||
or push @{$c->stash->{errors}}, "Invalid email address or password.";
|
||||
|
||||
if ( $c->stash->{errors} or not $person ) {
|
||||
$c->render( template => 'login', format => 'html', handler => 'tx' );
|
||||
return;
|
||||
}
|
||||
|
||||
$person->auth_password->check_password( $password )
|
||||
or push @{$c->stash->{errors}}, "Invalid email address or password.";
|
||||
|
||||
if ( $c->stash->{errors} ) {
|
||||
$c->render( template => 'login', format => 'html', handler => 'tx' );
|
||||
return;
|
||||
}
|
||||
|
||||
$c->session->{uid} = $person->id;
|
||||
|
||||
$c->redirect_to( $c->url_for( 'dashboard' ) );
|
||||
})->name( 'post_login' );
|
||||
|
||||
$r->get ( '/logout' )->to( cb => sub ($c) {
|
||||
undef $c->session->{uid};
|
||||
$c->redirect_to( $c->url_for( 'view_login' ) );
|
||||
})->name( 'logout' );
|
||||
}
|
||||
|
||||
1;
|
|
@ -16,7 +16,7 @@
|
|||
</div>
|
||||
%% }
|
||||
|
||||
<form style="margin-top: 1.5em" method="POST" action="/first">
|
||||
<form style="margin-top: 1.5em" method="POST" action="[% $c.url_for( 'auth_init' ) %]">
|
||||
|
||||
%% include '_base/form/input.tx' { type => 'text', name => 'name',
|
||||
%% title => 'Your name',
|
|
@ -16,7 +16,7 @@
|
|||
</div>
|
||||
%% }
|
||||
|
||||
<form style="margin-top: 1.5em" method="POST" action="/login">
|
||||
<form style="margin-top: 1.5em" method="POST" action="[% $c.url_for( 'auth_login' ) %]">
|
||||
|
||||
%% include '_base/form/input.tx' { type => 'email', name => 'email',
|
||||
%% title => 'Email Address',
|
|
@ -1,286 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="Mark Otto, Jacob Thornton, and Bootstrap contributors">
|
||||
<meta name="generator" content="Hugo 0.83.1">
|
||||
<title>Dashboard Template · Bootstrap v5.0</title>
|
||||
|
||||
<!-- Bootstrap core CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous">
|
||||
|
||||
<!-- Favicons -->
|
||||
<link rel="apple-touch-icon" href="/docs/5.0/assets/img/favicons/apple-touch-icon.png" sizes="180x180">
|
||||
<link rel="icon" href="/docs/5.0/assets/img/favicons/favicon-32x32.png" sizes="32x32" type="image/png">
|
||||
<link rel="icon" href="/docs/5.0/assets/img/favicons/favicon-16x16.png" sizes="16x16" type="image/png">
|
||||
<link rel="manifest" href="/docs/5.0/assets/img/favicons/manifest.json">
|
||||
<link rel="mask-icon" href="/docs/5.0/assets/img/favicons/safari-pinned-tab.svg" color="#7952b3">
|
||||
<link rel="icon" href="/docs/5.0/assets/img/favicons/favicon.ico">
|
||||
<meta name="theme-color" content="#7952b3">
|
||||
|
||||
|
||||
<!-- Custom styles for this template -->
|
||||
<link href="/assets/css/dashboard.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow">
|
||||
<a class="navbar-brand col-md-3 col-lg-2 me-0 px-3" href="#">Company name</a>
|
||||
<button class="navbar-toggler position-absolute d-md-none collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<input class="form-control form-control-dark w-100" type="text" placeholder="Search" aria-label="Search">
|
||||
<ul class="navbar-nav px-3">
|
||||
<li class="nav-item text-nowrap">
|
||||
<a class="nav-link" href="#">Sign out</a>
|
||||
</li>
|
||||
</ul>
|
||||
</header>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<nav id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse">
|
||||
<div class="position-sticky pt-3">
|
||||
<ul class="nav flex-column">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" aria-current="page" href="#">
|
||||
<span data-feather="home"></span>
|
||||
Dashboard
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">
|
||||
<span data-feather="file"></span>
|
||||
Orders
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">
|
||||
<span data-feather="shopping-cart"></span>
|
||||
Products
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">
|
||||
<span data-feather="users"></span>
|
||||
Customers
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">
|
||||
<span data-feather="bar-chart-2"></span>
|
||||
Reports
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">
|
||||
<span data-feather="layers"></span>
|
||||
Integrations
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
|
||||
<span>Saved reports</span>
|
||||
<a class="link-secondary" href="#" aria-label="Add a new report">
|
||||
<span data-feather="plus-circle"></span>
|
||||
</a>
|
||||
</h6>
|
||||
<ul class="nav flex-column mb-2">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">
|
||||
<span data-feather="file-text"></span>
|
||||
Current month
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">
|
||||
<span data-feather="file-text"></span>
|
||||
Last quarter
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">
|
||||
<span data-feather="file-text"></span>
|
||||
Social engagement
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">
|
||||
<span data-feather="file-text"></span>
|
||||
Year-end sale
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4">
|
||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
||||
<h1 class="h2">Dashboard</h1>
|
||||
<div class="btn-toolbar mb-2 mb-md-0">
|
||||
<div class="btn-group me-2">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary">Share</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary">Export</button>
|
||||
</div>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary dropdown-toggle">
|
||||
<span data-feather="calendar"></span>
|
||||
This week
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<canvas class="my-4 w-100" id="myChart" width="900" height="380"></canvas>
|
||||
|
||||
<h2>Section title</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Header</th>
|
||||
<th>Header</th>
|
||||
<th>Header</th>
|
||||
<th>Header</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>1,001</td>
|
||||
<td>random</td>
|
||||
<td>data</td>
|
||||
<td>placeholder</td>
|
||||
<td>text</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,002</td>
|
||||
<td>placeholder</td>
|
||||
<td>irrelevant</td>
|
||||
<td>visual</td>
|
||||
<td>layout</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,003</td>
|
||||
<td>data</td>
|
||||
<td>rich</td>
|
||||
<td>dashboard</td>
|
||||
<td>tabular</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,003</td>
|
||||
<td>information</td>
|
||||
<td>placeholder</td>
|
||||
<td>illustrative</td>
|
||||
<td>data</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,004</td>
|
||||
<td>text</td>
|
||||
<td>random</td>
|
||||
<td>layout</td>
|
||||
<td>dashboard</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,005</td>
|
||||
<td>dashboard</td>
|
||||
<td>irrelevant</td>
|
||||
<td>text</td>
|
||||
<td>placeholder</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,006</td>
|
||||
<td>dashboard</td>
|
||||
<td>illustrative</td>
|
||||
<td>rich</td>
|
||||
<td>data</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,007</td>
|
||||
<td>placeholder</td>
|
||||
<td>tabular</td>
|
||||
<td>information</td>
|
||||
<td>irrelevant</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,008</td>
|
||||
<td>random</td>
|
||||
<td>data</td>
|
||||
<td>placeholder</td>
|
||||
<td>text</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,009</td>
|
||||
<td>placeholder</td>
|
||||
<td>irrelevant</td>
|
||||
<td>visual</td>
|
||||
<td>layout</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,010</td>
|
||||
<td>data</td>
|
||||
<td>rich</td>
|
||||
<td>dashboard</td>
|
||||
<td>tabular</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,011</td>
|
||||
<td>information</td>
|
||||
<td>placeholder</td>
|
||||
<td>illustrative</td>
|
||||
<td>data</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,012</td>
|
||||
<td>text</td>
|
||||
<td>placeholder</td>
|
||||
<td>layout</td>
|
||||
<td>dashboard</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,013</td>
|
||||
<td>dashboard</td>
|
||||
<td>irrelevant</td>
|
||||
<td>text</td>
|
||||
<td>visual</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,014</td>
|
||||
<td>dashboard</td>
|
||||
<td>illustrative</td>
|
||||
<td>rich</td>
|
||||
<td>data</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,015</td>
|
||||
<td>random</td>
|
||||
<td>tabular</td>
|
||||
<td>information</td>
|
||||
<td>text</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-gtEjrD/SeCtmISkJkNUaaKMoLD0//ElJ19smozuHV6z3Iehds+3Ulb9Bn9Plx0x4"
|
||||
crossorigin="anonymous">
|
||||
</script>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/feather-icons@4.28.0/dist/feather.min.js"
|
||||
integrity="sha384-uO3SXW5IuS1ZpFPKugNNWqTZRRglnUJK6UAZ/gxOX80nxEkN9NcGZTftn6RzhGWE"
|
||||
crossorigin="anonymous">
|
||||
</script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.4/dist/Chart.min.js"
|
||||
integrity="sha384-zNy6FEbO50N+Cg5wap8IKA4M/ZnLJgzc6w2NqACZaK0u0FXfOWRRJOnQtpZun8ha"
|
||||
crossorigin="anonymous">
|
||||
</script>
|
||||
<script src="/assets/js/dashboard.js"></script>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in a new issue