Skip to content

Commit

Permalink
User Details Endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
galeaspablo committed Sep 25, 2024
1 parent 912bcb3 commit 21a1174
Show file tree
Hide file tree
Showing 20 changed files with 658 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
#!/bin/bash
set -e

php bin/console galeas:dbs:updates

sleep 50000000
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
use Galeas\Api\BoundedContext\Identity\User\CommandHandler\RequestPrimaryEmailChange\RequestPrimaryEmailChangeHandler;
use Galeas\Api\BoundedContext\Identity\User\CommandHandler\SignUp\SignUpHandler;
use Galeas\Api\BoundedContext\Identity\User\CommandHandler\VerifyPrimaryEmail\VerifyPrimaryEmailHandler;
use Galeas\Api\BoundedContext\Identity\User\Query\GetUserDetailsQuery;
use Galeas\Api\BoundedContext\Identity\User\Query\ListSentVerificationEmailQuery;
use Galeas\Api\BoundedContext\Identity\User\QueryHandler\GetUserDetailsQueryHandler;
use Galeas\Api\BoundedContext\Identity\User\QueryHandler\ListSentVerificationEmailQueryHandler;
use Galeas\Api\CommonController\BaseController;
use Symfony\Component\HttpFoundation\Request;
Expand All @@ -24,17 +26,20 @@ class UserController extends BaseController
private VerifyPrimaryEmailHandler $verifyPrimaryEmailHandler;
private RequestPrimaryEmailChangeHandler $requestPrimaryEmailChangeHandler;
private ListSentVerificationEmailQueryHandler $listSentVerificationEmailQueryHandler;
private GetUserDetailsQueryHandler $getUserDetailsQueryHandler;

public function __construct(
SignUpHandler $signUpHandler,
VerifyPrimaryEmailHandler $verifyPrimaryEmailHandler,
RequestPrimaryEmailChangeHandler $requestPrimaryEmailChangeHandler,
ListSentVerificationEmailQueryHandler $listSentVerificationEmailQueryHandler
ListSentVerificationEmailQueryHandler $listSentVerificationEmailQueryHandler,
GetUserDetailsQueryHandler $getUserDetailsQueryHandler
) {
$this->signUpHandler = $signUpHandler;
$this->verifyPrimaryEmailHandler = $verifyPrimaryEmailHandler;
$this->requestPrimaryEmailChangeHandler = $requestPrimaryEmailChangeHandler;
$this->listSentVerificationEmailQueryHandler = $listSentVerificationEmailQueryHandler;
$this->getUserDetailsQueryHandler = $getUserDetailsQueryHandler;
}

