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:
parent
99f5ae0fcc
commit
301bc59a6d
|
@ -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
|
||||
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
26
templates/Account/account.html.twig
Normal file
26
templates/Account/account.html.twig
Normal 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 %}
|
34
templates/Account/api.html.twig
Normal file
34
templates/Account/api.html.twig
Normal 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>
|
|
@ -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>
|
||||
|
|
24
templates/Account/profile.html.twig
Normal file
24
templates/Account/profile.html.twig
Normal 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>
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue