Skip to content

Commit 87918a2

Browse files
authored
Feature: Add admin events page (#897)
1 parent f7a4998 commit 87918a2

File tree

46 files changed

+1843
-718
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+1843
-718
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace HiEvents\Http\Actions\Admin\Events;
6+
7+
use HiEvents\DomainObjects\Enums\Role;
8+
use HiEvents\Http\Actions\BaseAction;
9+
use HiEvents\Resources\Event\AdminEventResource;
10+
use HiEvents\Services\Application\Handlers\Admin\DTO\GetAllEventsDTO;
11+
use HiEvents\Services\Application\Handlers\Admin\GetAllEventsHandler;
12+
use Illuminate\Http\JsonResponse;
13+
use Illuminate\Http\Request;
14+
15+
class GetAllEventsAction extends BaseAction
16+
{
17+
public function __construct(
18+
private readonly GetAllEventsHandler $handler,
19+
)
20+
{
21+
}
22+
23+
public function __invoke(Request $request): JsonResponse
24+
{
25+
$this->minimumAllowedRole(Role::SUPERADMIN);
26+
27+
$events = $this->handler->handle(new GetAllEventsDTO(
28+
perPage: min((int)$request->query('per_page', 20), 100),
29+
search: $request->query('search'),
30+
sortBy: $request->query('sort_by', 'start_date'),
31+
sortDirection: $request->query('sort_direction', 'desc'),
32+
));
33+
34+
return $this->resourceResponse(
35+
resource: AdminEventResource::class,
36+
data: $events
37+
);
38+
}
39+
}

backend/app/Http/Actions/BaseAction.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,23 @@ protected function getAuthenticatedAccountId(): int
177177
throw new UnauthorizedException();
178178
}
179179

180+
protected function getAuthenticatedUserRole(): Role
181+
{
182+
if (Auth::check()) {
183+
/** @var AuthUserService $service */
184+
$service = app(AuthUserService::class);
185+
$role = $service->getAuthenticatedUserRole();
186+
187+
if ($role === null) {
188+
throw new UnauthorizedException(__('No user role found in token'));
189+
}
190+
191+
return $role;
192+
}
193+
194+
throw new UnauthorizedException();
195+
}
196+
180197
protected function getAuthenticatedUser(): UserDomainObject|DomainObjectInterface
181198
{
182199
if (Auth::check()) {

backend/app/Http/Actions/Events/GetEventPublicAction.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace HiEvents\Http\Actions\Events;
44

5+
use HiEvents\DomainObjects\Enums\Role;
56
use HiEvents\DomainObjects\EventDomainObject;
67
use HiEvents\DomainObjects\Status\EventStatus;
78
use HiEvents\Http\Actions\BaseAction;
@@ -52,6 +53,14 @@ private function canUserViewEvent(EventDomainObject $event): bool
5253
return true;
5354
}
5455

56+
if ($this->isUserAuthenticated() && $this->getAuthenticatedUserRole() === Role::SUPERADMIN) {
57+
$this->logger->debug(__('Superadmin user is viewing non-live event with ID :eventId', [
58+
'eventId' => $event->getId(),
59+
'accountId' => $this->getAuthenticatedAccountId(),
60+
]));
61+
return true;
62+
}
63+
5564
return false;
5665
}
5766
}

backend/app/Repository/Eloquent/EventRepository.php

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,15 @@
44

55
namespace HiEvents\Repository\Eloquent;
66