/**
Expand Down Expand Up @@ -112,4 +117,23 @@ public function listSentVerificationEmails(Request $request): Response
Response::HTTP_OK
);
}

/**
* @RequestSchema(name="V1_Identity_User_Details")
*
* @ResponseSchema(name="V1_Identity_User_Details")
*/
#[Route('/identity/user/details', name: 'V1_Identity_User_Details', methods: ['POST'])]
public function getUserDetails(Request $request): Response
{
return $this->jsonPostRequestJsonResponse(
$request,
'Request/V1_Identity_User_Details.json',
'Response/V1_Identity_User_Details.json',
GetUserDetailsQuery::class,
$this->getUserDetailsQueryHandler,
null,
Response::HTTP_OK
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Galeas\Api\BoundedContext\Identity\User\Projection\SentVerificationEmail\SentVerificationEmailProjector;
use Galeas\Api\BoundedContext\Identity\User\Projection\TakenEmail\TakenEmailProjector;
use Galeas\Api\BoundedContext\Identity\User\Projection\TakenUsername\TakenUsernameProjector;
use Galeas\Api\BoundedContext\Identity\User\Projection\UserDetails\UserDetailsProjector;
use Galeas\Api\BoundedContext\Identity\User\Reaction\SendPrimaryEmailVerification\SendPrimaryEmailVerificationReactor;
use Galeas\Api\CommonController\ProjectionReactionController;
use Symfony\Component\HttpFoundation\Request;
Expand All @@ -22,19 +23,22 @@ class UserProjectionReactionController extends ProjectionReactionController
private TakenUsernameProjector $takenUsernameProjector;
private SentVerificationEmailProjector $sentVerificationEmailProjector;
private SendPrimaryEmailVerificationReactor $sendPrimaryEmailVerificationReactor;
private UserDetailsProjector $userDetailsProjector;

public function __construct(
PrimaryEmailVerificationCodeProjector $primaryEmailVerificationCodeProjector,
TakenEmailProjector $takenEmailProjector,
TakenUsernameProjector $takenUsernameProjector,
SentVerificationEmailProjector $sentVerificationEmailProjector,
SendPrimaryEmailVerificationReactor $sendPrimaryEmailVerificationReactor
SendPrimaryEmailVerificationReactor $sendPrimaryEmailVerificationReactor,
UserDetailsProjector $userDetailsProjector
) {
$this->primaryEmailVerificationCodeProjector = $primaryEmailVerificationCodeProjector;
$this->takenEmailProjector = $takenEmailProjector;
$this->takenUsernameProjector = $takenUsernameProjector;
$this->sentVerificationEmailProjector = $sentVerificationEmailProjector;
$this->sendPrimaryEmailVerificationReactor = $sendPrimaryEmailVerificationReactor;
$this->userDetailsProjector = $userDetailsProjector;
}

#[Route('/projection/primary_email_verification_code', name: 'projection_primary_email_verification_code', methods: ['POST'])]
Expand Down Expand Up @@ -77,6 +81,16 @@ public function sentVerificationEmail(Request $request): Response
);
}

#[Route('/projection/user_details', name: 'projection_user_details', methods: ['POST'])]
public function userDetails(Request $request): Response
{
return $this->jsonPostRequestJsonResponse(
$request,
$this->userDetailsProjector,
200
);
}

#[Route('/reaction/send_primary_email_verification', name: 'reaction_send_primary_email_verification', methods: ['POST'])]
public function sendPrimaryEmailVerificationReactor(Request $request): Response
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mongo-mapping xmlns="http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping.xsd">
<document name="Galeas\Api\BoundedContext\Identity\User\Projection\UserDetails\UserDetails" collection="Identity_User_UserDetails_UserDetails">
<id type="string" strategy="NONE"/>
<embed-one field="primaryEmailStatus">
<discriminator-field name="type"/>
<discriminator-map>
<discriminator-mapping value="unverifiedEmail" class="Galeas\Api\BoundedContext\Identity\User\Projection\UserDetails\ValueObject\UnverifiedEmail"/>
<discriminator-mapping value="verifiedEmail" class="Galeas\Api\BoundedContext\Identity\User\Projection\UserDetails\ValueObject\VerifiedEmail"/>
<discriminator-mapping value="verifiedButRequestedNewEmail" class="Galeas\Api\BoundedContext\Identity\User\Projection\UserDetails\ValueObject\VerifiedEmailButRequestedNewEmail"/>
</discriminator-map>
</embed-one>
</document>
</doctrine-mongo-mapping>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mongo-mapping xmlns="http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping.xsd">
<embedded-document name="Galeas\Api\BoundedContext\Identity\User\Projection\UserDetails\ValueObject\UnverifiedEmail">
<field name="email" type="string"/>
</embedded-document>
</doctrine-mongo-mapping>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mongo-mapping xmlns="http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping.xsd">
<embedded-document name="Galeas\Api\BoundedContext\Identity\User\Projection\UserDetails\ValueObject\VerifiedEmail">
<field name="email" type="string"/>
</embedded-document>
</doctrine-mongo-mapping>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mongo-mapping xmlns="http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping.xsd">
<embedded-document name="Galeas\Api\BoundedContext\Identity\User\Projection\UserDetails\ValueObject\VerifiedEmailButRequestedNewEmail">
<field name="verifiedEmail" type="string"/>
<field name="requestedEmail" type="string"/>
</embedded-document>
</doctrine-mongo-mapping>
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

