Skip to content

Commit

Permalink
Add budgetId to folder operations and implement update feature
Browse files Browse the repository at this point in the history
  • Loading branch information
deeravenger committed Oct 18, 2024
1 parent 146ac08 commit fb5effb
Show file tree
Hide file tree
Showing 12 changed files with 312 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

namespace App\EconumoOneBundle\Application\Budget\Assembler;

use App\EconumoOneBundle\Application\Budget\Dto\UpdateFolderV1ResultDto;
use App\EconumoOneBundle\Domain\Service\Budget\Dto\BudgetStructureFolderDto;

readonly class UpdateFolderV1ResultAssembler
{
public function __construct(
private BudgetFolderToResultDtoAssembler $budgetFolderToResultDtoAssembler,
) {
}

public function assemble(
BudgetStructureFolderDto $folder
): UpdateFolderV1ResultDto {
$result = new UpdateFolderV1ResultDto();
$result->item = $this->budgetFolderToResultDtoAssembler->assemble($folder);

return $result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,16 @@

/**
* @OA\Schema(
* required={"id"}
* required={"budgetId", "id"}
* )
*/
class DeleteFolderV1RequestDto
{
/**
* @OA\Property(example="9b29b760-ddca-46fb-a754-8743fc2c49a7")
*/
public string $budgetId;

/**
* @OA\Property(example="9b29b760-ddca-46fb-a754-8743fc2c49a7")
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace App\EconumoOneBundle\Application\Budget\Dto;

use OpenApi\Annotations as OA;

/**
* @OA\Schema(
* required={"budgetId", "id", "name"}
* )
*/
class UpdateFolderV1RequestDto
{
/**
* @OA\Property(example="9b29b760-ddca-46fb-a754-8743fc2c49a7")
*/
public string $budgetId;

/**
* Folder ID
* @OA\Property(example="9b29b760-ddca-46fb-a754-8743fc2c49a7")
*/
public string $id;

/**
* @OA\Property(example="Savings")
*/
public string $name;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace App\EconumoOneBundle\Application\Budget\Dto;

use OpenApi\Annotations as OA;

/**
* @OA\Schema(
* required={}
* )
*/
class UpdateFolderV1ResultDto
{
/**
* @OA\Property()
*/
public BudgetFolderResultDto $item;
}
25 changes: 22 additions & 3 deletions src/EconumoOneBundle/Application/Budget/FolderService.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
use App\EconumoOneBundle\Application\Budget\Dto\DeleteFolderV1RequestDto;
use App\EconumoOneBundle\Application\Budget\Dto\DeleteFolderV1ResultDto;
use App\EconumoOneBundle\Application\Budget\Assembler\DeleteFolderV1ResultAssembler;
use App\EconumoOneBundle\Application\Budget\Dto\UpdateFolderV1RequestDto;
use App\EconumoOneBundle\Application\Budget\Dto\UpdateFolderV1ResultDto;
use App\EconumoOneBundle\Application\Budget\Assembler\UpdateFolderV1ResultAssembler;

readonly class FolderService
{
Expand All @@ -25,6 +28,7 @@ public function __construct(
private FolderServiceInterface $folderService,
private DeleteFolderV1ResultAssembler $deleteFolderV1ResultAssembler,
private BudgetFolderRepositoryInterface $budgetFolderRepository,
private UpdateFolderV1ResultAssembler $updateFolderV1ResultAssembler,
) {
}

Expand All @@ -46,13 +50,28 @@ public function deleteFolder(
DeleteFolderV1RequestDto $dto,
Id $userId
): DeleteFolderV1ResultDto {
$budgetId = new Id($dto->budgetId);
$folderId = new Id($dto->id);
$folder = $this->budgetFolderRepository->get($folderId);
if (!$this->budgetAccessService->canUpdateBudget($userId, $folder->getBudget()->getId())) {
if (!$this->budgetAccessService->canUpdateBudget($userId, $budgetId)) {
throw new AccessDeniedException();
}

$this->folderService->delete($folderId);
$this->folderService->delete($budgetId, $folderId);
return $this->deleteFolderV1ResultAssembler->assemble($dto);
}

public function updateFolder(
UpdateFolderV1RequestDto $dto,
Id $userId
): UpdateFolderV1ResultDto {
$budgetId = new Id($dto->budgetId);
$folderId = new Id($dto->id);
$folderName = new BudgetFolderName($dto->name);
if (!$this->budgetAccessService->canUpdateBudget($userId, $budgetId)) {
throw new AccessDeniedException();
}

$folder = $this->folderService->update($budgetId, $folderId, $folderName);
return $this->updateFolderV1ResultAssembler->assemble($folder);
}
}
20 changes: 19 additions & 1 deletion src/EconumoOneBundle/Domain/Service/Budget/FolderService.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use App\EconumoOneBundle\Domain\Entity\ValueObject\BudgetFolderName;
use App\EconumoOneBundle\Domain\Entity\ValueObject\Id;
use App\EconumoOneBundle\Domain\Exception\AccessDeniedException;
use App\EconumoOneBundle\Domain\Factory\BudgetFolderFactoryInterface;
use App\EconumoOneBundle\Domain\Repository\BudgetFolderRepositoryInterface;
use App\EconumoOneBundle\Domain\Repository\BudgetRepositoryInterface;
Expand Down Expand Up @@ -44,9 +45,13 @@ public function create(Id $budgetId, Id $folderId, BudgetFolderName $name): Budg
return $this->budgetStructureFolderDtoAssembler->assemble($newFolder);
}

public function delete(Id $folderId): void
public function delete(Id $budgetId, Id $folderId): void
{
$folder = $this->budgetFolderRepository->get($folderId);
if (!$folder->getBudget()->getId()->isEqual($budgetId)) {
throw new AccessDeniedException();
}

$this->budgetFolderRepository->delete([$folder]);

$toSave = [];
Expand All @@ -63,4 +68,17 @@ public function delete(Id $folderId): void
}
$this->budgetFolderRepository->save($toSave);
}

public function update(Id $budgetId, Id $folderId, BudgetFolderName $name): BudgetStructureFolderDto
{
$folder = $this->budgetFolderRepository->get($folderId);
if (!$folder->getBudget()->getId()->isEqual($budgetId)) {
throw new AccessDeniedException();
}

$folder->updateName($name);
$this->budgetFolderRepository->save([$folder]);

return $this->budgetStructureFolderDtoAssembler->assemble($folder);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,7 @@ interface FolderServiceInterface
{
public function create(Id $budgetId, Id $folderId, BudgetFolderName $name): BudgetStructureFolderDto;

public function delete(Id $folderId): void;
public function update(Id $budgetId, Id $folderId, BudgetFolderName $name): BudgetStructureFolderDto;

public function delete(Id $budgetId, Id $folderId): void;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php

declare(strict_types=1);

namespace App\EconumoOneBundle\UI\Controller\Api\Budget\Folder;

use App\EconumoOneBundle\Application\Budget\FolderService;
use App\EconumoOneBundle\Application\Budget\Dto\UpdateFolderV1RequestDto;
use App\EconumoOneBundle\UI\Controller\Api\Budget\Folder\Validation\UpdateFolderV1Form;
use App\EconumoOneBundle\Application\Exception\ValidationException;
use App\EconumoOneBundle\Domain\Entity\User;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use App\EconumoOneBundle\UI\Service\Validator\ValidatorInterface;
use App\EconumoOneBundle\UI\Service\Response\ResponseFactory;
use Symfony\Component\Routing\Annotation\Route;
use Nelmio\ApiDocBundle\Annotation\Model;
use OpenApi\Annotations as OA;

class UpdateFolderV1Controller extends AbstractController
{
public function __construct(private readonly FolderService $folderService, private readonly ValidatorInterface $validator)
{
}

/**
* Update folder
*
* @OA\Tag(name="Budget"),
* @OA\RequestBody(@OA\JsonContent(ref=@Model(type=\App\EconumoOneBundle\Application\Budget\Dto\UpdateFolderV1RequestDto::class))),
* @OA\Response(
* response=200,
* description="OK",
* @OA\JsonContent(
* type="object",
* allOf={
* @OA\Schema(ref="#/components/schemas/JsonResponseOk"),
* @OA\Schema(
* @OA\Property(
* property="data",
* ref=@Model(type=\App\EconumoOneBundle\Application\Budget\Dto\UpdateFolderV1ResultDto::class)
* )
* )
* }
* )
* ),
* @OA\Response(response=400, description="Bad Request", @OA\JsonContent(ref="#/components/schemas/JsonResponseError")),
* @OA\Response(response=401, description="Unauthorized", @OA\JsonContent(ref="#/components/schemas/JsonResponseUnauthorized")),
* @OA\Response(response=500, description="Internal Server Error", @OA\JsonContent(ref="#/components/schemas/JsonResponseException")),
*
*
* @return Response
* @throws ValidationException
*/
#[Route(path: '/api/v1/budget/update-folder', methods: ['POST'])]
public function __invoke(Request $request): Response
{
$dto = new UpdateFolderV1RequestDto();
$this->validator->validate(UpdateFolderV1Form::class, $request->request->all(), $dto);
/** @var User $user */
$user = $this->getUser();
$result = $this->folderService->updateFolder($dto, $user->getId());

return ResponseFactory::createOkResponse($request, $result);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ public function configureOptions(OptionsResolver $resolver): void

public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('id', TextType::class, [
$builder
->add('budgetId', TextType::class, [
'constraints' => [new NotBlank(), new Uuid()],
])
->add('id', TextType::class, [
'constraints' => [new NotBlank(), new Uuid()],
]);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

declare(strict_types=1);

namespace App\EconumoOneBundle\UI\Controller\Api\Budget\Folder\Validation;

use App\EconumoOneBundle\Domain\Entity\ValueObject\BudgetFolderName;
use App\EconumoOneBundle\UI\Service\Validator\ValueObjectValidationFactoryInterface;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Uuid;

class UpdateFolderV1Form extends AbstractType
{
public function __construct(private readonly ValueObjectValidationFactoryInterface $valueObjectValidationFactory)
{
}

public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults(['csrf_protection' => false]);
}

public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('budgetId', TextType::class, [
'constraints' => [new NotBlank(), new Uuid()],
])
->add('id', TextType::class, [
'constraints' => [new NotBlank(), new Uuid()],
])
->add('name', TextType::class, options: [
'constraints' => [
new NotBlank(),
new Length(['max' => BudgetFolderName::MAX_LENGTH, 'min' => BudgetFolderName::MIN_LENGTH]),
$this->valueObjectValidationFactory->create(BudgetFolderName::class)
],
]);
}
}
57 changes: 57 additions & 0 deletions tests/api/v1/budget/UpdateFolderCest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

declare(strict_types=1);

namespace App\Tests\api\v1\budget;

use Codeception\Exception\ModuleException;
use App\Tests\ApiTester;
use Codeception\Util\HttpCode;

class UpdateFolderCest
{
private string $url = '/api/v1/budget/update-folder';

/**
* @throws ModuleException
*/
public function requestShouldReturn200ResponseCode(ApiTester $I): void
{
$I->amAuthenticatedAsJohn();
$I->sendPOST($this->url, ['id' => 'test']);
$I->seeResponseCodeIs(HttpCode::OK);
}

/**
* @throws ModuleException
*/
public function requestShouldReturn400ResponseCode(ApiTester $I): void
{
$I->amAuthenticatedAsJohn();
$I->sendPOST($this->url, ['unexpected_param' => 'test']);
$I->seeResponseCodeIs(HttpCode::BAD_REQUEST);
}

/**
* @throws ModuleException
*/
public function requestShouldReturn401ResponseCode(ApiTester $I): void
{
$I->sendPOST($this->url, ['id' => 'test']);
$I->seeResponseCodeIs(HttpCode::UNAUTHORIZED);
}

/**
* @throws ModuleException
*/
public function requestShouldReturnResponseWithCorrectStructure(ApiTester $I): void
{
$I->amAuthenticatedAsJohn();
$I->sendPOST($this->url, ['id' => 'test']);
$I->seeResponseMatchesJsonType([
'data' => [
'result' => 'string',
],
]);
}
}
12 changes: 12 additions & 0 deletions tests/functional/api/v1/budget/UpdateFolderCest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace App\Tests\functional\api\v1\budget;

use App\Tests\FunctionalTester;

class UpdateFolderCest
{
private string $url = '/api/v1/budget/update-folder';
}

0 comments on commit fb5effb

Please sign in to comment.