7+
use HiEvents\DomainObjects\AccountDomainObject;
78
use HiEvents\DomainObjects\EventDomainObject;
9+
use HiEvents\DomainObjects\EventStatisticDomainObject;
810
use HiEvents\DomainObjects\Generated\EventDomainObjectAbstract;
11+
use HiEvents\DomainObjects\OrganizerDomainObject;
912
use HiEvents\DomainObjects\Status\EventStatus;
1013
use HiEvents\Http\DTO\QueryParamsDTO;
1114
use HiEvents\Models\Event;
15+
use HiEvents\Repository\Eloquent\Value\Relationship;
1216
use HiEvents\Repository\Interfaces\EventRepositoryInterface;
1317
use Illuminate\Database\Eloquent\Builder;
1418
use Illuminate\Pagination\LengthAwarePaginator;
@@ -96,9 +100,40 @@ public function getUpcomingEventsForAdmin(int $perPage): LengthAwarePaginator
96100
->where(EventDomainObjectAbstract::START_DATE, '<=', $next24Hours)
97101
->whereIn(EventDomainObjectAbstract::STATUS, [
98102
EventStatus::LIVE->name,
99-
EventStatus::DRAFT->name,
100103
])
101104
->orderBy(EventDomainObjectAbstract::START_DATE, 'asc')
102105
->paginate($perPage));
103106
}
107+
108+
public function getAllEventsForAdmin(
109+
?string $search = null,
110+
int $perPage = 20,
111+
?string $sortBy = 'start_date',
112+
?string $sortDirection = 'desc'
113+
): LengthAwarePaginator {
114+
$this->model = $this->model
115+
->select('events.*')
116+
->withCount('attendees');
117+
118+
if ($search) {
119+
$this->model = $this->model->where(function ($q) use ($search) {
120+
$q->where(EventDomainObjectAbstract::TITLE, 'ilike', '%' . $search . '%')
121+
->orWhereHas('organizer', function ($orgQuery) use ($search) {
122+
$orgQuery->where('name', 'ilike', '%' . $search . '%');
123+
});
124+
});
125+
}
126+
127+
$allowedSortColumns = ['start_date', 'end_date', 'title', 'created_at'];
128+
$sortColumn = in_array($sortBy, $allowedSortColumns, true) ? $sortBy : 'start_date';
129+
$sortDir = in_array(strtolower($sortDirection), ['asc', 'desc']) ? $sortDirection : 'desc';
130+
131+
$this->model = $this->model->orderBy($sortColumn, $sortDir);
132+
133+
$this->loadRelation(new Relationship(OrganizerDomainObject::class, name: 'organizer'));
134+
$this->loadRelation(new Relationship(AccountDomainObject::class, name: 'account'));
135+
$this->loadRelation(new Relationship(EventStatisticDomainObject::class, name: 'event_statistics'));
136+
137+
return $this->paginate($perPage);
138+
}
104139
}