declare(strict_types=1);

namespace Galeas\Api\BoundedContext\Identity\User\Projection\UserDetails;

use Doctrine\ODM\MongoDB\DocumentManager;
use Galeas\Api\BoundedContext\Identity\User\Projection\UserDetails\ValueObject\UnverifiedEmail;
use Galeas\Api\BoundedContext\Identity\User\Projection\UserDetails\ValueObject\VerifiedEmail;
use Galeas\Api\BoundedContext\Identity\User\Projection\UserDetails\ValueObject\VerifiedEmailButRequestedNewEmail;
use Galeas\Api\CommonException\ProjectionCannotRead;

class GetUserDetails
{
private DocumentManager $projectionDocumentManager;

public function __construct(DocumentManager $projectionDocumentManager)
{
$this->projectionDocumentManager = $projectionDocumentManager;
}

/**
* @return array{userId: string, primaryEmailStatus: array{unverifiedEmail: array{email: string}}|array{verifiedEmail: array{email: string}}|array{verifiedEmailButRequestedNewEmail: array{requestedEmail: string, verifiedEmail: string}}}
*
* @throws ProjectionCannotRead
*/
public function getUserDetails(string $userId): array
{
try {
$userDetails = $this->projectionDocumentManager
->createQueryBuilder(UserDetails::class)
->field('id')->equals($userId)
->getQuery()
->getSingleResult()
;

if ($userDetails instanceof UserDetails) {
$status = $userDetails->getPrimaryEmailStatus();

switch ($status) {
case $status instanceof UnverifiedEmail:
$primaryEmailStatus = [
'unverifiedEmail' => [
'email' => $status->getEmail(),
],
];

break;

case $status instanceof VerifiedEmail:
$primaryEmailStatus = [
'verifiedEmail' => [
'email' => $status->getEmail(),
],
];

break;

case $status instanceof VerifiedEmailButRequestedNewEmail:
$primaryEmailStatus = [
'verifiedButRequestedNewEmail' => [
'requestedEmail' => $status->getRequestedEmail(),
'verifiedEmail' => $status->getVerifiedEmail(),
],
];

break;
}

return [
'userId' => $userDetails->getUserId(),
'primaryEmailStatus' => $primaryEmailStatus,
];
}

throw new \Exception('Expected UserDetails instance, got nothing.');
} catch (\Throwable $exception) {
throw new ProjectionCannotRead($exception);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

namespace Galeas\Api\BoundedContext\Identity\User\Projection\UserDetails;

use Galeas\Api\BoundedContext\Identity\User\Projection\UserDetails\ValueObject\UnverifiedEmail;
use Galeas\Api\BoundedContext\Identity\User\Projection\UserDetails\ValueObject\VerifiedEmail;
use Galeas\Api\BoundedContext\Identity\User\Projection\UserDetails\ValueObject\VerifiedEmailButRequestedNewEmail;

class UserDetails
{
private string $id;

private UnverifiedEmail|VerifiedEmail|VerifiedEmailButRequestedNewEmail $primaryEmailStatus;

private function __construct() {}

public function getUserId(): string
{
return $this->id;
}

public function getPrimaryEmailStatus(): UnverifiedEmail|VerifiedEmail|VerifiedEmailButRequestedNewEmail
{
return $this->primaryEmailStatus;
}

public function changePrimaryEmailStatus(UnverifiedEmail|VerifiedEmail|VerifiedEmailButRequestedNewEmail $primaryEmailStatus): self
{
$this->primaryEmailStatus = $primaryEmailStatus;

return $this;
}

public static function fromProperties(string $userId, UnverifiedEmail|VerifiedEmail|VerifiedEmailButRequestedNewEmail $primaryEmailStatus): self
{
$userDetails = new self();
$userDetails->id = $userId;
$userDetails->primaryEmailStatus = $primaryEmailStatus;

return $userDetails;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php

declare(strict_types=1);

namespace Galeas\Api\BoundedContext\Identity\User\Projection\UserDetails;

use Doctrine\ODM\MongoDB\DocumentManager;
use Galeas\Api\BoundedContext\Identity\User\Event\PrimaryEmailChangeRequested;
use Galeas\Api\BoundedContext\Identity\User\Event\PrimaryEmailVerified;
use Galeas\Api\BoundedContext\Identity\User\Event\SignedUp;
use Galeas\Api\BoundedContext\Identity\User\Projection\UserDetails\ValueObject\UnverifiedEmail;
use Galeas\Api\BoundedContext\Identity\User\Projection\UserDetails\ValueObject\VerifiedEmail;
use Galeas\Api\BoundedContext\Identity\User\Projection\UserDetails\ValueObject\VerifiedEmailButRequestedNewEmail;
use Galeas\Api\Common\Event\Event;
use Galeas\Api\CommonException\ProjectionCannotProcess;
use Galeas\Api\Service\QueueProcessor\EventProjector;

class UserDetailsProjector implements EventProjector
{
private DocumentManager $projectionDocumentManager;

public function __construct(DocumentManager $projectionDocumentManager)
{
$this->projectionDocumentManager = $projectionDocumentManager;
}

public function project(Event $event): void
{
try {
if (
$event instanceof SignedUp
|| $event instanceof PrimaryEmailVerified
|| $event instanceof PrimaryEmailChangeRequested
) {
$userId = $event->aggregateId()->id();
} else {
return;
}

$userDetails = $this->projectionDocumentManager
->createQueryBuilder(UserDetails::class)
->field('id')->equals($userId)
->getQuery()
->getSingleResult()
;

if ($userDetails instanceof UserDetails && $event instanceof SignedUp) {
$userDetails = UserDetails::fromProperties($userId, UnverifiedEmail::fromProperties(
$event->primaryEmail()
));
} elseif ($userDetails instanceof UserDetails) {
$currentStatus = $userDetails->getPrimaryEmailStatus();
$newStatus = $this->getPrimaryEmailStatusFromEvent($event, $currentStatus);
$userDetails->changePrimaryEmailStatus($newStatus);
} else {
throw new \InvalidArgumentException('Unsupported operation');
}

$this->projectionDocumentManager->persist($userDetails);
$this->projectionDocumentManager->flush();
} catch (\Throwable $throwable) {
throw new ProjectionCannotProcess($throwable);
}
}

/**
* @throws \InvalidArgumentException
*/
private function getPrimaryEmailStatusFromEvent(
Event $event,
null|UnverifiedEmail|VerifiedEmail|VerifiedEmailButRequestedNewEmail $currentStatus = null
): UnverifiedEmail|VerifiedEmail|VerifiedEmailButRequestedNewEmail {
if ($event instanceof PrimaryEmailVerified && $currentStatus instanceof UnverifiedEmail) {
return VerifiedEmail::fromProperties($currentStatus->getEmail());
}
if ($event instanceof PrimaryEmailVerified && $currentStatus instanceof VerifiedEmailButRequestedNewEmail) {
return VerifiedEmail::fromProperties($currentStatus->getRequestedEmail());
}
if ($event instanceof PrimaryEmailChangeRequested && $currentStatus instanceof UnverifiedEmail) {
return UnverifiedEmail::fromProperties($event->newEmailRequested());
}
if ($event instanceof PrimaryEmailChangeRequested && $currentStatus instanceof VerifiedEmailButRequestedNewEmail) {
return VerifiedEmailButRequestedNewEmail::fromProperties($currentStatus->getVerifiedEmail(), $event->newEmailRequested());
}

throw new \InvalidArgumentException('Unsupported operation');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

namespace Galeas\Api\BoundedContext\Identity\User\Projection\UserDetails\ValueObject;

class UnverifiedEmail
{
private string $email;

private function __construct() {}

public function getEmail(): string
{
return $this->email;
}

public static function fromProperties(string $email): self
{
$instance = new self();
$instance->email = $email;

return $instance;
}
}
Loading

0 comments on commit 21a1174

Please sign in to comment.