feat: registration and password reset

This commit is contained in:
Björn Fromme
2024-12-03 16:08:06 +01:00
parent 8b796a8543
commit 3e59031ba9
10 changed files with 285 additions and 22 deletions
+45 -20
View File
@@ -13,6 +13,7 @@ use App\BusProNet\Model\File;
use App\BusProNet\Model\Notification;
use App\BusProNet\Model\PersonalData;
use App\Form\Model\BookingData;
use App\Form\Model\RegistrationData;
use Psr\Log\LoggerInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Serializer\Encoder\XmlEncoder;
@@ -33,10 +34,11 @@ class ApiClient
public function __construct(
private readonly HttpClientInterface $httpClient,
private readonly SerializerInterface $serializer,
private readonly ResponseParser $responseParser,
private readonly LoggerInterface $logger,
array $options
) {
private readonly ResponseParser $responseParser,
private readonly LoggerInterface $logger,
array $options
)
{
$this->config = $this->resolveOptions($options);
}
@@ -57,6 +59,45 @@ class ApiClient
return $this->sendRequest(static::TYPE_CUSTOMER_DATA, $data);
}
/**
* @throws ApiClientException
*/
public function register(RegistrationData $registrationData): Notification
{
$data = [
'user' => $this->config['bpn_username'],
'key' => $this->createKey($this->config['bpn_username'], $this->config['bpn_password'], static::TYPE_CUSTOMER_DATA),
'satz' => ['@typ' => static::TYPE_CUSTOMER_DATA],
'art' => 'Adresse_Neu',
'adressdaten' => [
'geschlecht' => $registrationData->gender,
'vorname' => $registrationData->firstName,
'name' => $registrationData->name,
'kommunikation' => [
'email' => $registrationData->email,
],
],
];
return $this->sendRequest(static::TYPE_CUSTOMER_DATA, $data);
}
/**
* @throws ApiClientException
*/
public function resetPassword(string $email): Notification
{
$data = [
'user' => $this->config['bpn_username'],
'key' => $this->createKey($this->config['bpn_username'], $this->config['bpn_password'], static::TYPE_CUSTOMER_DATA),
'satz' => ['@typ' => static::TYPE_CUSTOMER_DATA],
'art' => 'Passwort_Anfrage',
'email' => $email,
];
return $this->sendRequest(static::TYPE_CUSTOMER_DATA, $data);
}
/**
* @throws ApiClientException
*/
@@ -180,22 +221,6 @@ class ApiClient
return $this->sendRequest(static::TYPE_AVAILABILITY, $data);
}
/**
* @throws ApiClientException
*/
public function resetPassword(string $email): Notification
{
$data = [
'user' => $this->config['bpn_username'],
'key' => $this->createKey($this->config['bpn_username'], $this->config['bpn_password'], static::TYPE_CUSTOMER_DATA),
'satz' => ['@typ' => static::TYPE_CUSTOMER_DATA],
'art' => 'Passwort_Anfrage',
'email' => $email,
];
return $this->sendRequest(static::TYPE_CUSTOMER_DATA, $data);
}
/**
* @throws ApiClientException
*/
+50
View File
@@ -0,0 +1,50 @@
<?php
namespace App\Controller;
use App\BusProNet\ApiClient;
use App\BusProNet\Exception\ApiClientException;
use App\Form\Model\RegistrationData;
use App\Form\RegistrationType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
class RegistrationController extends AbstractController
{
public function __construct(private readonly ApiClient $apiClient)
{
}
#[Route('/registration', name: 'app_registration')]
#[IsGranted('PUBLIC_ACCESS')]
public function index(Request $request): Response
{
$registrationData = new RegistrationData();
$form = $this->createForm(RegistrationType::class, $registrationData,[
'action' => $this->generateUrl('app_registration'),
]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
try {
$response = $this->apiClient->register($registrationData);
if ($response->isSuccessful()) {
$this->addFlash('success', 'Du erhältst in Kürze eine E-Mail mit einem Link zum (Zurück)setzen deines Passworts.');
} else {
$this->addFlash('error', 'Möglicherweise bist du bereits registriert. Bitte setze dein Passwort zurück.');
}
} catch (ApiClientException $e) {
$this->addFlash('error', $e->getMessage());
}
return $this->redirectToRoute('app_login');
}
return $this->render('registration/index.html.twig', [
'form' => $form->createView(),
]);
}
}
@@ -0,0 +1,61 @@
<?php
namespace App\Controller;
use App\BusProNet\ApiClient;
use App\BusProNet\Exception\ApiClientException;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Component\Validator\Constraints\Email;
use Symfony\Component\Validator\Constraints\NotBlank;
class ResetPasswordController extends AbstractController
{
public function __construct(private readonly ApiClient $apiClient)
{
}
#[Route('/reset-password', name: 'app_reset_password')]
#[IsGranted('PUBLIC_ACCESS')]
public function index(Request $request): Response
{
$form = $this
->createFormBuilder(null, [
'action' => $this->generateUrl('app_reset_password'),
])
->add('email', EmailType::class, [
'label' => 'E-Mail',
'constraints' => [
new NotBlank([
'message' => 'Bitte angeben',
]),
new Email([
'mode' => 'strict',
'message' => 'Bitte eine gültige E-Mail Adresse angeben',
])
],
])
->getForm()
;
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
try {
$this->apiClient->resetPassword($form->get('email')->getData());
$this->addFlash('success', 'Du erhältst in Kürze eine E-Mail mit einem Link zum (Zurück)setzen deines Passworts.');
} catch (ApiClientException $e) {
$this->addFlash('error', $e->getMessage());
}
return $this->redirectToRoute('app_login');
}
return $this->render('reset_password/index.html.twig', [
'form' => $form->createView(),
]);
}
}
+1
View File
@@ -37,6 +37,7 @@ class ParticipantData
public ?\DateTimeImmutable $dateOfBirth = null;
#[Assert\NotBlank(message: 'Bitte angeben')]
#[Assert\Email(message: 'Bitte eine gültige E-Mail Adresse angeben', mode: 'strict')]
public ?string $email = null;
#[Assert\NotBlank(message: 'Bitte angeben')]
+22
View File
@@ -0,0 +1,22 @@
<?php
namespace App\Form\Model;
use Symfony\Component\Validator\Constraints as Assert;
class RegistrationData
{
#[Assert\NotBlank(message: 'Bitte angeben')]
public ?string $firstName = null;
#[Assert\NotBlank(message: 'Bitte angeben')]
public ?string $name = null;
#[Assert\NotBlank(message: 'Bitte angeben')]
#[Assert\Choice(choices: ['M', 'W', 'd'])]
public ?string $gender = null;
#[Assert\NotBlank(message: 'Bitte angeben')]
#[Assert\Email(message: 'Bitte eine gültige E-Mail Adresse angeben', mode: 'strict')]
public ?string $email = null;
}
+45
View File
@@ -0,0 +1,45 @@
<?php
namespace App\Form;
use App\Form\Model\RegistrationData;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class RegistrationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('gender', ChoiceType::class, [
'label' => 'Gender',
'choices' => [
'männlich' => 'M',
'weiblich' => 'W',
'divers' => 'D',
],
])
->add('firstName', TextType::class, [
'label' => 'Name',
])
->add('name', TextType::class, [
'label' => 'Nachname',
])
->add('email', EmailType::class, [
'label' => 'E-Mail',
'property_path' => 'email',
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => RegistrationData::class,
]);
}
}
+3 -1
View File
@@ -3,7 +3,9 @@
hx-boost="true"
hx-indicator="#loading-indicator">
<div class="relative z-20 flex items-center justify-between pb-4">
<img class="h-8 w-auto" src="{{ asset('build/images/logo.svg') }}" alt="My E&amp;P Team">
<div class="text-3xl font-medium">
MyE&amp;P
</div>
{% if is_granted('ROLE_USER') %}
<div class="hidden lg:block">
{{ knp_menu_render(knp_menu_get('main')) }}
+26
View File
@@ -0,0 +1,26 @@
{% extends 'layout.html.twig' %}
{% block content %}
<div class="p-8 max-w-screen-sm border border-gray-300 rounded-md mx-auto">
<h2 class="text-2xl font-semibold pb-4">
Registrierung MyE&amp;P
</h2>
{{ form_start(form) }}
<div class="grid grid-cols-2 gap-x-8 gap-y-4 pb-4">
{{ form_row(form.firstName) }}
{{ form_row(form.name) }}
{{ form_row(form.gender) }}
{{ form_row(form.email) }}
</div>
<button type="submit" class="btn">
Absenden
</button>
<div class="pt-4">
<a href="{{ path('app_login') }}" class="underline">
zurück
</a>
</div>
{{ form_rest(form) }}
{{ form_end(form) }}
</div>
{% endblock %}
+23
View File
@@ -0,0 +1,23 @@
{% extends 'layout.html.twig' %}
{% block content %}
<div class="p-8 max-w-screen-sm border border-gray-300 rounded-md mx-auto">
<h2 class="text-2xl font-semibold pb-4">
Passwort zurücksetzen
</h2>
{{ form_start(form) }}
<div class="pb-4">
{{ form_row(form.email) }}
</div>
<button type="submit" class="btn">
Absenden
</button>
<div class="pt-4">
<a href="{{ path('app_login') }}" class="underline">
zurück
</a>
</div>
{{ form_rest(form) }}
{{ form_end(form) }}
</div>
{% endblock %}
+9 -1
View File
@@ -11,7 +11,7 @@
</div>
{% endif %}
<h2 class="text-2xl font-semibold pb-4">
Login MyE&amp;P
Login
</h2>
<form action="{{ path('app_login') }}" method="post">
<div class="mb-6">
@@ -47,6 +47,14 @@
<button type="submit" class="btn w-full">
Login
</button>
<div class="flex items-center space-x-2 pt-4">
<a href="{{ path('app_reset_password') }}" class="underline">
Passwort vergessen?
</a>
<a href="{{ path('app_registration') }}" class="underline">
Registrierung
</a>
</div>
<input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate') }}">
</form>
</div>