Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions backend/app/Exceptions/ResourceNotFoundException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace HiEvents\Exceptions;

use Exception;

class ResourceNotFoundException extends Exception
{
}
15 changes: 15 additions & 0 deletions backend/app/Http/Actions/BaseAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,21 @@ protected function jsonResponse(
return new JsonResponse($data, $statusCode);
}

protected function xmlResponse(
string $xmlContent,
int $statusCode = ResponseCodes::HTTP_OK,
array $headers = [],
): LaravelResponse
{
$defaultHeaders = [
'Content-Type' => 'application/xml',
];

$allHeaders = array_merge($defaultHeaders, $headers);

return Response::make($xmlContent, $statusCode, $allHeaders);
}

protected function isActionAuthorized(
int $entityId,
string $entityType,
Expand Down
39 changes: 39 additions & 0 deletions backend/app/Http/Actions/Sitemap/GetSitemapEventsAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace HiEvents\Http\Actions\Sitemap;

use HiEvents\Exceptions\ResourceNotFoundException;
use HiEvents\Http\Actions\BaseAction;
use HiEvents\Services\Application\Handlers\Sitemap\GetSitemapEventsHandler;
use Illuminate\Http\Response;

class GetSitemapEventsAction extends BaseAction
{
private const CONTENT_TYPE_XML = 'application/xml';

public function __construct(
private readonly GetSitemapEventsHandler $handler,
)
{
}

public function __invoke(int $page): Response
{
try {
$xml = $this->handler->handle($page);
$cacheTtl = (int) config('sitemap.cache_ttl');

return $this->xmlResponse(
xmlContent: $xml,
headers: [
'Content-Type' => self::CONTENT_TYPE_XML,
'Cache-Control' => "public, max-age=$cacheTtl",
]
);
} catch (ResourceNotFoundException) {
return $this->notFoundResponse();
}
}
}
33 changes: 33 additions & 0 deletions backend/app/Http/Actions/Sitemap/GetSitemapIndexAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace HiEvents\Http\Actions\Sitemap;

use HiEvents\Http\Actions\BaseAction;
use HiEvents\Services\Application\Handlers\Sitemap\GetSitemapIndexHandler;
use Illuminate\Http\Response;

class GetSitemapIndexAction extends BaseAction
{
private const CONTENT_TYPE_XML = 'application/xml';

public function __construct(
private readonly GetSitemapIndexHandler $handler,
)
{
}

public function __invoke(): Response
{
$xml = $this->handler->handle();
$cacheTtl = (int)config('sitemap.cache_ttl');

return $this->xmlResponse(
xmlContent: $xml,
headers: [
'Content-Type' => self::CONTENT_TYPE_XML,
'Cache-Control' => "public, max-age=$cacheTtl",
]);
}
}
38 changes: 38 additions & 0 deletions backend/app/Http/Actions/Sitemap/GetSitemapOrganizersAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace HiEvents\Http\Actions\Sitemap;

use HiEvents\Exceptions\ResourceNotFoundException;
use HiEvents\Http\Actions\BaseAction;
use HiEvents\Services\Application\Handlers\Sitemap\GetSitemapOrganizersHandler;
use Illuminate\Http\Response;

class GetSitemapOrganizersAction extends BaseAction
{
private const CONTENT_TYPE_XML = 'application/xml';

public function __construct(
private readonly GetSitemapOrganizersHandler $handler,
) {
}

public function __invoke(int $page): Response
{
try {
$xml = $this->handler->handle($page);
$cacheTtl = (int) config('sitemap.cache_ttl');

return $this->xmlResponse(
xmlContent: $xml,
headers: [
'Content-Type' => self::CONTENT_TYPE_XML,
'Cache-Control' => "public, max-age=$cacheTtl",
]
);
} catch (ResourceNotFoundException) {
return $this->notFoundResponse();
}
}
}
14 changes: 14 additions & 0 deletions backend/app/Http/DTO/GetSitemapEventsDTO.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace HiEvents\Http\DTO;

use HiEvents\DataTransferObjects\BaseDataObject;

class GetSitemapEventsDTO extends BaseDataObject
{
public function __construct(
public int $page,
)
{
}
}
29 changes: 29 additions & 0 deletions backend/app/Repository/Eloquent/EventRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use HiEvents\DomainObjects\EventDomainObject;
use HiEvents\DomainObjects\EventStatisticDomainObject;
use HiEvents\DomainObjects\Generated\EventDomainObjectAbstract;
use HiEvents\DomainObjects\Generated\EventSettingDomainObjectAbstract;
use HiEvents\DomainObjects\OrganizerDomainObject;
use HiEvents\DomainObjects\Status\EventStatus;
use HiEvents\Http\DTO\QueryParamsDTO;
Expand Down Expand Up @@ -136,4 +137,32 @@ public function getAllEventsForAdmin(

return $this->paginate($perPage);
}

public function getSitemapEvents(int $page, int $perPage): LengthAwarePaginator
{
return $this->handleResults($this->model
->select([
'events.' . EventDomainObjectAbstract::ID,
'events.' . EventDomainObjectAbstract::TITLE,
'events.' . EventDomainObjectAbstract::UPDATED_AT,
'events.' . EventDomainObjectAbstract::START_DATE,
])
->join('event_settings', 'events.id', '=', 'event_settings.event_id')
->where('events.' . EventDomainObjectAbstract::STATUS, EventStatus::LIVE->name)
->where('event_settings.' . EventSettingDomainObjectAbstract::ALLOW_SEARCH_ENGINE_INDEXING, true)
->whereNull('events.' . EventDomainObjectAbstract::DELETED_AT)
->orderBy('events.' . EventDomainObjectAbstract::ID)
->paginate($perPage, ['*'], 'page', $page));
}

public function getSitemapEventCount(): int
{
return $this->model
->newQuery()
->join('event_settings', 'events.id', '=', 'event_settings.event_id')
->where('events.' . EventDomainObjectAbstract::STATUS, EventStatus::LIVE->name)
->where('event_settings.' . EventSettingDomainObjectAbstract::ALLOW_SEARCH_ENGINE_INDEXING, true)
->whereNull('events.' . EventDomainObjectAbstract::DELETED_AT)
->count();
}
}
31 changes: 31 additions & 0 deletions backend/app/Repository/Eloquent/OrganizerRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@

