Skip to content

Commit

Permalink
Merge pull request #87 from cash-track/feature/tags
Browse files Browse the repository at this point in the history
Feature / Tags
  • Loading branch information
vokomarov authored Jul 24, 2022
2 parents 6085fee + 63b2da5 commit fdc1023
Show file tree
Hide file tree
Showing 26 changed files with 1,313 additions and 107 deletions.
45 changes: 34 additions & 11 deletions app/src/Controller/Tags/ChargesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,57 +7,64 @@
use App\Controller\AuthAwareController;
use App\Database\Charge;
use App\Database\Tag;
use App\Database\Wallet;
use App\Repository\ChargeRepository;
use App\Repository\TagRepository;
use App\Repository\UserRepository;
use App\Service\ChargeWalletService;
use App\Service\Pagination\PaginationFactory;
use App\Service\Statistics\ChargeAmountGraph;
use App\View\ChargesView;
use App\View\CurrencyView;
use Psr\Http\Message\ResponseInterface;
use Spiral\Auth\AuthScope;
use Spiral\Http\Request\InputManager;
use Spiral\Http\ResponseWrapper;
use Spiral\Router\Annotation\Route;

final class ChargesController extends AuthAwareController
{
public function __construct(
AuthScope $auth,
private ResponseWrapper $response,
private TagRepository $tagRepository,
private ChargeRepository $chargeRepository,
private PaginationFactory $paginationFactory,
private ChargesView $chargesView,
private ChargeWalletService $chargeWalletService,
private UserRepository $userRepository,
private CurrencyView $currencyView,
private readonly ResponseWrapper $response,
private readonly TagRepository $tagRepository,
private readonly ChargeRepository $chargeRepository,
private readonly PaginationFactory $paginationFactory,
private readonly ChargesView $chargesView,
private readonly ChargeWalletService $chargeWalletService,
private readonly UserRepository $userRepository,
private readonly CurrencyView $currencyView,
) {
parent::__construct($auth);
}

#[Route(route: '/tags/<id:\d+>/charges', name: 'tag.charges', methods: 'GET', group: 'auth')]
public function list(int $id): ResponseInterface
public function list(int $id, InputManager $input): ResponseInterface
{
$tag = $this->tagRepository->findByPKByUsersPK($id, $this->userRepository->getCommonUserIDs($this->user));
if (! $tag instanceof Tag) {
return $this->response->create(404);
}

$charges = $this->chargeRepository
->filter($input->query->fetch(['date-from', 'date-to']))
->paginate($this->paginationFactory->createPaginator())
->findByTagIdWithPagination((int) $tag->id);

return $this->chargesView->jsonPaginated($charges, $this->chargeRepository->getPaginationState());
return $this->chargesView->withRelation(Wallet::class)
->jsonPaginated($charges, $this->chargeRepository->getPaginationState());
}

#[Route(route: '/tags/<id:\d+>/charges/total', name: 'tag.charges.total', methods: 'GET', group: 'auth')]
public function total(int $id): ResponseInterface
public function total(int $id, InputManager $input): ResponseInterface
{
$tag = $this->tagRepository->findByPKByUsersPK($id, $this->userRepository->getCommonUserIDs($this->user));
if (! $tag instanceof Tag) {
return $this->response->create(404);
}

$this->chargeRepository->filter($input->query->fetch(['date-from', 'date-to']));

$income = $this->chargeRepository->totalByTagPK((int) $tag->id, Charge::TYPE_INCOME);
$expense = $this->chargeRepository->totalByTagPK((int) $tag->id, Charge::TYPE_EXPENSE);

Expand All @@ -70,4 +77,20 @@ public function total(int $id): ResponseInterface
],
]);
}

#[Route(route: '/tags/<id:\d+>/charges/graph', name: 'tag.charges.graph', methods: 'GET', group: 'auth')]
public function graph(int $id, InputManager $input, ChargeAmountGraph $graph): ResponseInterface
{
$tag = $this->tagRepository->findByPKByUsersPK($id, $this->userRepository->getCommonUserIDs($this->user));
if (! $tag instanceof Tag) {
return $this->response->create(404);
}

$graph->filter($input->query->fetch(['date-from', 'date-to']));
$graph->groupBy($input->query('group-by'));

return $this->response->json([
'data' => $graph->getGraphByTag($tag),
]);
}
}
50 changes: 50 additions & 0 deletions app/src/Controller/Tags/CommonController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

declare(strict_types=1);

namespace App\Controller\Tags;

use App\Controller\AuthAwareController;
use App\Database\Tag;
use App\Repository\TagRepository;
use App\Repository\UserRepository;
use App\View\TagsView;
use App\View\TagView;
use Psr\Http\Message\ResponseInterface;
use Spiral\Auth\AuthScope;
use Spiral\Http\ResponseWrapper;
use Spiral\Router\Annotation\Route;

final class CommonController extends AuthAwareController
{
public function __construct(
AuthScope $auth,
private ResponseWrapper $response,
private TagRepository $tagRepository,
private TagsView $tagsView,
private TagView $tagView,
private UserRepository $userRepository,
) {
parent::__construct($auth);
}

#[Route(route: '/tags/common', name: 'tag.common.list', methods: 'GET', group: 'auth')]
public function list(): ResponseInterface
{
return $this->tagsView->json($this->tagRepository->findAllByUsersPK(
$this->userRepository->getCommonUserIDs($this->user)
));
}

#[Route(route: '/tags/common/<id>', name: 'tag.common.index', methods: 'GET', group: 'auth')]
public function index(int $id): ResponseInterface
{
$tag = $this->tagRepository->findByPKByUsersPK($id, $this->userRepository->getCommonUserIDs($this->user));

if (! $tag instanceof Tag) {
return $this->response->create(404);
}

return $this->tagView->json($tag);
}
}
11 changes: 0 additions & 11 deletions app/src/Controller/Tags/TagsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@

