Skip to content

Commit

Permalink
Merge pull request #86 from cash-track/feature/tags
Browse files Browse the repository at this point in the history
Feature / Tags
  • Loading branch information
vokomarov authored Jun 25, 2022
2 parents 9aaebe6 + e323a23 commit 6085fee
Show file tree
Hide file tree
Showing 8 changed files with 185 additions and 15 deletions.
9 changes: 7 additions & 2 deletions app/src/Controller/Tags/ChargesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@
use App\Database\Tag;
use App\Repository\ChargeRepository;
use App\Repository\TagRepository;
use App\Repository\UserRepository;
use App\Service\ChargeWalletService;
use App\Service\Pagination\PaginationFactory;
use App\View\ChargesView;
use App\View\CurrencyView;
use Psr\Http\Message\ResponseInterface;
use Spiral\Auth\AuthScope;
use Spiral\Http\ResponseWrapper;
Expand All @@ -27,14 +29,16 @@ public function __construct(
private PaginationFactory $paginationFactory,
private ChargesView $chargesView,
private ChargeWalletService $chargeWalletService,
private UserRepository $userRepository,
private CurrencyView $currencyView,
) {
parent::__construct($auth);
}

#[Route(route: '/tags/<id:\d+>/charges', name: 'tag.charges', methods: 'GET', group: 'auth')]
public function list(int $id): ResponseInterface
{
$tag = $this->tagRepository->findByPKByUserPK($id, (int) $this->user->id);
$tag = $this->tagRepository->findByPKByUsersPK($id, $this->userRepository->getCommonUserIDs($this->user));
if (! $tag instanceof Tag) {
return $this->response->create(404);
}
Expand All @@ -49,7 +53,7 @@ public function list(int $id): ResponseInterface
#[Route(route: '/tags/<id:\d+>/charges/total', name: 'tag.charges.total', methods: 'GET', group: 'auth')]
public function total(int $id): ResponseInterface
{
$tag = $this->tagRepository->findByPKByUserPK($id, (int) $this->user->id);
$tag = $this->tagRepository->findByPKByUsersPK($id, $this->userRepository->getCommonUserIDs($this->user));
if (! $tag instanceof Tag) {
return $this->response->create(404);
}
Expand All @@ -62,6 +66,7 @@ public function total(int $id): ResponseInterface
'totalAmount' => $this->chargeWalletService->totalByIncomeAndExpense($income, $expense),
'totalIncomeAmount' => $income,
'totalExpenseAmount' => $expense,
'currency' => $this->currencyView->map($this->user->getDefaultCurrency()),
],
]);
}
Expand Down
11 changes: 11 additions & 0 deletions app/src/Controller/Tags/TagsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

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 @@ -28,6 +30,7 @@ public function __construct(
private TagService $tagService,
private TagsView $tagsView,
private TagView $tagView,
private UserRepository $userRepository,
) {
parent::__construct($auth);
}
Expand All @@ -38,6 +41,14 @@ 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
2 changes: 1 addition & 1 deletion app/src/Controller/Wallets/TagsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public function find(int $walletId, string $query = ''): ResponseInterface
return $this->response->create(404);
}

$tags = $this->tagRepository->findAllByUsersPK($wallet->getUserIDs(), urldecode($query));
$tags = $this->tagRepository->searchAllByUsersPK($wallet->getUserIDs(), urldecode($query));

