Skip to content

Commit

Permalink
Lesson 2.0 - User Role Management
Browse files Browse the repository at this point in the history
  • Loading branch information
tomgobich committed Nov 14, 2022
1 parent 375ab4a commit e6ac2e4
Show file tree
Hide file tree
Showing 9 changed files with 249 additions and 113 deletions.
44 changes: 44 additions & 0 deletions app/Controllers/Http/UsersController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import Role from 'App/Models/Role'
import User from 'App/Models/User'
import { schema, rules } from '@ioc:Adonis/Core/Validator'
import Roles from 'App/Enums/Roles'

export default class UsersController {
public async manage({ view }: HttpContextContract) {
const users = await User.query()
.orderBy('email')

const roles = await Role.query()
.orderBy('name')

return view.render('users/manage', { users, roles })
}

public async role({ request, response, params, auth }: HttpContextContract) {
const roleSchema = schema.create({
roleId: schema.number([rules.exists({ table: 'roles', column: 'id' })])
})

const data = await request.validate({ schema: roleSchema })
const user = await User.findOrFail(params.id)
const isAuthUser = user.id === auth.user?.id

await user.merge(data).save()

return isAuthUser && user.roleId !== Roles.ADMIN
? response.redirect().toPath('/')
: response.redirect().back()
}

public async destroy({ response, params, auth }: HttpContextContract) {
const user = await User.findOrFail(params.id)
const isAuthUser = user.id === auth.user?.id

await user.delete()

return isAuthUser
? response.redirect().toPath('/')
: response.redirect().back()
}
}
16 changes: 16 additions & 0 deletions app/Middleware/Role.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import Roles from 'App/Enums/Roles'

