diff --git a/src/App/Controller/GetCheepController.php b/src/App/Controller/GetCheepController.php index 20b1550..9ea7c13 100644 --- a/src/App/Controller/GetCheepController.php +++ b/src/App/Controller/GetCheepController.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace App\Controller; -use App\Dto\AuthorDto; use App\Dto\CheepDto; use Cheeper\Application\CheepApplicationService; use Nelmio\ApiDocBundle\Annotation\Model; @@ -26,9 +25,7 @@ final class GetCheepController extends AbstractController response: Response::HTTP_OK, description: "Retrieves a single cheep by ID", content: new OA\JsonContent( - oneOf: [ - new OA\Schema(ref: new Model(type: CheepDto::class),) - ] + ref: new Model(type: CheepDto::class) ) )] #[OA\Response( diff --git a/src/App/Controller/GetTimelineController.php b/src/App/Controller/GetTimelineController.php index d46787e..d3c9d84 100644 --- a/src/App/Controller/GetTimelineController.php +++ b/src/App/Controller/GetTimelineController.php @@ -6,11 +6,16 @@ namespace App\Controller; use App\Dto\CheepDto; use Cheeper\Application\CheepApplicationService; +use Cheeper\DomainModel\Author\AuthorDoesNotExist; use Cheeper\DomainModel\Cheep\Cheep; +use Nelmio\ApiDocBundle\Annotation\Model; +use Ramsey\Uuid\Uuid; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\Exception\BadRequestException; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; +use OpenApi\Attributes as OA; final class GetTimelineController extends AbstractController { @@ -22,14 +27,56 @@ final class GetTimelineController extends AbstractController } #[Route("/authors/{id}/timeline", methods: [Request::METHOD_GET])] + #[OA\Parameter( + name: "offset", + description: "The offset position where to start getting cheeps from", + in: "query", + required: false, + schema: new OA\Schema(type: "int") + )] + #[OA\Parameter( + name: "size", + description: "The total number of cheeps to get", + in: "query", + required: false, + schema: new OA\Schema(type: "int") + )] + #[OA\Response( + response: Response::HTTP_OK, + description: "Retrieves author timeline", + content: new OA\JsonContent( + type: "array", + items: new OA\Items( + ref: new Model(type: CheepDto::class) + ) + ) + )] + #[OA\Response( + response: Response::HTTP_BAD_REQUEST, + description: "When the author ID is not valid" + )] + #[OA\Response( + response: Response::HTTP_NOT_FOUND, + description: "When the author does not exist" + )] public function __invoke(string $id, Request $request): Response { + if (!Uuid::isValid($id)) { + throw new BadRequestException("Invalid author ID"); + } + $offset = $request->query->getInt('offset'); $size = $request->query->getInt('size', self::DEFAULT_TIMELINE_CHUNK_SIZE); + try { + $timeline = $this->cheepApplicationService->timelineFrom($id, $offset, $size); + } catch (AuthorDoesNotExist) { + throw $this->createNotFoundException(); + } + $cheeps = array_map( static fn(Cheep $c) => CheepDto::assembleFrom($c), - $this->cheepApplicationService->timelineFrom($id, $offset, $size) + $timeline ); return $this->json($cheeps); diff --git a/src/App/Controller/PostAuthorController.php b/src/App/Controller/PostAuthorController.php index b4cdd18..059c008 100644 --- a/src/App/Controller/PostAuthorController.php +++ b/src/App/Controller/PostAuthorController.php @@ -6,14 +6,18 @@ namespace App\Controller; use App\Dto\AuthorDto; use Cheeper\Application\AuthorApplicationService; +use Cheeper\DomainModel\Author\AuthorAlreadyExists; +use Nelmio\ApiDocBundle\Annotation\Model; use Ramsey\Uuid\Uuid; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Validator\Validator\ValidatorInterface; use Symfony\Component\Validator\Constraints as Assert; use function Safe\json_decode; +use OpenApi\Attributes as OA; final class PostAuthorController extends AbstractController { @@ -24,6 +28,61 @@ final class PostAuthorController extends AbstractController } #[Route("/authors", methods: [Request::METHOD_POST])] + #[OA\RequestBody( + required: true, + content: new OA\JsonContent( + properties: [ + new OA\Property(property: "username", type: "string", nullable: false), + new OA\Property(property: "email", type: "string", nullable: false), + new OA\Property(property: "name", type: "string", nullable: true), + new OA\Property(property: "biography", type: "string", nullable: true), + new OA\Property(property: "location", type: "string", nullable: true), + new OA\Property(property: "website", type: "string", nullable: true), + new OA\Property(property: "birth_date", type: "string", nullable: true), + ] + ) + )] + #[OA\Response( + response: Response::HTTP_CREATED, + description: "Creates a new author", + content: new OA\JsonContent( + ref: new Model(type: AuthorDto::class) + ) + )] + #[OA\Response( + response: Response::HTTP_BAD_REQUEST, + description: "When the data submitted is not valid", + content: new OA\JsonContent( + properties: [ + new OA\Property(property: "type", type: "string"), + new OA\Property(property: "title", type: "string"), + new OA\Property(property: "detail", type: "string"), + new OA\Property( + property: "violations", + type: "array", + items: new OA\Items( + properties: [ + new OA\Property(property: "propertyPath", type: "string"), + new OA\Property(property: "title", type: "string"), + new OA\Property( + property: "parameters", + properties: [ + new OA\Property(property: "{{ field }}", type: "string", nullable: true), + new OA\Property(property: "{{ value }}", type: "string", nullable: true), + ], + type: "object" + ), + new OA\Property(property: "type", type: "string") + ] + ) + ), + ] + ) + )] + #[OA\Response( + response: Response::HTTP_CONFLICT, + description: "When the author is already registered with the given username" + )] public function __invoke(Request $request): Response { $constraint = new Assert\Collection([ @@ -43,16 +102,20 @@ final class PostAuthorController extends AbstractController return $this->json($violations, Response::HTTP_BAD_REQUEST); } - $author = $this->authorApplicationService->signUp( - Uuid::uuid4()->toString(), - $data['username'], - $data['email'], - $data['name'], - $data['biography'], - $data['location'], - $data['website'], - $data['birth_date'] - ); + try { + $author = $this->authorApplicationService->signUp( + Uuid::uuid4()->toString(), + $data['username'], + $data['email'], + $data['name'], + $data['biography'], + $data['location'], + $data['website'], + $data['birth_date'] + ); + } catch (AuthorAlreadyExists) { + throw new HttpException(statusCode: Response::HTTP_CONFLICT); + } return $this->json( data: AuthorDto::assembleFrom($author), diff --git a/src/App/Dto/AuthorDto.php b/src/App/Dto/AuthorDto.php index 87eda4f..7f8ddb5 100644 --- a/src/App/Dto/AuthorDto.php +++ b/src/App/Dto/AuthorDto.php @@ -5,7 +5,6 @@ declare(strict_types=1); namespace App\Dto; use Cheeper\DomainModel\Author\Author; -use DateTimeImmutable; final class AuthorDto {