Skip to content
Open
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
5 changes: 5 additions & 0 deletions app/modules/Events/Domain/EventRepositoryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Modules\Events\Domain;

use Cycle\ORM\Select;
use Cycle\ORM\RepositoryInterface;

/**
Expand All @@ -14,8 +15,12 @@ interface EventRepositoryInterface extends RepositoryInterface
{
public function findAll(array $scope = [], array $orderBy = [], int $limit = 30, int $offset = 0): iterable;

public function select(): Select;

public function countAll(array $scope = []): int;

public function countByType(array $scope = []): array;

public function store(Event $event): bool;

public function deleteAll(array $scope = []): void;
Expand Down
15 changes: 15 additions & 0 deletions app/modules/Events/Integration/CycleOrm/EventRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Modules\Events\Integration\CycleOrm;

use Cycle\Database\DatabaseInterface;
use Cycle\Database\Injection\Fragment;
use Cycle\ORM\EntityManagerInterface;
use Cycle\ORM\Select;
use Cycle\ORM\Select\Repository;
Expand Down Expand Up @@ -61,6 +62,20 @@ public function countAll(array $scope = []): int
->count();
}

public function countByType(array $scope = []): array
{
return $this->db
->select()
->from(Event::TABLE_NAME)
->columns([
Event::TYPE,
new Fragment('COUNT(*) AS cnt'),
])
->where($this->buildScope($scope))
->groupBy(Event::TYPE)
->fetchAll();
}

public function findAll(array $scope = [], array $orderBy = [], int $limit = 30, int $offset = 0): iterable
{
return $this->select()
Expand Down
53 changes: 42 additions & 11 deletions app/modules/Events/Interfaces/Http/Controllers/ListAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,20 @@

namespace Modules\Events\Interfaces\Http\Controllers;

use App\Application\Commands\FindEvents;
use App\Application\Commands\FindEventsCursor;
use App\Application\HTTP\Response\ErrorResource;
use Modules\Events\Interfaces\Http\Request\EventsRequest;
use Modules\Events\Interfaces\Http\Resources\EventCollection;
use Modules\Events\Interfaces\Http\Resources\EventCursorCollection;
use Modules\Events\Interfaces\Http\Resources\EventResource;
use Spiral\Cqrs\QueryBusInterface;
use Modules\Events\Interfaces\Queries\EventsCursorResult;
use OpenApi\Attributes as OA;
use Spiral\Cqrs\QueryBusInterface;
use Spiral\Http\Request\InputManager;
use Spiral\Router\Annotation\Route;

#[OA\Get(
path: '/api/events',
description: 'Retrieve all events',
description: 'Retrieve events with cursor pagination. Uses a composite cursor (timestamp + uuid) ordered by timestamp DESC, uuid DESC. Use meta.next_cursor as the cursor parameter to fetch the next page; meta.has_more indicates more data.',
tags: ['Events'],
parameters: [
new OA\QueryParameter(
Expand All @@ -26,7 +28,19 @@
),
new OA\QueryParameter(
name: 'project',
description: 'Filter by event type',
description: 'Filter by event project',
required: false,
schema: new OA\Schema(type: 'string'),
),
new OA\QueryParameter(
name: 'limit',
description: 'Page size (default 100, max 100)',
required: false,
schema: new OA\Schema(type: 'integer', minimum: 1, maximum: 100),
),
new OA\QueryParameter(
name: 'cursor',
description: 'Opaque composite cursor (timestamp + uuid) from meta.next_cursor of the previous response',
required: false,
schema: new OA\Schema(type: 'string'),
),
Expand All @@ -46,7 +60,11 @@
),
new OA\Property(
property: 'meta',
ref: '#/components/schemas/ResponseMeta',
properties: [
new OA\Property(property: 'limit', type: 'integer'),
new OA\Property(property: 'has_more', type: 'boolean'),
new OA\Property(property: 'next_cursor', type: 'string', nullable: true),
],
type: 'object',
),
],
Expand All @@ -66,12 +84,25 @@
#[Route(route: 'events', name: 'events.list', methods: 'GET', group: 'api')]
public function __invoke(
EventsRequest $request,
InputManager $input,
QueryBusInterface $bus,
): EventCollection {
return new EventCollection(
$bus->ask(
new FindEvents(type: $request->type, project: $request->project),
),
): EventCursorCollection {
$limit = $input->query->get('limit');
$cursor = $input->query->get('cursor');

/** @var EventsCursorResult $result */
$result = $bus->ask(new FindEventsCursor(
type: $request->type,
project: $request->project,
limit: $limit,
cursor: $cursor,
));

