Implementing new account page. Adding ability to create API tokens on the account page. Showing RSS feed link on the account page.

This commit is contained in:
Alexey Skobkin 2020-07-02 02:12:49 +03:00
parent 99f5ae0fcc
commit 301bc59a6d
7 changed files with 179 additions and 37 deletions

View File

@ -47,9 +47,21 @@ user_auth_login:
user_auth_logout:
path: /auth/logout
user_account_invites:
path: /account/invites
controller: App\Controller\AccountController::invites
user_account:
path: /account
controller: App\Controller\AccountController::account
requirements:
method: GET
user_account_token_create:
path: /profile/api/token/create
controller: App\Controller\AccountController::addApiToken
requirements:
method: GET
user_account_token_revoke:
path: /profile/api/token/revoke/{key}
controller: App\Controller\AccountController::revokeApiToken
requirements:
method: GET

View File

@ -2,22 +2,60 @@
namespace App\Controller;
use App\Entity\ApiToken;
use App\Entity\User;
use App\Repository\ApiTokenRepository;
use App\Repository\InviteRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
class AccountController extends AbstractController
{
public function invites(InviteRepository $inviteRepo): Response
public function account(InviteRepository $inviteRepo, ApiTokenRepository $apiTokenRepo): Response
{
/** @var User $user */
if (null === $user = $this->getUser()) {
throw $this->createAccessDeniedException('User not found exception');
}
return $this->render('Account/invites.html.twig', [
return $this->render(
'Account/account.html.twig', [
'invites' => $inviteRepo->findInvitesByUser($user),
'tokens' => $apiTokenRepo->findBy(['user' => $user->getId()]),
'user' => $user,
]);
}
}
public function addApiToken(EntityManagerInterface $em): Response
{
/** @var User|null $user */
if (null === $user = $this->getUser()) {
throw $this->createAccessDeniedException();
}
$token = new ApiToken($user);
$em->persist($token);
$em->flush();
$this->addFlash('success', 'API token created.');
return $this->redirectToRoute('user_account');
}
public function revokeApiToken(string $key, ApiTokenRepository $repo, EntityManagerInterface $em): Response
{
$token = $repo->findOneBy(['key' => $key]);
if (null === $token || $token->getUser() !== $this->getUser()) {
throw $this->createNotFoundException('Token not found');
}
$em->remove($token);
$em->flush();
$this->addFlash('success', 'API token removed.');
return $this->redirectToRoute('user_account');
}
}

View File

@ -0,0 +1,26 @@
{% extends 'base.html.twig' %}
{% block content %}
<ul class="nav nav-tabs" id="myTab" role="tablist">
<li class="nav-item">
<a class="nav-link active" id="home-tab" data-toggle="tab" href="#profile" role="tab" aria-controls="home" aria-selected="true">Profile</a>
</li>
<li class="nav-item">
<a class="nav-link" id="profile-tab" data-toggle="tab" href="#invites" role="tab" aria-controls="profile" aria-selected="false">Invites</a>
</li>
<li class="nav-item">
<a class="nav-link" id="contact-tab" data-toggle="tab" href="#api" role="tab" aria-controls="contact" aria-selected="false">RSS / API</a>
</li>
</ul>
<div class="tab-content" id="myTabContent">
<div class="tab-pane fade show active" id="profile" role="tabpanel" aria-labelledby="home-tab">
{% include 'Account/profile.html.twig' with {'user': user} %}
</div>
<div class="tab-pane fade" id="invites" role="tabpanel" aria-labelledby="profile-tab">
{% include 'Account/invites.html.twig' with {'invites': invites} %}
</div>
<div class="tab-pane fade" id="api" role="tabpanel" aria-labelledby="contact-tab">
{% include 'Account/api.html.twig' with {'tokens': tokens} %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,34 @@
<div class="row mb-3 mt-3">
<a role="button" class="btn btn-primary" href="{{ path('user_account_token_create') }}">Create token</a>
</div>
<table class="table">
<thead>
<tr>
<th scope="col">Token</th>
<th scope="col">RSS link</th>
<th scope="col">Created</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
{% for token in tokens %}
<tr>
<th scope="row">
<code>{{ token.key }}</code>
</th>
<td>
{# We can't use constant() in the hash so it's necessay to hard-code query parameter here #}
<input class="form-control" type="text" value="{{ url('api_v1_rss_last') }}?{{ {'api-key': token.key} | url_encode }}" readonly onclick="this.select();">
</td>
<td>
{{ token.createdAt | date('Y-m-d H:i:s') }}
</td>
<td>
<a role="button" class="btn btn-danger" href="{{ path('user_account_token_revoke', {'key': token.key}) }}" onclick="return confirm('Are you sure?')">Revoke</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>

View File

@ -1,30 +1,28 @@
{% extends 'base.html.twig' %}
<div class="row mb-3 mt-3">
<button type="button" class="btn btn-primary disabled" title="Not implemented">Request more</button>
</div>
{% block content %}
<h1>Your invites</h1>
<table class="table table-striped">
<thead>
<tr>
<th>#</th>
<th><i class="fas fa-external-link-alt"></i></th>
</tr>
</thead>
<tbody>
{# @var invite \App\Entity\Invite #}
{% for invite in invites %}
<tr>
<th scope="row">{{ loop.index }}</th>
<td>
{% if invite.usedBy %}
Used by <strong>{{ invite.usedBy.username }}</strong>.
{% else %}
{% set invite_url = url('user_register', { code: invite.code }) %}
<input class="form-control" type="url" value="{{ invite_url }}" readonly="readonly" onclick="this.select()">
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
<table class="table table-striped">
<thead>
<tr>
<th>#</th>
<th><i class="fas fa-external-link-alt"></i></th>
</tr>
</thead>
<tbody>
{# @var invite \App\Entity\Invite #}
{% for invite in invites %}
<tr>
<th scope="row">{{ loop.index }}</th>
<td>
{% if invite.usedBy %}
Used by <strong>{{ invite.usedBy.username }}</strong>.
{% else %}
{% set invite_url = url('user_register', { code: invite.code }) %}
<input class="form-control" type="url" value="{{ invite_url }}" readonly="readonly" onclick="this.select()">
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>

View File

@ -0,0 +1,24 @@
<table class="table">
<thead>
<tr>
<th scope="col">Field</th>
<th scope="col">Value</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Username</th>
<td>{{ user.username }}</td>
<td></td>
</tr>
<tr>
<th scope="row">Email</th>
<td>{{ user.email }}</td>
<td>
<button type="button" class="btn disabled" title="Not implemented">Change</button>
</td>
</tr>
</tbody>
</table>

View File

@ -36,8 +36,8 @@
<i class="fas fa-user"></i> {{ app.user.username }}
</a>
<div class="dropdown-menu">
<a href="{{ path('user_account_invites') }}" class="dropdown-item"><i class="fas fa-user-plus"></i> Invites</a>
<a href="{{ path('user_auth_logout') }}" class="dropdown-item"><i class="fas fa-sign-out-alt"></i> Logout</a>
<a href="{{ path('user_account') }}" class="dropdown-item"><i class="fas fa-wrench"></i> Profile</a>
<a href="{{ logout_path('main') }}" class="dropdown-item"><i class="fas fa-sign-out-alt"></i> Logout</a>
</div>
</li>
</ul>
@ -62,6 +62,16 @@
</div>
{% endif %}
<div id="alerts">
{% for type, messages in app.flashes %}
{% for message in messages %}
<div class="alert alert-{{ type }}" role="alert">
{{ message }}
</div>
{% endfor %}
{% endfor %}
</div>
<div>
{% block content %}{% endblock %}
</div>