namespace HiEvents\Repository\Eloquent;

use HiEvents\DomainObjects\Generated\OrganizerDomainObjectAbstract;
use HiEvents\DomainObjects\Generated\OrganizerSettingDomainObjectAbstract;
use HiEvents\DomainObjects\OrganizerDomainObject;
use HiEvents\DomainObjects\Status\OrganizerStatus;
use HiEvents\Models\Organizer;
use HiEvents\Repository\DTO\Organizer\OrganizerStatsResponseDTO;
use HiEvents\Repository\Interfaces\OrganizerRepositoryInterface;
use Illuminate\Pagination\LengthAwarePaginator;

class OrganizerRepository extends BaseRepository implements OrganizerRepositoryInterface
{
Expand All @@ -21,6 +25,33 @@ public function getDomainObject(): string
return OrganizerDomainObject::class;
}

public function getSitemapOrganizers(int $page, int $perPage): LengthAwarePaginator
{
return $this->handleResults($this->model
->select([
'organizers.' . OrganizerDomainObjectAbstract::ID,
'organizers.' . OrganizerDomainObjectAbstract::NAME,
'organizers.' . OrganizerDomainObjectAbstract::UPDATED_AT,
])
->join('organizer_settings', 'organizers.id', '=', 'organizer_settings.organizer_id')
->where('organizers.' . OrganizerDomainObjectAbstract::STATUS, OrganizerStatus::LIVE->name)
->where('organizer_settings.' . OrganizerSettingDomainObjectAbstract::ALLOW_SEARCH_ENGINE_INDEXING, true)
->whereNull('organizers.' . OrganizerDomainObjectAbstract::DELETED_AT)
->orderBy('organizers.' . OrganizerDomainObjectAbstract::ID)
->paginate($perPage, ['*'], 'page', $page));
}

public function getSitemapOrganizerCount(): int
{
return $this->model
->newQuery()
->join('organizer_settings', 'organizers.id', '=', 'organizer_settings.organizer_id')
->where('organizers.' . OrganizerDomainObjectAbstract::STATUS, OrganizerStatus::LIVE->name)
->where('organizer_settings.' . OrganizerSettingDomainObjectAbstract::ALLOW_SEARCH_ENGINE_INDEXING, true)
->whereNull('organizers.' . OrganizerDomainObjectAbstract::DELETED_AT)
->count();
}