return $this->tagsView->json($tags);
}
Expand Down
2 changes: 2 additions & 0 deletions app/src/Repository/ChargeRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public function findByWalletIDLatest(int $walletId, int $limit = 4): array
/** @var \App\Database\Charge[] $charges */
$charges = $this->select()
->load('user')
->load('tags')
->where('wallet_id', $walletId)
->orderBy('created_at', 'DESC')
->limit($limit)
Expand Down Expand Up @@ -70,6 +71,7 @@ public function findByTagIdWithPagination(int $tagId)
{
$query = $this->select()
->load('user')
->load('tags')
->where('tags.id', $tagId)
->orderBy('created_at', 'DESC');

Expand Down
50 changes: 38 additions & 12 deletions app/src/Repository/TagRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,30 +30,56 @@ public function findAllByUserPK(int $userID): array
* @param int $limit
* @return \App\Database\Tag[]
*/
public function findAllByUsersPK(array $userIDs, string $query = '', int $limit = 10): array
public function searchAllByUsersPK(array $userIDs, string $query = '', int $limit = 10): array
{
/**
* @var \App\Database\Tag[] $tags
* @psalm-suppress InternalClass
* @psalm-suppress UndefinedMagicMethod
*/
$tags = $this->select()
$tags = $this->selectAllOrderedByCharges()
->where(['user_id' => ['in' => new Parameter($userIDs)]])
->where('name', 'like', "{$query}%")
->with('tagCharges', [
'method' => Select\JoinableLoader::LEFT_JOIN
])
->with('tagCharges.charge', [
'method' => Select\JoinableLoader::LEFT_JOIN
])
->groupBy('tag.id')
->orderBy(new Expression('count(tag.id)'), SelectQuery::SORT_DESC)
->limit($limit)
->fetchAll();

return $tags;
}

/**
* @param array<array-key, int> $userIDs
* @return \App\Database\Tag[]
*/
public function findAllByUsersPK(array $userIDs): array
{
/**
* @var \App\Database\Tag[] $tags
*/
$tags = $this->selectAllOrderedByCharges()
->where(['user_id' => ['in' => new Parameter($userIDs)]])
->fetchAll();

return $tags;
}

/**
* @return \Cycle\ORM\Select
*/
protected function selectAllOrderedByCharges(): Select
{
/**
* @psalm-suppress InternalClass
* @psalm-suppress UndefinedMagicMethod
*/
return $this->select()
->with('tagCharges', [
'method' => Select\JoinableLoader::LEFT_JOIN
])
->with('tagCharges.charge', [
'method' => Select\JoinableLoader::LEFT_JOIN
])
->groupBy('tag.id')
->orderBy(new Expression('count(tag.id)'), SelectQuery::SORT_DESC);
}

/**
* @param array<array-key, int> $ids
* @param array<array-key, int> $userIDs
Expand Down
27 changes: 27 additions & 0 deletions app/src/Repository/UserRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,33 @@ public function findByCommonWallets(User $user): array
->fetchAll();
}

/**
* @param \App\Database\User $user
* @return array<array-key, int>
*/
public function getCommonUserIDs(User $user): array
{
$users = $this->findAllByCommonWallets($user);

if (count($users) === 0) {
return [(int) $user->id];
}

return array_map(fn (User $user) => (int) $user->id, $users);
}

/**
* @param \App\Database\User $user
* @return \App\Database\User[]
*/
public function findAllByCommonWallets(User $user): array
{
/** @var \App\Database\User[] $users */
$users = $this->byCommonWallets($user)->fetchAll();

return $users;
}

/**
* @psalm-suppress UndefinedMagicMethod
* @param \App\Database\User $user
Expand Down
81 changes: 81 additions & 0 deletions tests/Feature/Controller/Tags/TagsControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,30 @@
use App\Service\TagService;
use PHPUnit\Framework\MockObject\MockObject;
use Tests\DatabaseTransaction;
use Tests\Factories\ChargeFactory;
use Tests\Factories\TagFactory;
use Tests\Factories\UserFactory;
use Tests\Factories\WalletFactory;
use Tests\Fixtures;
use Tests\TestCase;

class TagsControllerTest extends TestCase implements DatabaseTransaction
{
protected UserFactory $userFactory;

protected WalletFactory $walletFactory;

protected ChargeFactory $chargeFactory;

protected TagFactory $tagFactory;

protected function setUp(): void
{
parent::setUp();

$this->userFactory = $this->getContainer()->get(UserFactory::class);
$this->walletFactory = $this->getContainer()->get(WalletFactory::class);
$this->chargeFactory = $this->getContainer()->get(ChargeFactory::class);
$this->tagFactory = $this->getContainer()->get(TagFactory::class);
}

Expand Down Expand Up @@ -66,6 +74,79 @@ public function testListDoesNotReturnForeignTags(): void
$this->assertEquals([], $body['data']);
}

public function testListCommonRequireAuth(): void
{
$response = $this->get('/tags/common');

$response->assertUnauthorized();
}

public function testListCommonReturnTags(): void
{
$auth = $this->makeAuth($user = $this->userFactory->create());

[$tag1, $tag2, $tag3] = $this->tagFactory->forUser($user)->createMany(3)->toArray();

$wallet = $this->walletFactory->forUser($user)->create();

$this->chargeFactory->forWallet($wallet)->forUser($user)->withTags([$tag1])->createMany(3);
$this->chargeFactory->forWallet($wallet)->forUser($user)->withTags([$tag2])->create();

$response = $this->withAuth($auth)->get('/tags/common');

$response->assertOk();

$body = $this->getJsonResponseBody($response);

$this->assertIsArray($body);
$this->assertArrayHasKey('data', $body);
$this->assertCount(3, $body['data']);

foreach ([$tag1, $tag2, $tag3] as $index => $tag) {
$this->assertArrayHasKey($index, $body['data']);
$this->assertArrayHasKey('id', $body['data'][$index]);
$this->assertEquals($tag->id, $body['data'][$index]['id']);
$this->assertArrayHasKey('name', $body['data'][$index]);
$this->assertEquals($tag->name, $body['data'][$index]['name']);
}
}

public function testListCommonReturnCommonTagsOrderedByChargesCount(): void
{
$auth = $this->makeAuth($user = $this->userFactory->create());
$friend = $this->userFactory->create();

[$tag1, $tag2, $tag3] = $this->tagFactory->forUser($user)->createMany(3)->toArray();
$commonTag = $this->tagFactory->forUser($user)->create();

$wallet = WalletFactory::make();
$wallet->users->add($user);
$wallet->users->add($friend);
$wallet = $this->walletFactory->create($wallet);

$this->chargeFactory->forWallet($wallet)->forUser($friend)->withTags([$commonTag])->createMany(2);
$this->chargeFactory->forWallet($wallet)->forUser($user)->withTags([$tag1])->createMany(3);
$this->chargeFactory->forWallet($wallet)->forUser($user)->withTags([$tag2])->create();

$response = $this->withAuth($auth)->get('/tags/common');

$response->assertOk();

$body = $this->getJsonResponseBody($response);

$this->assertIsArray($body);
$this->assertArrayHasKey('data', $body);
$this->assertCount(4, $body['data']);

foreach ([$tag1, $commonTag, $tag2, $tag3] as $index => $tag) {
$this->assertArrayHasKey($index, $body['data']);
$this->assertArrayHasKey('id', $body['data'][$index]);
$this->assertEquals($tag->id, $body['data'][$index]['id']);
$this->assertArrayHasKey('name', $body['data'][$index]);
$this->assertEquals($tag->name, $body['data'][$index]['name']);
}
}

public function testCreateRequireAuth(): void
{
$response = $this->post('/tags');
Expand Down
18 changes: 18 additions & 0 deletions tests/Feature/Database/TagChargeTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace Tests\Feature\Database;

use App\Database\TagCharge;
use Tests\TestCase;

class TagChargeTest extends TestCase
{
public function testCreateEntity(): void
{
$entity = new TagCharge();

$this->assertNotNull($entity);
}
}

0 comments on commit 6085fee

Please sign in to comment.