export default class Role {
// .middleware(['auth', 'role:admin'])
public async handle({ response, auth }: HttpContextContract, next: () => Promise<void>, guards: string[]) {
const roleIds = guards.map(guard => Roles[guard.toUpperCase()])

if (!roleIds.includes(auth.user?.roleId)) {
return response.unauthorized({ error: `This is restricted to ${guards.join(', ')} users` })
}

// code for middleware goes here. ABOVE THE NEXT CALL
await next()
}
}
3 changes: 3 additions & 0 deletions app/Models/Role.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ export default class Role extends BaseModel {
@column({ isPrimary: true })
public id: number

@column()
public name: string

@column.dateTime({ autoCreate: true })
public createdAt: DateTime

Expand Down
2 changes: 1 addition & 1 deletion config/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export const http: ServerConfig = {
| so on.
|
*/
allowMethodSpoofing: false,
allowMethodSpoofing: true,

/*
|--------------------------------------------------------------------------
Expand Down
90 changes: 90 additions & 0 deletions resources/views/layouts/app.edge
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AdonisJS - A fully featured web framework for Node.js</title>
<link href="https://fonts.googleapis.com/css?family=Poppins:400,500&display=swap" rel="stylesheet">
<style>
html, body {
background-color: #F7F8FA;
font-family: 'Poppins', sans-serif;
height: 100vh;
color: #46444c;
position: relative;
}
body:before {
content: '';
background: #5A45FF;
top: 0;
left: 0;
right: 0;
height: 6px;
position: absolute;
}
* {
margin: 0;
padding: 0;
}
a {
color: #5A45FF;
text-decoration: none;
}
main {
max-width: 620px;
margin: auto;
height: 100vh;
padding: 0 30px;
align-items: center;
display: flex;
justify-content: center;
}
.title {
font-size: 50px;
line-height: 50px;
margin-bottom: 10px;
color: #17161A;
}
.subtitle {
font-size: 26px;
margin-bottom: 40px;
}
p {
margin-bottom: 20px;
}
main ul {
list-style: none;
}
main li {
margin-bottom: 5px;
position: relative;
padding-left: 25px;
}
main li:before {
content: '';
position: absolute;
left: 0;
}
main code {
font-size: 16px;
background: #e6e2ff;
}
</style>
</head>
<body>
<main>
@!section('content')
</main>
</body>
</html>
51 changes: 51 additions & 0 deletions resources/views/users/manage.edge
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
@layout('layouts/app')

@section('content')

<div>
<h1>User Management</h1>

<table>
<thead>
<tr>
<th>Email</th>
<th>Role</th>
<th>Joined</th>
<th></th>
</tr>
</thead>
<tbody>
@each (user in users)
<tr>
<td>
{{ user.email }}
@if (user.id === auth.user?.id)
(you)
@endif
</td>
<th>
<form action="{{ route('users.role', { id: user.id }, { qs: { _method: 'PATCH' }}) }}" method="POST">
<select name="roleId" onchange="this.parentElement.submit()">
@each (role in roles)
<option value="{{ role.id }}" {{ role.id === user.roleId ? 'selected' : '' }}>
{{ role.name }}
</option>
@endeach
</select>
</form>
</th>
<th>{{ user.createdAt.toLocaleString() }}</th>
<th>
<form action="{{ route('users.destroy', { id: user.id }, { qs: { _method: 'DELETE' }}) }}" method="POST">
<button type="button" onclick="confirm('Are you sure you want to delete this user?') && this.parentElement.submit()">
Delete
</button>
</form>
</th>
</tr>
@endeach
</tbody>
</table>
</div>

@endsection
145 changes: 34 additions & 111 deletions resources/views/welcome.edge
Original file line number Diff line number Diff line change
@@ -1,116 +1,39 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AdonisJS - A fully featured web framework for Node.js</title>
<link href="https://fonts.googleapis.com/css?family=Poppins:400,500&display=swap" rel="stylesheet">
<style>
html, body {
background-color: #F7F8FA;
font-family: 'Poppins', sans-serif;
height: 100vh;
color: #46444c;
position: relative;
}
body:before {
content: '';
background: #5A45FF;
top: 0;
left: 0;
right: 0;
height: 6px;
position: absolute;
}
* {
margin: 0;
padding: 0;
}
a {
color: #5A45FF;
text-decoration: none;
}
main {
max-width: 620px;
margin: auto;
height: 100vh;
padding: 0 30px;
align-items: center;
display: flex;
justify-content: center;
}
.title {
font-size: 50px;
line-height: 50px;
margin-bottom: 10px;
color: #17161A;
}
.subtitle {
font-size: 26px;
margin-bottom: 40px;
}
p {
margin-bottom: 20px;
}
main ul {
list-style: none;
}
main li {
margin-bottom: 5px;
position: relative;
padding-left: 25px;
}
main li:before {
content: '';
position: absolute;
left: 0;
}
main code {
font-size: 16px;
background: #e6e2ff;
}
</style>
</head>
<body>

<main>
@if (!auth.user)
<form method="POST" action="{{ route('auth.register') }}">
<h2>Register Form</h2>
<input type="email" name="email" placeholder="email" />
<input type="password" name="password" placeholder="password" />
<button type="submit">
Register
</button>
</form>

<form method="POST" action="{{ route('auth.login') }}">
<h2>Login Form</h2>
<input type="email" name="email" placeholder="email" />
<input type="password" name="password" placeholder="password" />
<button type="submit">
Login
</button>
</form>
@else
@layout('layouts/app')

@section('content')

@if (!auth.user)
<form method="POST" action="{{ route('auth.register') }}">
<h2>Register Form</h2>
<input type="email" name="email" placeholder="email" />
<input type="password" name="password" placeholder="password" />
<button type="submit">
Register
</button>
</form>

<form method="POST" action="{{ route('auth.login') }}">
<h2>Login Form</h2>
<input type="email" name="email" placeholder="email" />
<input type="password" name="password" placeholder="password" />
<button type="submit">
Login
</button>
</form>
@else
<div>
@if (auth.user.isAdmin)
Admin User {{ auth.user.email }}
<div>Admin User {{ auth.user.email }}</div>
<div>
<a href="{{ route('users.manage') }}">
Manage Users
</a>
</div>
@else
Regular User {{ auth.user.email }}
@endif
<a href="{{ route('auth.logout') }}">Logout </a>
@endif
</main>
</body>
</html>
</div>
@endif

@endsection
3 changes: 2 additions & 1 deletion start/kernel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,6 @@ Server.middleware.register([
|
*/
Server.middleware.registerNamed({
auth: () => import('App/Middleware/Auth')
auth: () => import('App/Middleware/Auth'),
role: () => import('App/Middleware/Role')
})
8 changes: 8 additions & 0 deletions start/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,11 @@ Route.get('/', async ({ view }) => {
Route.post('/auth/register', 'AuthController.register').as('auth.register')
Route.post('/auth/login', 'AuthController.login').as('auth.login')
Route.get('/auth/logout', 'AuthController.logout').as('auth.logout')

Route.group(() => {

Route.get('/manage', 'UsersController.manage').as('manage')
Route.patch('/:id/role', 'UsersController.role').as('role')
Route.delete('/:id', 'UsersController.destroy').as('destroy')

}).prefix('users').as('users').middleware(['auth', 'role:admin'])

0 comments on commit e6ac2e4

Please sign in to comment.