public function getOrganizerStats(int $organizerId, int $accountId, string $currencyCode): OrganizerStatsResponseDTO
{
$totalsQuery = <<<SQL
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,8 @@ public function getAllEventsForAdmin(
?string $sortBy = 'start_date',
?string $sortDirection = 'desc'
): LengthAwarePaginator;

public function getSitemapEvents(int $page, int $perPage): LengthAwarePaginator;

public function getSitemapEventCount(): int;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,16 @@
use HiEvents\DomainObjects\OrganizerDomainObject;
use HiEvents\Repository\DTO\Organizer\OrganizerStatsResponseDTO;
use HiEvents\Repository\Eloquent\BaseRepository;
use Illuminate\Pagination\LengthAwarePaginator;

/**
* @extends BaseRepository<OrganizerDomainObject>
*/
interface OrganizerRepositoryInterface extends RepositoryInterface
{
public function getOrganizerStats(int $organizerId, int $accountId, string $currencyCode): OrganizerStatsResponseDTO;

public function getSitemapOrganizers(int $page, int $perPage): LengthAwarePaginator;

public function getSitemapOrganizerCount(): int;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

declare(strict_types=1);

namespace HiEvents\Services\Application\Handlers\Sitemap;

use HiEvents\Exceptions\ResourceNotFoundException;
use HiEvents\Repository\Interfaces\EventRepositoryInterface;
use HiEvents\Services\Domain\Sitemap\SitemapGeneratorService;
use Illuminate\Support\Facades\Cache;

class GetSitemapEventsHandler
{
private const CACHE_KEY_PREFIX = 'sitemap:events:';
private const MIN_PAGE = 1;

public function __construct(
private readonly EventRepositoryInterface $eventRepository,
private readonly SitemapGeneratorService $sitemapGenerator,
)
{
}

public function handle(int $page): string
{
if ($page < self::MIN_PAGE) {
throw new ResourceNotFoundException(__('Page must be a positive integer'));
}

$eventsPerPage = (int) config('sitemap.events_per_page');
$totalEvents = $this->eventRepository->getSitemapEventCount();
$totalPages = $this->calculateTotalPages($totalEvents, $eventsPerPage);

if ($page > $totalPages) {
throw new ResourceNotFoundException(__('Page not found'));
}

$cacheTtl = (int) config('sitemap.cache_ttl');
$cacheKey = self::CACHE_KEY_PREFIX . $page;

return Cache::remember($cacheKey, $cacheTtl, function () use ($page, $eventsPerPage): string {
$events = $this->eventRepository->getSitemapEvents($page, $eventsPerPage);
$baseUrl = rtrim((string) config('app.frontend_url'), '/');

return $this->sitemapGenerator->generateEventsSitemap(
$events->getCollection(),
$baseUrl
);
});
}

private function calculateTotalPages(int $totalEvents, int $eventsPerPage): int
{
return max(self::MIN_PAGE, (int) ceil($totalEvents / $eventsPerPage));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

declare(strict_types=1);

namespace HiEvents\Services\Application\Handlers\Sitemap;

use HiEvents\Repository\Interfaces\EventRepositoryInterface;
use HiEvents\Repository\Interfaces\OrganizerRepositoryInterface;
use HiEvents\Services\Domain\Sitemap\SitemapGeneratorService;
use Illuminate\Support\Facades\Cache;

class GetSitemapIndexHandler
{
private const CACHE_KEY = 'sitemap:index';
private const MIN_PAGES = 1;

public function __construct(
private readonly EventRepositoryInterface $eventRepository,
private readonly OrganizerRepositoryInterface $organizerRepository,
private readonly SitemapGeneratorService $sitemapGenerator,
) {
}

public function handle(): string
{
$cacheTtl = (int) config('sitemap.cache_ttl');

return Cache::remember(self::CACHE_KEY, $cacheTtl, function (): string {
$eventsPerPage = (int) config('sitemap.events_per_page');
$organizersPerPage = (int) config('sitemap.organizers_per_page');

$totalEvents = $this->eventRepository->getSitemapEventCount();
$totalOrganizers = $this->organizerRepository->getSitemapOrganizerCount();

$totalEventPages = $this->calculateTotalPages($totalEvents, $eventsPerPage);
$totalOrganizerPages = $this->calculateTotalPages($totalOrganizers, $organizersPerPage);

$baseUrl = rtrim((string) config('app.frontend_url'), '/');
$lastMod = now()->toAtomString();

return $this->sitemapGenerator->generateSitemapIndex(
$totalEventPages,
$totalOrganizerPages,
$baseUrl,
$lastMod
);
});
}

private function calculateTotalPages(int $total, int $perPage): int
{
return max(self::MIN_PAGES, (int) ceil($total / $perPage));
}
}
Loading
Loading