backend/app/Repository/Interfaces/EventRepositoryInterface.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,11 @@ public function findEventsForOrganizer(int $organizerId, int $accountId, QueryPa
1919
public function findEvents(array $where, QueryParamsDTO $params): LengthAwarePaginator;
2020

2121
public function getUpcomingEventsForAdmin(int $perPage): LengthAwarePaginator;
22+
23+
public function getAllEventsForAdmin(
24+
?string $search = null,
25+
int $perPage = 20,
26+
?string $sortBy = 'start_date',
27+
?string $sortDirection = 'desc'
28+
): LengthAwarePaginator;
2229
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
namespace HiEvents\Resources\Event;
4+
5+
use HiEvents\DomainObjects\EventDomainObject;
6+
use HiEvents\Resources\BaseResource;
7+
use Illuminate\Http\Request;
8+
9+
/**
10+
* @mixin EventDomainObject
11+
*/
12+
class AdminEventResource extends BaseResource
13+
{
14+
public function toArray(Request $request): array
15+
{
16+
$statistics = $this->getEventStatistics();
17+
18+
return [
19+
'id' => $this->getId(),
20+
'title' => $this->getTitle(),
21+
'start_date' => $this->getStartDate(),
22+
'end_date' => $this->getEndDate(),
23+
'status' => $this->getStatus(),
24+
'organizer_name' => $this->getOrganizer()?->getName(),
25+
'organizer_id' => $this->getOrganizerId(),
26+
'account_name' => $this->getAccount()?->getName(),
27+
'account_id' => $this->getAccountId(),
28+
'user_id' => $this->getUserId(),
29+
'slug' => $this->getSlug(),
30+
'statistics' => $statistics ? [
31+
'total_gross_sales' => $statistics->getSalesTotalGross(),
32+
'products_sold' => $statistics->getProductsSold(),
33+
'attendees_registered' => $statistics->getAttendeesRegistered(),
34+
'orders_created' => $statistics->getOrdersCreated(),
35+
'orders_cancelled' => $statistics->getOrdersCancelled(),
36+
] : null,
37+
];
38+
}
39+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace HiEvents\Services\Application\Handlers\Admin\DTO;
4+
5+
use HiEvents\DataTransferObjects\BaseDataObject;
6+
use HiEvents\DomainObjects\Generated\EventDomainObjectAbstract;
7+
8+
class GetAllEventsDTO extends BaseDataObject
9+
{
10+
public function __construct(
11+
public readonly int $perPage = 20,
12+
public readonly ?string $search = null,
13+
public readonly ?string $sortBy = EventDomainObjectAbstract::START_DATE,
14+
public readonly ?string $sortDirection = 'desc',
15+
)
16+
{
17+
}
18+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
namespace HiEvents\Services\Application\Handlers\Admin;
4+
5+
use HiEvents\Repository\Interfaces\EventRepositoryInterface;
6+
use HiEvents\Services\Application\Handlers\Admin\DTO\GetAllEventsDTO;
7+
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
8+
9+
class GetAllEventsHandler
10+
{
11+
public function __construct(
12+
private readonly EventRepositoryInterface $eventRepository,
13+
)
14+
{
15+
}
16+
17+
public function handle(GetAllEventsDTO $dto): LengthAwarePaginator
18+
{
19+
return $this->eventRepository->getAllEventsForAdmin(
20+
search: $dto->search,
21+
perPage: $dto->perPage,
22+
sortBy: $dto->sortBy,
23+
sortDirection: $dto->sortDirection,
24+
);
25+
}
26+
}

backend/app/Services/Domain/Auth/AuthUserService.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace HiEvents\Services\Domain\Auth;
44

5+
use Exception;
6+
use HiEvents\DomainObjects\Enums\Role;
57
use HiEvents\DomainObjects\Interfaces\DomainObjectInterface;
68
use HiEvents\DomainObjects\UserDomainObject;
79
use HiEvents\Models\User;
@@ -38,6 +40,26 @@ public function getAuthenticatedAccountId(): ?int
3840
return $payload->get('account_id');
3941
}
4042

43+
public function getAuthenticatedUserRole(): ?Role
44+
{
45+
if (!$this->authManager->check()) {
46+
return null;
47+
}
48+
49+
try {
50+
/** @var Payload $payload */
51+
$payload = $this->authManager->payload();
52+
} catch (JWTException) {
53+
return null;
54+
}
55+
56+
try {
57+
return Role::from($payload->get('role'));
58+
} catch (Exception) {
59+
return null;
60+
}
61+
}
62+
4163
public function getUser(): UserDomainObject|DomainObjectInterface|null
4264
{
4365
/** @var User $user */

backend/app/Services/Domain/Auth/LoginService.php

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use HiEvents\DomainObjects\AccountDomainObject;
66
use HiEvents\DomainObjects\AccountUserDomainObject;
7+
use HiEvents\DomainObjects\Enums\Role;
78
use HiEvents\DomainObjects\Status\UserStatus;
89
use HiEvents\DomainObjects\UserDomainObject;
910
use HiEvents\Exceptions\UnauthorizedException;
@@ -56,9 +57,17 @@ public function authenticate(string $email, string $password, ?int $requestedAcc
5657
$this->validateUserStatus($accountId, $userAccounts);
5758
}
5859

60+
$userRole = $this->getUserRole($accountId, $userAccounts);
61+
5962
return new LoginResponse(
6063
accounts: $accounts,
61-
token: $this->getToken($accounts, $email, $password, $requestedAccountId),
64+
token: $this->getToken(
65+
accounts: $accounts,
66+
email: $email,
67+
password: $password,
68+
requestedAccountId: $requestedAccountId,
69+
userRole: $userRole,
70+
),
6271
user: $user,
6372
accountId: $accountId,
6473
);
@@ -83,7 +92,13 @@ private function getAccountId(Collection $accounts, ?int $requestedAccountId): ?
8392
return null;
8493
}
8594

86-
private function getToken(Collection $accounts, string $email, string $password, ?int $requestedAccountId): ?string
95+
private function getToken(
96+
Collection $accounts,
97+
string $email,
98+
string $password,
99+
?int $requestedAccountId,
100+
?Role $userRole,
101+
): ?string
87102
{
88103
$accountId = $this->getAccountId($accounts, $requestedAccountId);
89104

@@ -92,9 +107,13 @@ private function getToken(Collection $accounts, string $email, string $password,
92107
return null;
93108
}
94109

95-
$token = $this->jwtAuth->claims([
96-
'account_id' => $accountId,
97-
])->attempt([
110+
$claims = ['account_id' => $accountId];
111+
112+
if ($userRole !== null) {
113+
$claims['role'] = $userRole->value;
114+
}
115+
116+
$token = $this->jwtAuth->claims($claims)->attempt([
98117
'email' => strtolower($email),
99118
'password' => $password,
100119
]);
@@ -118,4 +137,17 @@ private function validateUserStatus(int $accountId, Collection $userAccounts): v
118137
throw new UnauthorizedException(__('User account is not active'));
119138
}
120139
}
140+
141+
private function getUserRole(?int $accountId, Collection $userAccounts): ?Role
142+
{
143+
if ($accountId === null) {
144+
return null;
145+
}
146+
147+
/** @var AccountUserDomainObject $currentAccount */
148+
$currentAccount = $userAccounts
149+
->first(fn(AccountUserDomainObject $userAccount) => $userAccount->getAccountId() === $accountId);
150+
151+
return Role::from($currentAccount?->getRole());
152+
}
121153
}

0 commit comments

Comments
 (0)