use App\Controller\AuthAwareController;
use App\Database\Tag;
use App\Database\User;
use App\Repository\TagRepository;
use App\Repository\UserRepository;
use App\Request\Tag\CreateRequest;
use App\Request\Tag\UpdateRequest;
use App\Service\TagService;
Expand All @@ -30,7 +28,6 @@ public function __construct(
private TagService $tagService,
private TagsView $tagsView,
private TagView $tagView,
private UserRepository $userRepository,
) {
parent::__construct($auth);
}
Expand All @@ -41,14 +38,6 @@ public function list(): ResponseInterface
return $this->tagsView->json($this->tagRepository->findAllByUserPK((int) $this->user->id));
}

#[Route(route: '/tags/common', name: 'tag.list.common', methods: 'GET', group: 'auth')]
public function listCommon(): ResponseInterface
{
return $this->tagsView->json($this->tagRepository->findAllByUsersPK(
$this->userRepository->getCommonUserIDs($this->user)
));
}

#[Route(route: '/tags', name: 'tag.create', methods: 'POST', group: 'auth')]
public function create(CreateRequest $request): ResponseInterface
{
Expand Down
4 changes: 2 additions & 2 deletions app/src/Controller/Wallets/Charges/ChargesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ public function create(int $walletId, CreateRequest $request): ResponseInterface
], 500);
}

return $this->chargeView->json($charge);
return $this->chargeView->withRelation(Wallet::class)->json($charge);
}

/**
Expand Down Expand Up @@ -175,7 +175,7 @@ public function update(int $walletId, string $chargeId, CreateRequest $request):
], 500);
}

return $this->chargeView->json($charge);
return $this->chargeView->withRelation(Wallet::class)->json($charge);
}

/**
Expand Down
15 changes: 12 additions & 3 deletions app/src/Repository/ChargeRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@

namespace App\Repository;

use App\Service\Filter\Filter;
use Cycle\ORM\Select\AbstractLoader;
use Cycle\ORM\Select\Repository;
use Cycle\Database\Injection\Parameter;

class ChargeRepository extends Repository
{
use Paginator;
use Filter;

/**
* @param string $chargeId
Expand Down Expand Up @@ -65,19 +67,24 @@ public function findByWalletIdWithPagination(int $walletId)

/**
* @param int $tagId
* @return array
* @return \App\Database\Charge[]
*/
public function findByTagIdWithPagination(int $tagId)
public function findByTagIdWithPagination(int $tagId): array
{
$query = $this->select()
->load('user')
->load('tags')
->load('wallet')
->where('tags.id', $tagId)
->orderBy('created_at', 'DESC');

$this->injectFilter($query);
$query = $this->injectPaginator($query);

return $query->fetchAll();
/** @var \App\Database\Charge[] $charges */
$charges = $query->fetchAll();

return $charges;
}

/**
Expand Down Expand Up @@ -131,6 +138,8 @@ public function totalByTagPK(int $tagId, string $type = null): float
$query = $query->where('type', $type);
}

$this->injectFilter($query);

return (float) $query->sum('amount');
}

Expand Down
72 changes: 72 additions & 0 deletions app/src/Service/Filter/Filter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

declare(strict_types=1);

namespace App\Service\Filter;

use Cycle\Database\Query\SelectQuery;
use Cycle\ORM\Select;

trait Filter
{
/**
* @var array<string, string>
*/
protected array $filter = [];

public function filter(array $query): static
{
if (count($query) === 0) {
return $this;
}

$this->filter = [];

foreach (FilterType::cases() as $type) {
if (! array_key_exists($type->value, $query)) {
continue;
}

if (! $type->validate($query[$type->value])) {
continue;
}

$this->filter[$type->value] = $query[$type->value];
}

return $this;
}

public function hasFilter(FilterType $filter): bool
{
return array_key_exists($filter->value, $this->filter);
}

public function getFilterValue(FilterType $filter): ?string
{
return $this->filter[$filter->value] ?? null;
}

protected function filterColumnsMapping(): array
{
return [
FilterType::ByDateFrom->value => 'created_at',
FilterType::ByDateTo->value => 'created_at',
];
}

protected function injectFilter(Select|SelectQuery $query): void
{
if (! count($this->filter)) {
return;
}

foreach ($this->filter as $type => $value) {
try {
FilterType::from($type)->inject($query, $value, $this->filterColumnsMapping());
} catch (\ValueError) {
continue;
}
}
}
}
44 changes: 44 additions & 0 deletions app/src/Service/Filter/FilterType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

namespace App\Service\Filter;

use Cycle\Database\Query\SelectQuery;
use Cycle\ORM\Select;

// FIXME. PHPCS PSR12 Does not support PHP 8.1 new feature syntax
// @codingStandardsIgnoreStart
enum FilterType: string {
case ByDateFrom = 'date-from';
case ByDateTo = 'date-to';

public function inject(Select|SelectQuery $query, string $value, array $mapping): void
{
switch ($this) {
case self::ByDateFrom:
$query->where($mapping[$this->value] ?? 'created_at', '>=', new \DateTimeImmutable($value));
break;
case self::ByDateTo:
$query->where($mapping[$this->value] ?? 'created_at', '<=', new \DateTimeImmutable($value));
break;
}
}

public function validate(string $value): bool
{
switch ($this) {
case self::ByDateFrom:
case self::ByDateTo:
try {
new \DateTimeImmutable($value);
} catch (\Throwable) {
return false;
}
break;
}

return true;
}
}
// @codingStandardsIgnoreEnd
Loading

0 comments on commit fdc1023

Please sign in to comment.