Skip to content

Commit ca2388e

Browse files
authored
Merge pull request #902 from HiEventsDev/develop
2 parents 8846da2 + eb43fb2 commit ca2388e

Some content is hidden

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

61 files changed

+4315
-886
lines changed

backend/app/DomainObjects/AttendeeDomainObject.php

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

1010
class AttendeeDomainObject extends Generated\AttendeeDomainObjectAbstract implements IsSortable, IsFilterable
1111
{
12+
public const TICKET_NAME_SORT_KEY = 'ticket_name';
13+
1214
private ?OrderDomainObject $order = null;
1315

1416
private ?ProductDomainObject $product = null;
@@ -30,6 +32,10 @@ public static function getAllowedSorts(): AllowedSorts
3032
{
3133
return new AllowedSorts(
3234
[
35+
self::TICKET_NAME_SORT_KEY => [
36+
'asc' => __('Ticket Name A-Z'),
37+
'desc' => __('Ticket Name Z-A'),
38+
],
3339
self::CREATED_AT => [
3440
'asc' => __('Older First'),
3541
'desc' => __('Newest First'),
@@ -64,6 +70,7 @@ public static function getAllowedFilterFields(): array
6470
return [
6571
self::STATUS,
6672
self::PRODUCT_ID,
73+
self::PRODUCT_PRICE_ID,
6774
];
6875
}
6976

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/Attendees/GetAttendeesAction.php

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,38 +4,29 @@
44

55
use HiEvents\DomainObjects\AttendeeDomainObject;
66
use HiEvents\DomainObjects\EventDomainObject;
7-
use HiEvents\DomainObjects\OrderDomainObject;
87
use HiEvents\Http\Actions\BaseAction;
98
use HiEvents\Http\DTO\QueryParamsDTO;
10-
use HiEvents\Repository\Eloquent\Value\Relationship;
11-
use HiEvents\Repository\Interfaces\AttendeeRepositoryInterface;
129
use HiEvents\Resources\Attendee\AttendeeResource;
10+
use HiEvents\Services\Application\Handlers\Attendee\GetAttendeesHandler;
1311
use Illuminate\Http\JsonResponse;
1412
use Illuminate\Http\Request;
1513

16-
/**
17-
* @todo move to handler
18-
* @todo - add validation for filter fields
19-
*/
2014
class GetAttendeesAction extends BaseAction
2115
{
22-
private AttendeeRepositoryInterface $attendeeRepository;
23-
24-
public function __construct(AttendeeRepositoryInterface $attendeeRepository)
16+
public function __construct(
17+
private readonly GetAttendeesHandler $getAttendeesHandler,
18+
)
2519
{
26-
$this->attendeeRepository = $attendeeRepository;
2720
}
2821

2922
public function __invoke(int $eventId, Request $request): JsonResponse
3023
{
3124
$this->isActionAuthorized($eventId, EventDomainObject::class);
3225

33-
$attendees = $this->attendeeRepository
34-
->loadRelation(new Relationship(
35-
domainObject: OrderDomainObject::class,
36-
name: 'order'
37-
))
38-
->findByEventId($eventId, QueryParamsDTO::fromArray($request->query->all()));
26+
$attendees = $this->getAttendeesHandler->handle(
27+
eventId: $eventId,
28+
queryParams: QueryParamsDTO::fromArray($request->query->all())
29+
);
3930

4031
return $this->filterableResourceResponse(
4132
resource: AttendeeResource::class,

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/AttendeeRepository.php

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,22 @@ public function findByEventId(int $eventId, QueryParamsDTO $params): LengthAware
7676

7777
$this->model = $this->model->select('attendees.*')
7878
->join('orders', 'orders.id', '=', 'attendees.order_id')
79-
->whereIn('orders.status', [OrderStatus::COMPLETED->name, OrderStatus::CANCELLED->name, OrderStatus::AWAITING_OFFLINE_PAYMENT->name])
80-
->orderBy(
81-
'attendees.' . ($params->sort_by ?? AttendeeDomainObject::getDefaultSort()),
82-
$params->sort_direction ?? 'desc',
83-
);
79+
->whereIn('orders.status', [OrderStatus::COMPLETED->name, OrderStatus::CANCELLED->name, OrderStatus::AWAITING_OFFLINE_PAYMENT->name]);
80+
81+
if ($params->filter_fields && $params->filter_fields->isNotEmpty()) {
82+
$this->applyFilterFields($params, AttendeeDomainObject::getAllowedFilterFields(), prefix: 'attendees');
83+
}
84+
85+
$sortBy = $params->sort_by ?? AttendeeDomainObject::getDefaultSort();
86+
$sortDirection = $params->sort_direction ?? AttendeeDomainObject::getDefaultSortDirection();
87+
88+
if ($sortBy === AttendeeDomainObject::TICKET_NAME_SORT_KEY) {
89+
$this->model = $this->model
90+
->leftJoin('products', 'products.id', '=', 'attendees.product_id')
91+
->orderBy('products.title', $sortDirection);
92+
} else {
93+
$this->model = $this->model->orderBy('attendees.' . $sortBy, $sortDirection);
94+
}
8495

8596
return $this->paginateWhere(
8697
where: $where,

backend/app/Repository/Eloquent/BaseRepository.php

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -387,10 +387,14 @@ protected function handleSingleResult(
387387
return $this->hydrateDomainObjectFromModel($model, $domainObjectOverride);
388388
}
389389

390-
protected function applyFilterFields(QueryParamsDTO $params, array $allowedFilterFields = []): void
390+
protected function applyFilterFields(
391+
QueryParamsDTO $params,
392+
array $allowedFilterFields = [],
393+
?string $prefix = null,
394+
): void
391395
{
392396
if ($params->filter_fields && $params->filter_fields->isNotEmpty()) {
393-
$params->filter_fields->each(function ($filterField) use ($allowedFilterFields) {
397+
$params->filter_fields->each(function ($filterField) use ($prefix, $allowedFilterFields) {
394398
if (!in_array($filterField->field, $allowedFilterFields, true)) {
395399
return;
396400
}
@@ -412,6 +416,8 @@ protected function applyFilterFields(QueryParamsDTO $params, array $allowedFilte
412416
sprintf('Operator %s is not supported', $filterField->operator)
413417
);
414418

419+
$field = $prefix ? $prefix . '.' . $filterField->field : $filterField->field;
420+
415421
// Special handling for IN operator
416422
if ($operator === 'IN') {
417423
// Ensure value is array or convert comma-separated string to array
@@ -420,12 +426,12 @@ protected function applyFilterFields(QueryParamsDTO $params, array $allowedFilte
420426
: explode(',', $filterField->value);
421427

422428
$this->model = $this->model->whereIn(
423-
column: $filterField->field,
429+
column: $field,
424430
values: $value
425431
);
426432
} else {
427433
$this->model = $this->model->where(
428-
column: $filterField->field,
434+
column: $field,
429435
operator: $operator,
430436
value: $isNull ? null : $filterField->value,
431437
);

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+
}

0 commit comments

Comments
 (0)