return new EventCursorCollection(
$result->items,
$result->limit,
$result->hasMore,
$result->nextCursor,
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,21 @@

namespace Modules\Events\Interfaces\Http\Controllers;

use App\Application\Commands\FindEvents;
use App\Application\Commands\FindEventsCursor;
use App\Application\Event\EventTypeMapperInterface;
use App\Application\HTTP\Response\ErrorResource;
use Modules\Events\Interfaces\Http\Request\EventsRequest;
use Modules\Events\Interfaces\Http\Resources\EventPreviewCollection;
use Modules\Events\Interfaces\Http\Resources\EventPreviewCursorCollection;
use Modules\Events\Interfaces\Http\Resources\EventPreviewResource;
use Modules\Events\Interfaces\Queries\EventsCursorResult;
use Spiral\Cqrs\QueryBusInterface;
use Spiral\Router\Annotation\Route;
use OpenApi\Attributes as OA;
use Spiral\Http\Request\InputManager;

#[OA\Get(
path: '/api/events/preview',
description: 'Retrieve all events preview',
description: 'Retrieve event previews with cursor pagination. Uses a composite cursor (timestamp + uuid) ordered by timestamp DESC, uuid DESC. Use meta.next_cursor as the cursor parameter to fetch the next page; meta.has_more indicates more data.',
tags: ['Events'],
parameters: [
new OA\QueryParameter(
Expand All @@ -27,7 +29,19 @@
),
new OA\QueryParameter(
name: 'project',
description: 'Filter by event type',
description: 'Filter by event project',
required: false,
schema: new OA\Schema(type: 'string'),
),
new OA\QueryParameter(
name: 'limit',
description: 'Page size (default 100, max 100)',
required: false,
schema: new OA\Schema(type: 'integer', maximum: 100, minimum: 1),
),
new OA\QueryParameter(
name: 'cursor',
description: 'Opaque composite cursor (timestamp + uuid) from meta.next_cursor of the previous response',
required: false,
schema: new OA\Schema(type: 'string'),
),
Expand All @@ -47,7 +61,12 @@
),
new OA\Property(
property: 'meta',
ref: '#/components/schemas/ResponseMeta',
properties: [
new OA\Property(property: 'grid', type: 'array', items: new OA\Items()),
new OA\Property(property: 'limit', type: 'integer'),
new OA\Property(property: 'has_more', type: 'boolean'),
new OA\Property(property: 'next_cursor', type: 'string', nullable: true),
],
type: 'object',
),
],
Expand All @@ -67,14 +86,29 @@
#[Route(route: 'events/preview', name: 'events.preview.list', methods: 'GET', group: 'api')]
public function __invoke(
EventsRequest $request,
InputManager $input,
QueryBusInterface $bus,
EventTypeMapperInterface $mapper,
): EventPreviewCollection {
return new EventPreviewCollection(
$bus->ask(
new FindEvents(type: $request->type, project: $request->project),
): EventPreviewCursorCollection {
$limit = $input->query->get('limit');
$cursor = $input->query->get('cursor');

/** @var EventsCursorResult $result */
$result = $bus->ask(
new FindEventsCursor(
type: $request->type,
project: $request->project,
limit: $limit,
cursor: $cursor,
),
);

return new EventPreviewCursorCollection(
$result->items,
$mapper,
$result->limit,
$result->hasMore,
$result->nextCursor,
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php

declare(strict_types=1);

namespace Modules\Events\Interfaces\Http\Controllers;

use App\Application\Commands\CountEventsByType;
use App\Application\HTTP\Response\ErrorResource;
use Modules\Events\Interfaces\Http\Request\EventsRequest;
use Modules\Events\Interfaces\Http\Resources\EventTypeCountCollection;
use Modules\Events\Interfaces\Http\Resources\EventTypeCountResource;
use OpenApi\Attributes as OA;
use Spiral\Cqrs\QueryBusInterface;
use Spiral\Router\Annotation\Route;

#[OA\Get(
path: '/api/events/type-counts',
description: 'Retrieve event counts grouped by type',
tags: ['Events'],
parameters: [
new OA\QueryParameter(
name: 'type',
description: 'Filter by event type',
required: false,
schema: new OA\Schema(type: 'string'),
),
new OA\QueryParameter(
name: 'project',
description: 'Filter by event project',
required: false,
schema: new OA\Schema(type: 'string'),
),
],
responses: [
new OA\Response(
response: 200,
description: 'Success',
content: new OA\JsonContent(
properties: [
new OA\Property(
property: 'data',
type: 'array',
items: new OA\Items(
ref: EventTypeCountResource::class,
),
),
new OA\Property(
property: 'meta',
ref: '#/components/schemas/ResponseMeta',
type: 'object',
),
],
),
),
new OA\Response(
response: 404,
description: 'Not found',
content: new OA\JsonContent(
ref: ErrorResource::class,
),
),
],
)]
final readonly class TypeCountsAction
{
#[Route(route: 'events/type-counts', name: 'events.type-counts', methods: 'GET', group: 'api')]
public function __invoke(
EventsRequest $request,
QueryBusInterface $bus,
): EventTypeCountCollection {
return new EventTypeCountCollection(
$bus->ask(
new CountEventsByType(type: $request->type, project: $request->project),
),
);
}
}
Loading