diff --git a/app/src/Controller/Tags/ChargesController.php b/app/src/Controller/Tags/ChargesController.php index 77fc24a..6ce487e 100644 --- a/app/src/Controller/Tags/ChargesController.php +++ b/app/src/Controller/Tags/ChargesController.php @@ -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; @@ -27,6 +29,8 @@ public function __construct( private PaginationFactory $paginationFactory, private ChargesView $chargesView, private ChargeWalletService $chargeWalletService, + private UserRepository $userRepository, + private CurrencyView $currencyView, ) { parent::__construct($auth); } @@ -34,7 +38,7 @@ public function __construct( #[Route(route: '/tags//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); } @@ -49,7 +53,7 @@ public function list(int $id): ResponseInterface #[Route(route: '/tags//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); } @@ -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()), ], ]); } diff --git a/app/src/Controller/Tags/TagsController.php b/app/src/Controller/Tags/TagsController.php index e8f9f66..9df9694 100644 --- a/app/src/Controller/Tags/TagsController.php +++ b/app/src/Controller/Tags/TagsController.php @@ -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; @@ -28,6 +30,7 @@ public function __construct( private TagService $tagService, private TagsView $tagsView, private TagView $tagView, + private UserRepository $userRepository, ) { parent::__construct($auth); } @@ -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 { diff --git a/app/src/Controller/Wallets/TagsController.php b/app/src/Controller/Wallets/TagsController.php index a9df515..c486ebb 100644 --- a/app/src/Controller/Wallets/TagsController.php +++ b/app/src/Controller/Wallets/TagsController.php @@ -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); } diff --git a/app/src/Repository/ChargeRepository.php b/app/src/Repository/ChargeRepository.php index efeb5e6..af83f39 100644 --- a/app/src/Repository/ChargeRepository.php +++ b/app/src/Repository/ChargeRepository.php @@ -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) @@ -70,6 +71,7 @@ public function findByTagIdWithPagination(int $tagId) { $query = $this->select() ->load('user') + ->load('tags') ->where('tags.id', $tagId) ->orderBy('created_at', 'DESC'); diff --git a/app/src/Repository/TagRepository.php b/app/src/Repository/TagRepository.php index a4a5936..262fbd1 100644 --- a/app/src/Repository/TagRepository.php +++ b/app/src/Repository/TagRepository.php @@ -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 $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 $ids * @param array $userIDs diff --git a/app/src/Repository/UserRepository.php b/app/src/Repository/UserRepository.php index f8d13dd..8bb28bd 100644 --- a/app/src/Repository/UserRepository.php +++ b/app/src/Repository/UserRepository.php @@ -47,6 +47,33 @@ public function findByCommonWallets(User $user): array ->fetchAll(); } + /** + * @param \App\Database\User $user + * @return array + */ + 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 diff --git a/tests/Feature/Controller/Tags/TagsControllerTest.php b/tests/Feature/Controller/Tags/TagsControllerTest.php index a0314ad..e0d5627 100644 --- a/tests/Feature/Controller/Tags/TagsControllerTest.php +++ b/tests/Feature/Controller/Tags/TagsControllerTest.php @@ -7,8 +7,10 @@ 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; @@ -16,6 +18,10 @@ class TagsControllerTest extends TestCase implements DatabaseTransaction { protected UserFactory $userFactory; + protected WalletFactory $walletFactory; + + protected ChargeFactory $chargeFactory; + protected TagFactory $tagFactory; protected function setUp(): void @@ -23,6 +29,8 @@ 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); } @@ -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'); diff --git a/tests/Feature/Database/TagChargeTest.php b/tests/Feature/Database/TagChargeTest.php new file mode 100644 index 0000000..d33492b --- /dev/null +++ b/tests/Feature/Database/TagChargeTest.php @@ -0,0 +1,18 @@ +assertNotNull($entity); + } +}