diff --git a/.env.example b/.env.example index 94ddbae..f978f5d 100644 --- a/.env.example +++ b/.env.example @@ -55,7 +55,8 @@ TELESCOPE_GATE_WATCHER=true TELESCOPE_SCHEDULE_WATCHER=false TELESCOPE_VIEW_WATCHER=true -L5_SWAGGER_GENERATE_ALWAYS=true +# Scramble API Documentation +API_VERSION=1.0.0 CLIENT_ID_SENDY= CLIENT_SECRET_SENDY= diff --git a/app/Http/Controllers/ChannelController.php b/app/Http/Controllers/ChannelController.php index 9f7a3df..6e16e0c 100644 --- a/app/Http/Controllers/ChannelController.php +++ b/app/Http/Controllers/ChannelController.php @@ -17,12 +17,13 @@ use App\Http\Repositories\PublisherRepository; use App\Http\Resources\ChannelSubscribeResource; use App\Http\Repositories\ChannelSubscribeRepository; +use Dedoc\Scramble\Attributes\Response as ScrambleResponse; class ChannelController extends Controller { - protected $channelRepository; - protected $channelSubscribeRepository; - protected $publisherRepository; + protected ChannelRepository $channelRepository; + protected ChannelSubscribeRepository $channelSubscribeRepository; + protected PublisherRepository $publisherRepository; public function __construct(ChannelRepository $channelRepository, ChannelSubscribeRepository $ChannelSubscribeRepository, PublisherRepository $publisherRepository) { @@ -31,62 +32,6 @@ public function __construct(ChannelRepository $channelRepository, ChannelSubscri $this->channelSubscribeRepository = $ChannelSubscribeRepository; } - /** - * @OA\Get( - * path="/api/channels", - * summary="List of all channels", - * tags={"channels"}, - * security={{"passport":{}}}, - * description="Use to get the list of all channels", - * @OA\Parameter( - * name="q", - * in="query", - * description="values to filter returned data (name values)", - * required=false, - * @OA\Schema( - * type="string" - * ) - * ), - * @OA\Parameter( - * name="limit", - * in="query", - * description="maximum number of results to return", - * required=false, - * @OA\Schema( - * type="integer", - * format="int32", - * minimum=1 - * ) - * ), - * @OA\Parameter( - * name="order", - * in="query", - * description="type of order: ASC, DESC", - * required=false, - * @OA\Schema( - * type="string", - * ) - * ), - * @OA\Parameter( - * name="orderBy", - * in="query", - * description="field to order: id - name(default) - created_at - updated_at", - * required=false, - * @OA\Schema( - * type="string", - * ) - * ), - * @OA\Response( - * response=200, - * ref="#/components/responses/Success200" - * ), - * @OA\Response( - * response=500, - * ref="#/components/responses/Error500", - * ) - * - * ) - */ public function getList(Request $request) { $query = $request->input('q'); @@ -97,92 +42,16 @@ public function getList(Request $request) return ChannelResource::collection($retriviedChannels); } - - /** - * @OA\Get( - * path="/api/channels/{id}", - * summary="Find a channel by id", - * tags={"channels"}, - * security={{"passport":{}}}, - * description="Use to get a channel by id", - * operationId="channelController.getchannel", - * @OA\Parameter( - * in="path", - * required=true, - * description="channel id", - * name="id", - * @OA\Schema( - * type="integer", - * minimum=1 - * ) - * ), - * @OA\Response( - * response=200, - * ref="#/components/responses/Success200" - * ), - * @OA\Response( - * response=404, - * ref="#/components/responses/Error404" - * ) - * ) - * - * Response to route /api/channels/{id} - * - * @param int $id - * @return ChannelResource $channel - * - */ public function getChannel($id) { $channel = $this->channelRepository->find($id); if (!$channel) { - return response()->error404(__('messages.Channel') . $id); + return $this->error404(__('messages.Channel') . $id); } return new ChannelResource($channel); } - /** - * @OA\Post( - * path="/api/channels", - * summary="Save new channel", - * tags={"channels"}, - * security={{"passport":{}}}, - * description="Use to store a new channel", - * @OA\RequestBody( - * description="Channel object that needs to be created", - * @OA\MediaType( - * mediaType="application/json", - * @OA\Schema( - * schema="Channel", - * type="object", - * @OA\Property( - * property="name", - * type="string", - * example="channel1" - * ), - * @OA\Property( - * property="priority", - * type="string", - * example="default", - * enum={"high", "medium", "low", "default"}, - * ) - * ), - * ) - * ), - * @OA\Response( - * response=201, - * ref="#/components/responses/Success201", - * ), - * @OA\Response( - * response=500, - * ref="#/components/responses/Error500", - * ), - * @OA\Response( - * response=422, - * ref="#/components/responses/Error422", - * ) - * ) - */ + #[ScrambleResponse(status: 201, description: 'Channel created successfully', type: 'array{message: string, channel: array{name: string, priority: string}}')] public function store(ChannelRequest $request) { $channel = $request->only(['name', 'priority']); @@ -190,58 +59,27 @@ public function store(ChannelRequest $request) try { $this->channelRepository->save((object) $channel); } catch (\Exception $e) { - return response()->error500(__('messages.SaveError') . ' ' . json_encode($channel)); + return $this->error500(__('messages.SaveError') . ' ' . json_encode($channel)); } - return response()->success201("Channel successfully saved", "channel", $channel); + return $this->success201("Channel successfully saved", "channel", $channel); } - /** - * @OA\Delete( - * path="/api/channels/{id}", - * summary="Delete the channel", - * tags={"channels"}, - * security={{"passport":{}}}, - * description="Delete channel and its relations", - * @OA\Parameter( - * in="path", - * required=true, - * description="channel id", - * name="id", - * @OA\Schema( - * type="integer", - * minimum=1 - * ) - * ), - * @OA\Response( - * response=500, - * ref="#/components/responses/Error500" - * ), - * @OA\Response( - * response=404, - * ref="#/components/responses/Error404" - * ), - * @OA\Response( - * response=200, - * ref="#/components/responses/Success200" - * ) - * ) - */ public function destroy(int $id) { $channel = $this->channelRepository->find($id); if (!$channel) { - return response()->error404(__('messages.Channel') . $id); + return $this->error404(__('messages.Channel') . $id); } Cache::forget(Config::get('cache.channel_key_prefix') . $channel->name); if (!$this->channelRepository->delete($channel)) { - return response()->error500(__('messages.DeleteError') . $channel); + return $this->error500(__('messages.DeleteError') . $channel->id); } - return response()->success200(__('messages.DeleteSuccess'), [ + return $this->success200(__('messages.DeleteSuccess'), [ 'action' => 'DELETE', // TO DO: add user id key value pair 'object_type' => 'channel', @@ -249,137 +87,24 @@ public function destroy(int $id) ]); } - /** - * @OA\Get( - * path="/api/channels/{id}/subscribers", - * summary="List of all subscribers of a channel", - * tags={"channels"}, - * security={{"passport":{}}}, - * description="Use to get the list of all the subscribers of a channel", - * operationId="ChannelController.getChannelSubscribers", - * @OA\Parameter( - * in="path", - * required=true, - * description="Channel id", - * name="id", - * @OA\Schema( - * type="integer", - * minimum=1 - * ) - * ), - * @OA\Response( - * response=200, - * ref="#/components/responses/Success200" - * ), - * @OA\Response( - * response=404, - * ref="#/components/responses/Error404" - * ) - * ) - * - * Response to route /api/channels/{id}/subscribers - * - * @param int $id - * @return $subscriber - * - */ - public function getChannelSubscribers($id) + public function getChannelSubscribers(int $id) { $channel = $this->channelRepository->find($id); if (!$channel) { - return response()->error404("Channel " . $id); + return $this->error404("Channel " . $id); } return ChannelSubscribeResource::collection($channel->registrations); } - /** - * @OA\Get( - * path="/api/channels/{id}/publishers", - * summary="List of all publishers of a channel", - * tags={"channels"}, - * security={{"passport":{}}}, - * description="Use to get the list of all the publishers of a channel", - * operationId="ChannelController.getChannelPublishers", - * @OA\Parameter( - * in="path", - * required=true, - * description="Channel id", - * name="id", - * @OA\Schema( - * type="integer", - * minimum=1 - * ) - * ), - * @OA\Response( - * response=200, - * ref="#/components/responses/Success200" - * ), - * @OA\Response( - * response=404, - * ref="#/components/responses/Error404" - * ) - * ) - * - * Response to route /api/channels/{id}/publishers - * - * @param int $id - * @return $publisher - * - */ - public function getChannelPublishers($id) + public function getChannelPublishers(int $id) { $channel = $this->channelRepository->find($id); if (!$channel) { - return response()->error404("Channel " . $id); + return $this->error404("Channel " . $id); } return PublisherResource::collection(($channel->publishers)->unique()); } - /** - * @OA\Post( - * path="/api/sendMessage", - * summary="Send a message", - * tags={"messages"}, - * description="Use to send messages to the subscribers of a channel", - * operationId="ChannelController.sendMessage", - * security = {{"basicAuth": {}}}, - * @OA\RequestBody( - * description="Message that needs to be sent and channel name", - * @OA\MediaType( - * mediaType="application/json", - * @OA\Schema( - * schema="SendMessage", - * type="object", - * required={"channel", "message"}, - * @OA\Property( - * property="channel", - * type="string", - * example="channel name" - * ), - * @OA\Property( - * property="message", - * type="string", - * example="message example" - * ), - * ), - * ) - * ), - * @OA\Response( - * response=200, - * ref="#/components/responses/Success200" - * ), - * @OA\Response( - * response=422, - * ref="#/components/responses/Error422" - * ) - * ) - * - * Response to route /api/sendMessage - * - * @param MessageChannelRequest $request - * @return Response - * - */ public function sendMessage(MessageChannelRequest $request) { $channel = $this->getCachedChannelByName($request->channel); @@ -387,79 +112,21 @@ public function sendMessage(MessageChannelRequest $request) return $this->sendMessageTo($request, $channel); } - /** - * @OA\Post( - * path="/api/sendMessage/{channelName}", - * summary="Send a message to Channel", - * tags={"messages"}, - * description="Use to send message to the subscribers of a channel", - * operationId="ChannelController.SendMessageToChannel", - * security = {{"basicAuth": {}}}, - * @OA\Parameter( - * in="path", - * required=true, - * description="channel name", - * name="channelName", - * @OA\Schema( - * type="string", - * ) - * ), - * @OA\RequestBody( - * description="Message that needs to be sent", - * @OA\MediaType( - * mediaType="application/json", - * @OA\Schema( - * schema="Message", - * type="object", - * required={"message"}, - * @OA\Property( - * property="message", - * type="string", - * example="message example" - * ), - * ) - * ) - * ), - * @OA\Response( - * response=200, - * ref="#/components/responses/Success200" - * ), - * @OA\Response( - * response=422, - * ref="#/components/responses/Error422" - * ) - * ) - * - * Response to route /api/sendMessage/{channelName} - * - * @param MessageRequest $request - * @param String $channelName - * @return Response - * - */ - public function SendMessageToChannel(MessageRequest $request, $channelName) + public function sendMessageToChannel(MessageRequest $request, string $channelName) { $channel = $this->getCachedChannelByName($channelName); if (!$channel) { - return response()->error404(__('messages.Channel')); + return $this->error404(__('messages.Channel')); } return $this->sendMessageTo($request, $channel); } - /** - * Manage SendMessage request - * - * @param Request $request - * @param mixed $channel - * @return Response - * - */ - private function sendMessageTo($request, $channel) + private function sendMessageTo(Request $request, $channel) { $publisher = Auth::guard('api')->user(); if (!$this->publisherRepository->hasChannel($publisher, $channel->id)) { - return response()->error403( + return $this->error403( "Not authorized to send message on requested channel", [ 'publisherId' => $publisher->id, @@ -475,18 +142,10 @@ private function sendMessageTo($request, $channel) event(new SendMessageEvent($message, $subscriber->host, $relation, $channel->priority, $channel?->name ?? "", $subscriber->name)); } } - return response()->json([ - "message" => "Message dispatched", - ], 200); + return $this->success200("Message dispatched"); } - /** - * Save or retrieve channel and its subscribers from cache - * - * @param string $channelName - * @return mixed|boolean - */ - private function getCachedChannelByName($channelName) + private function getCachedChannelByName(string $channelName) { $cacheKey = Config::get('cache.channel_key_prefix') . $channelName; diff --git a/app/Http/Controllers/ChannelPublishController.php b/app/Http/Controllers/ChannelPublishController.php index 4801b3c..4341851 100644 --- a/app/Http/Controllers/ChannelPublishController.php +++ b/app/Http/Controllers/ChannelPublishController.php @@ -5,13 +5,15 @@ use App\Exceptions\DuplicateEntryException; use App\Http\Requests\ChannelPublishRequest; use App\Http\Repositories\PublisherRepository; +use App\Http\Repositories\ChannelPublishRepository; use App\Http\Repositories\RepositoryInterface; use App\Http\Resources\PublisherChannelResource; +use Dedoc\Scramble\Attributes\Response as ScrambleResponse; class ChannelPublishController extends Controller { - protected $channelPublishRepository; - protected $publisherRepository; + protected ChannelPublishRepository $channelPublishRepository; + protected PublisherRepository $publisherRepository; public function __construct(RepositoryInterface $channelPublishRepository, PublisherRepository $publisherRepository) { @@ -19,58 +21,11 @@ public function __construct(RepositoryInterface $channelPublishRepository, Publi $this->publisherRepository = $publisherRepository; } - /** - * @OA\Post( - * path="/api/publishers/{publisher_id}/channels", - * summary="Save new publisher registration to a channel", - * tags={"publishers"}, - * security={{"passport":{}}}, - * description="Use to register a publisher to a channel", - * operationId="ChannelPublishController.store", - * @OA\Parameter( - * in="path", - * required=true, - * description="publisher id", - * name="publisher_id", - * @OA\Schema( - * type="integer", - * minimum=1 - * ) - * ), - * @OA\RequestBody( - * description="Registration object that needs to be created", - * @OA\MediaType( - * mediaType="application/json", - * @OA\Schema( - * schema="ChannelPublish", - * type="object", - * required={"channel_id"}, - * @OA\Property( - * property="channel_id", - * type="integer", - * example=1 - * ) - * ), - * ) - * ), - * @OA\Response( - * response=201, - * ref="#/components/responses/Success201", - * ), - * @OA\Response( - * response=500, - * ref="#/components/responses/Error500", - * ), - * @OA\Response( - * response=422, - * ref="#/components/responses/Error422", - * ) - * ) - */ - public function store(ChannelPublishRequest $request, $id) + #[ScrambleResponse(status: 201, description: 'The publisher has been successfully registered to the channel', type: 'array{message: string, channelpublish: array{channel_id: int, publisher_id: int}}')] + public function store(ChannelPublishRequest $request, int $id) { if (!$this->publisherRepository->find($id)) { - return response()->error422('publisher_id', "Publisher with id = " . $id . " not Found"); + return $this->error422('publisher_id', "Publisher with id = " . $id . " not Found"); } $channelPublish = $request->only(['channel_id']); $channelPublish['publisher_id'] = $id; @@ -78,99 +33,23 @@ public function store(ChannelPublishRequest $request, $id) try { $this->channelPublishRepository->save((object) $channelPublish); } catch (DuplicateEntryException $e) { - return response()->error409("The subscription already exists", $channelPublish); + return $this->error409("The subscription already exists", $channelPublish); } catch (\Exception $e) { - return response()->error500(__('messages.SaveError') . ' ' . json_encode($channelPublish)); + return $this->error500(__('messages.SaveError') . ' ' . json_encode($channelPublish)); } - return response()->success201("The publisher has been successfully registered to the channel", "channelpublish", $channelPublish); + return $this->success201("The publisher has been successfully registered to the channel", "channelpublish", $channelPublish); } - /** - * @OA\Get( - * path="/api/publishers/{publisher_id}/channels", - * summary="Find a publisher registration by id", - * tags={"publishers"}, - * security={{"passport":{}}}, - * description="Use to get a publisher registration by id", - * operationId="ChannelPublishController.getChannelPublish", - * @OA\Parameter( - * in="path", - * required=true, - * description="Publisher id", - * name="publisher_id", - * @OA\Schema( - * type="integer", - * minimum=1 - * ) - * ), - * @OA\Response( - * response=200, - * ref="#/components/responses/Success200" - * ), - * @OA\Response( - * response=404, - * ref="#/components/responses/Error404" - * ) - * ) - * - * Response to route /api/publishers/{id}/channels - * - * @param int $id - * @return $channelPublish - * - */ - public function getChannelPublish($id) + public function getChannelPublish(int $id) { $publisher = $this->publisherRepository->find($id); if (!$publisher || count($publisher->registrations) == 0) { - return response()->error404(__('messages.channelPublish')); + return $this->error404(__('messages.channelPublish')); } return PublisherChannelResource::collection($publisher->registrations); } - /** - * @OA\Delete( - * path="/api/publishers/{publisher_id}/channels/{channel_id}", - * summary="Delete a publisher registration", - * tags={"publishers"}, - * security={{"passport":{}}}, - * description="Insert the publisher id and channel id that you want to delete", - * operationId="ChannelPublishController.destroy", - * @OA\Parameter( - * in="path", - * required=true, - * description="Publisher id", - * name="publisher_id", - * @OA\Schema( - * type="integer", - * minimum=1 - * ) - * ), - * @OA\Parameter( - * in="path", - * required=true, - * description="Channel id", - * name="channel_id", - * @OA\Schema( - * type="integer", - * minimum=1 - * ) - * ), - * @OA\Response( - * response=500, - * ref="#/components/responses/Error500" - * ), - * @OA\Response( - * response=404, - * ref="#/components/responses/Error404" - * ), - * @OA\Response( - * response=200, - * ref="#/components/responses/Success200" - * ) - * ) - */ public function destroy(int $publisher_id, int $channel_id) { $channelPublish = $this->channelPublishRepository->getByFilter( @@ -180,13 +59,13 @@ public function destroy(int $publisher_id, int $channel_id) ] ); if (!$channelPublish) { - return response()->error404(__('messages.channelPublish')); + return $this->error404(__('messages.channelPublish')); } if (!$this->channelPublishRepository->delete($channelPublish)) { - return response()->error500(__('messages.DeleteError') . $channelPublish); + return $this->error500(__('messages.DeleteError') . $channelPublish->id); } - return response()->success200(__('messages.DeleteSuccess'), [ + return $this->success200(__('messages.DeleteSuccess'), [ 'action' => 'DELETE', 'object_type' => 'ChannelPublish', 'object_id' => $channelPublish->id diff --git a/app/Http/Controllers/ChannelSubscribeController.php b/app/Http/Controllers/ChannelSubscribeController.php index 0959a68..e66e465 100644 --- a/app/Http/Controllers/ChannelSubscribeController.php +++ b/app/Http/Controllers/ChannelSubscribeController.php @@ -6,16 +6,18 @@ use Illuminate\Support\Facades\Config; use App\Exceptions\DuplicateEntryException; use App\Http\Repositories\ChannelRepository; +use App\Http\Repositories\ChannelSubscribeRepository; use App\Http\Repositories\RepositoryInterface; use App\Http\Requests\ChannelSubscribeRequest; use App\Http\Repositories\SubscriberRepository; use App\Http\Resources\SubscriberChannelResource; +use Dedoc\Scramble\Attributes\Response as ScrambleResponse; class ChannelSubscribeController extends Controller { - protected $channelSubscribeRepository; - protected $subscriberRepository; - protected $channelRepository; + protected ChannelSubscribeRepository $channelSubscribeRepository; + protected SubscriberRepository $subscriberRepository; + protected ChannelRepository $channelRepository; const CHANNEL_CACHE_KEY_PREFIX = "channel."; @@ -26,86 +28,15 @@ public function __construct(RepositoryInterface $channelSubscribeRepository, Sub $this->channelRepository = $channelRepository; } - /** - * @OA\Post( - * path="/api/subscribers/{subscriber_id}/channels", - * summary="Save new subscriber registration to a channel", - * tags={"subscribers"}, - * security={{"passport":{}}}, - * description="Use to register a subscriber to a channel", - * operationId="ChannelSubscribeController.store", - * @OA\Parameter( - * in="path", - * required=true, - * description="Subscribers id", - * name="subscriber_id", - * @OA\Schema( - * type="integer", - * minimum=1 - * ) - * ), - * @OA\RequestBody( - * description="Registration object that needs to be created", - * @OA\MediaType( - * mediaType="application/x-www-form-urlencoded", - * @OA\Schema( - * schema="ChannelSubscribe", - * type="object", - * required={"channel_id", "endpoint", "authentication"}, - * @OA\Property( - * property="channel_id", - * type="integer", - * example=1 - * ), - * @OA\Property( - * property="endpoint", - * type="string", - * example="api/test" - * ), - * @OA\Property( - * property="authentication", - * type="string", - * enum={"NONE", "OAUTH2", "BASIC"}, - * default="NONE" - * ), - * @OA\Property( - * property="username", - * type="string" - * ), - * @OA\Property( - * property="password", - * type="string", - * format="password" - * ) - * ), - * ) - * ), - * @OA\Response( - * response=201, - * ref="#/components/responses/Success201", - * ), - * @OA\Response( - * response=500, - * ref="#/components/responses/Error500", - * ), - * @OA\Response( - * response=409, - * ref="#/components/responses/Error409", - * ), - * @OA\Response( - * response=422, - * ref="#/components/responses/Error422", - * ) - * ) - */ + #[ScrambleResponse(status: 201, description: 'The subscriber has been successfully registered to the channel', type: 'array{message: string, channelsubscribe: array{channel_id: int, endpoint: string, authentication: string, username: string, password: string,subscriber_id: int }}')] public function store(ChannelSubscribeRequest $request, int $id) { if (!$this->subscriberRepository->find($id)) { - return response()->error422('subscriber_id', "Subscriber with id = " . $id . " not Found"); + return $this->error422('subscriber_id', "Subscriber with id = " . $id . " not Found"); } if (!$channel = $this->channelRepository->find($request->get("channel_id"))) { - return response()->error422('channel_id', "Channel with id = " . $request->get("channel_id") . " not found"); + return $this->error422('channel_id', "Channel with id = " . $request->get("channel_id") . " not found"); } $channelSubscribe = $request->only([ @@ -125,108 +56,42 @@ public function store(ChannelSubscribeRequest $request, int $id) try { $this->channelSubscribeRepository->save((object) $channelSubscribe); } catch (DuplicateEntryException $e) { - return response()->error409("The subscription already exists", $channelSubscribe); + return $this->error409("The subscription already exists", $channelSubscribe); } catch (\Exception $e) { - return response()->error500(__('messages.SaveError') . ' ' . json_encode($channelSubscribe)); + return $this->error500(__('messages.SaveError') . ' ' . json_encode($channelSubscribe)); } - return response()->success201("The subscriber has been successfully registered to the channel", "channelsubscribe", $channelSubscribe); + return $this->success201("The subscriber has been successfully registered to the channel", "channelsubscribe", $channelSubscribe); } - /** - * @OA\Get( - * path="/api/subscribers/{id}/channels", - * summary="Find a subscriber registration by id of subscribers", - * tags={"subscribers"}, - * security={{"passport":{}}}, - * description="Use to get a subscriber registrations by id", - * operationId="ChannelSubscribeController.getChannelSubscribe", - * @OA\Parameter( - * in="path", - * required=true, - * description="subscriber id", - * name="id", - * @OA\Schema( - * type="integer", - * minimum=1 - * ) - * ), - * @OA\Response( - * response=200, - * ref="#/components/responses/Success200" - * ), - * @OA\Response( - * response=404, - * ref="#/components/responses/Error404" - * ) - * ) - * - * Response to route /api/subscribers/{id}/channels - * - * @param int $id subscriber_id - * @return $channelSubscribe - * - */ - public function getChannelSubscribe($id) + public function getChannelSubscribe(int $id) { $subscriber = $this->subscriberRepository->find($id); if (!$subscriber || count($subscriber->registrations) == 0) { - return response()->error404(__('messages.subscriber') . $id); + return $this->error404(__('messages.subscriber') . $id); } return SubscriberChannelResource::collection($subscriber->registrations); } - /** - * @OA\Delete( - * path="/api/channel-subscriber/{id}", - * summary="Delete a subscriber registration", - * tags={"channel-subscriber"}, - * security={{"passport":{}}}, - * description="Insert the channel-subscriber relation id that you want to delete", - * operationId="ChannelSubscribeController.destroy", - * @OA\Parameter( - * in="path", - * required=true, - * description="channel-subscriber id", - * name="id", - * @OA\Schema( - * type="integer", - * minimum=1 - * ) - * ), - * @OA\Response( - * response=500, - * ref="#/components/responses/Error500" - * ), - * @OA\Response( - * response=404, - * ref="#/components/responses/Error404" - * ), - * @OA\Response( - * response=200, - * ref="#/components/responses/Success200" - * ) - * ) - */ public function destroy(int $id) { try { $channelSubscribe = $this->channelSubscribeRepository->find($id); if (!$channelSubscribe) { - return response()->error404(__('messages.channelSubscribe')); + return $this->error404(__('messages.channelSubscribe')); } Cache::forget(Config::get('cache.channel_key_prefix') . $channelSubscribe->channel->name); $this->channelSubscribeRepository->delete($channelSubscribe); - return response()->success200(__('messages.DeleteSuccess'), [ + return $this->success200(__('messages.DeleteSuccess'), [ 'action' => 'DELETE', 'object_type' => 'ChannelSubscribe', 'object_id' => $channelSubscribe->id ]); } catch (\Exception $e) { - return response()->error500(__('messages.DeleteError') . $channelSubscribe); + return $this->error500(__('messages.DeleteError') . $id); } } } diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index b42e59a..0382cf3 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -5,88 +5,98 @@ use Illuminate\Routing\Controller as BaseController; use Illuminate\Foundation\Validation\ValidatesRequests; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; - -/** - *@OA\Info( - * version="1.0.0", - * title="Zanichelli API Buzzer projects", - * description="REST APIs to get channels info", - * @OA\Contact( - * name="Zanichelli DEV team", - * email="developers@zanichelli.it" - * ) - *), - * @OA\Components( - * @OA\Response( - * response="Success200", - * description="Operation successful", - * @OA\MediaType( - * mediaType="application/json") - * ), - * @OA\Response( - * response="Success201", - * description="Created", - * @OA\MediaType( - * mediaType="application/json") - * ), - * @OA\Response( - * response="Success204", - * description="No Content", - * @OA\MediaType( - * mediaType="application/json") - * ), - * @OA\Response( - * response="Error404", - * description="Not Found", - * @OA\MediaType( - * mediaType="application/json", - * @OA\Schema(ref="#/components/schemas/Message404") - * ) - * ), - * @OA\Response( - * response="Error409", - * description="Conflict", - * @OA\MediaType( - * mediaType="application/json") - * ), - * @OA\Response( - * response="Error422", - * description="Unprocessable entity: data validation error", - * @OA\MediaType( - * mediaType="application/json") - * ), - * @OA\Response( - * response="Error500", - * description="Internal Server Error", - * @OA\MediaType( - * mediaType="application/json", - * @OA\Schema(ref="#/components/schemas/Message500") - * ) - * ), - * @OA\Schema( - * schema="Message404", - * type="object", - * @OA\Property( - * property="message", - * type="string", - * default="Object not found" - * ) - * ), - * @OA\Schema( - * schema="Message500", - * type="object", - * @OA\Property( - * property="message", - * type="string", - * default="System error" - * ) - * ), - * ), - */ +use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\Response; class Controller extends BaseController { use AuthorizesRequests, ValidatesRequests; const PAGINATION = 12; + + public function success200($value = "", $params = []) + { + $params['content'] = $value; + Log::info('200* ' . json_encode($params)); + if ($value) { + return Response::make(['message' => $value], 200); + } + return Response::make('', 200); + } + + public function success201($value, $type, $object) + { + $response = [ + 'message' => $value, + $type => $object + ]; + Log::info('201* ' . json_encode($response)); + return Response::make($response, 201); + } + + public function success204() + { + return Response::make('', 204); + } + + public function error401($value = '') + { + $message = $value; + Log::error('401* ' . json_encode(['content' => $message])); + return Response::make(['message' => $message], 401); + } + + public function error403($value = '', $details = []) + { + $message = ($value ? $value : __('messages.Unauthorized')); + Log::error('403* ' . json_encode([ + 'content' => $message, + 'details' => $details + ])); + return Response::make(['message' => $message], 403); + } + + public function error404($value = '') + { + $message = ($value ? $value : __('messages.Object')) . __('messages.NotFound'); + Log::error('404* ' . json_encode(['content' => $message])); + return Response::make(['message' => $message], 404); + } + + public function error409($value = '', $params = []) + { + $params['content'] = $value; + Log::error('409* ' . json_encode($params)); + return Response::make(['message' => $value], 409); + } + + public function error422($field, $error) + { + if (!$field) { + return Response::make( + [ + 'message' => 'Data is invalid', + 'errors' => $error + ], + 422 + ); + } + + return Response::make( + [ + 'message' => 'Data is invalid', + 'errors' => [ + $field => [$error] + ] + ], + 422 + ); + } + + public function error500($value = '') + { + $message = $value ? $value : __('messages.SystemError'); + Log::error('500* ' . json_encode(['content' => $message])); + return Response::make(['message' => $message], 500); + } } diff --git a/app/Http/Controllers/FailedJobController.php b/app/Http/Controllers/FailedJobController.php index ec9fe3e..dd01192 100644 --- a/app/Http/Controllers/FailedJobController.php +++ b/app/Http/Controllers/FailedJobController.php @@ -13,70 +13,13 @@ class FailedJobController extends Controller { - protected $failedJobRepository; + protected FailedJobRepository $failedJobRepository; public function __construct(FailedJobRepository $failedJobRepository) { $this->failedJobRepository = $failedJobRepository; - } - /** - * @OA\Get( - * path="/api/failedJobs", - * summary="List of all the failed jobs", - * tags={"jobs"}, - * security={{"passport":{}}}, - * description="Use to get the list of all failed jobs", - * @OA\Parameter( - * name="q", - * in="query", - * description="values to filter returned data (payload values)", - * required=false, - * @OA\Schema( - * type="string" - * ) - * ), - * @OA\Parameter( - * name="limit", - * in="query", - * description="maximum number of results to return", - * required=false, - * @OA\Schema( - * type="integer", - * format="int32", - * minimum=1 - * ) - * ), - * @OA\Parameter( - * name="order", - * in="query", - * description="type of order: ASC, DESC", - * required=false, - * @OA\Schema( - * type="string", - * ) - * ), - * @OA\Parameter( - * name="orderBy", - * in="query", - * description="field to order: id - name(default) - created_at - updated_at", - * required=false, - * @OA\Schema( - * type="string", - * ) - * ), - * @OA\Response( - * response=200, - * ref="#/components/responses/Success200" - * ), - * @OA\Response( - * response=500, - * ref="#/components/responses/Error500", - * ) - * - * ) - */ public function getList(Request $request) { $validator = Validator::make($request->all(), [ @@ -87,7 +30,7 @@ public function getList(Request $request) ]); if ($validator->fails()) { - return response()->error422(null, $validator->errors()); + return $this->error422(null, $validator->errors()); } $query = $request->input('q'); @@ -98,167 +41,66 @@ public function getList(Request $request) return FailedJobResource::collection($retriviedFailedJobs); } - /** - * @OA\Get( - * path="/api/failedJobs/retry/{id}", - * summary="Retry a failed jobs", - * tags={"jobs"}, - * security={{"passport":{}}}, - * description="Use to retry a failed job", - * operationId="failedJobController.retryJob", - * @OA\Parameter( - * in="path", - * required=true, - * description="id of the job that you want to retry", - * name="id", - * @OA\Schema( - * type="integer", - * minimum=1 - * ) - * ), - * @OA\Response( - * response=500, - * ref="#/components/responses/Error500" - * ), - * @OA\Response( - * response=200, - * ref="#/components/responses/Success200" - * ), - * @OA\Response( - * response=404, - * ref="#/components/responses/Error404" - * ) - * ) - */ - - public function retryJob($id) + public function retryJob(int $id) { - $failedJob= $this->failedJobRepository->find($id); - if(!$failedJob){ - return response()->error404(__('messages.FailedJob') . $id); + $failedJob = $this->failedJobRepository->find($id); + if (!$failedJob) { + return $this->error404(__('messages.FailedJob') . $id); } - try{ + try { $result = Artisan::call('queue:retry', ['id' => $id]); if ($result != 0) { - return response()->error500(__('messages.RetryError') . $failedJob); + return $this->error500(__('messages.RetryError') . $failedJob->id); } - } catch(Exception $e) { - return response()->error500(__('messages.RetryError') . $failedJob); + } catch (Exception $e) { + return $this->error500(__('messages.RetryError') . $failedJob->id); } - return response()->success200(__('messages.RetrySuccess'), [ + return $this->success200(__('messages.RetrySuccess'), [ 'action' => 'RETRY', 'object_type' => 'failedJob', 'object_id' => $id ]); } - /** - * @OA\Delete( - * path="/api/failedJobs/{id}", - * summary="Delete the failed Job", - * tags={"jobs"}, - * security={{"passport":{}}}, - * description="Insert the failed job id that you want to delete", - * operationId="FailedJobController.destroy", - * @OA\Parameter( - * in="path", - * required=true, - * description="id of the job that you want to delete", - * name="id", - * @OA\Schema( - * type="integer", - * minimum=1 - * ) - * ), - * @OA\Response( - * response=500, - * ref="#/components/responses/Error500" - * ), - * @OA\Response( - * response=404, - * ref="#/components/responses/Error404" - * ), - * @OA\Response( - * response=200, - * ref="#/components/responses/Success200" - * ) - * ) - */ public function destroy(int $id) { - $failedJob= $this->failedJobRepository->find($id); - if(!$failedJob){ - return response()->error404(__('messages.FailedJob') . $id); + $failedJob = $this->failedJobRepository->find($id); + if (!$failedJob) { + return $this->error404(__('messages.FailedJob') . $id); } - try{ + try { $result = Artisan::call('queue:forget', ['id' => $id]); - if($result != 0){ - return response()->error500(__('messages.DeleteError') . $failedJob); + if ($result != 0) { + return $this->error500(__('messages.DeleteError') . $failedJob->id); } - } catch(Exception $e) { - return response()->error500(__('messages.DeleteError') . $failedJob); + } catch (Exception $e) { + return $this->error500(__('messages.DeleteError') . $failedJob->id); } - return response()->success200(__('messages.DeleteSuccess'), [ + return $this->success200(__('messages.DeleteSuccess'), [ 'action' => 'DELETE', 'object_type' => 'failedJob', 'object_id' => $failedJob->id ]); } - /** - * @OA\Delete( - * path="/api/failedJobs/all", - * summary="Delete all the failed Jobs", - * tags={"jobs"}, - * security={{"passport":{}}}, - * description="Delete all jobs in failed_jobs table", - * operationId="FailedJobController.destroyAll", - * @OA\Response( - * response=500, - * ref="#/components/responses/Error500" - * ), - * @OA\Response( - * response=200, - * ref="#/components/responses/Success200" - * ) - * ) - */ public function destroyAll() { $result = Artisan::call('queue:flush'); if ($result != 0) { - return response()->error500(__('messages.DeleteError')); + return $this->error500(__('messages.DeleteError')); } - return response()->success200(__('messages.DeleteSuccess')); + return $this->success200(__('messages.DeleteSuccess')); } - /** - * @OA\Get( - * path="/api/failedJobs/retry/all", - * summary="Retry all the failed Jobs", - * tags={"jobs"}, - * security={{"passport":{}}}, - * description="Retry all jobs in failed_jobs table", - * operationId="FailedJobController.retryAll", - * @OA\Response( - * response=500, - * ref="#/components/responses/Error500" - * ), - * @OA\Response( - * response=200, - * ref="#/components/responses/Success200" - * ) - * ) - */ public function retryAll() { $result = Artisan::call('queue:lazy-retry'); if ($result != 0) { - return response()->error500(__('messages.RetryError') . $result); + return $this->error500(__('messages.RetryError') . $result); } - return response()->success200(__('messages.RetrySuccess')); + return $this->success200(__('messages.RetrySuccess')); } } diff --git a/app/Http/Controllers/PublisherController.php b/app/Http/Controllers/PublisherController.php index 877e4e9..bc86c28 100644 --- a/app/Http/Controllers/PublisherController.php +++ b/app/Http/Controllers/PublisherController.php @@ -2,71 +2,22 @@ namespace App\Http\Controllers; +use App\Http\Repositories\PublisherRepository; use Illuminate\Http\Request; use App\Http\Requests\PublisherRequest; use App\Http\Resources\PublisherResource; use App\Http\Repositories\RepositoryInterface; +use Dedoc\Scramble\Attributes\Response as ScrambleResponse; class PublisherController extends Controller { - protected $publisherRepository; + protected PublisherRepository $publisherRepository; public function __construct(RepositoryInterface $publisherRepository) { $this->publisherRepository = $publisherRepository; } - /** - * @OA\Get( - * path="/api/publishers", - * summary="List of all publishers", - * tags={"publishers"}, - * security={{"passport":{}}}, - * description="Use to get the list of all publishers", - * @OA\Parameter( - * name="q", - * in="query", - * description="values to filter returned data", - * required=false, - * @OA\Schema( - * type="string" - * ) - * ), - * @OA\Parameter( - * name="limit", - * in="query", - * description="maximum number of results to return", - * required=false, - * @OA\Schema( - * type="integer", - * format="int32" - * ) - * ), - * @OA\Parameter( - * name="order", - * in="query", - * description="type of order: ASC, DESC", - * required=false, - * @OA\Schema( - * type="string", - * ) - * ), - * @OA\Parameter( - * name="orderBy", - * in="query", - * description="field to order: id - name(default) - host - created_at - updated_at", - * required=false, - * @OA\Schema( - * type="string", - * ) - * ), - * @OA\Response( - * response=200, - * ref="#/components/responses/Success200" - * ) - * - * ) - */ public function getList(Request $request) { $query = $request->input('q'); @@ -77,57 +28,7 @@ public function getList(Request $request) return PublisherResource::collection($retriviedPublishers); } - /** - * @OA\Post( - * path="/api/publishers", - * summary="Save new publisher", - * tags={"publishers"}, - * security={{"passport":{}}}, - * description="Use to store a new publisher", - * @OA\RequestBody( - * description="Publisher object that needs to be created", - * @OA\MediaType( - * mediaType="application/json", - * @OA\Schema( - * schema="Publisher", - * type="object", - * @OA\Property( - * property="name", - * type="string", - * example="publisher1" - * ), - * @OA\Property( - * property="host", - * type="string", - * example="https://my-host.it" - * ), - * @OA\Property( - * property="username", - * type="string", - * example="username" - * ), - * @OA\Property( - * property="password", - * type="string", - * example="password" - * ), - * ), - * ) - * ), - * @OA\Response( - * response=201, - * ref="#/components/responses/Success201", - * ), - * @OA\Response( - * response=500, - * ref="#/components/responses/Error500", - * ), - * @OA\Response( - * response=422, - * ref="#/components/responses/Error422", - * ) - * ) - */ + #[ScrambleResponse(status: 201, description: 'Publisher successfully saved', type: 'array{message: string, publisher: PublisherResource}')] public function store(PublisherRequest $request) { $publisher = $request->only([ @@ -140,100 +41,34 @@ public function store(PublisherRequest $request) try { $this->publisherRepository->save((object) $publisher); } catch (\Exception $e) { - return response()->error500(__('messages.SaveError') . ' ' . json_encode($publisher)); + return $this->error500(__('messages.SaveError') . ' ' . json_encode($publisher)); } - return response()->success201("Publisher successfully saved", "publisher", $publisher); + return $this->success201("Publisher successfully saved", "publisher", $publisher); } - /** - * @OA\Delete( - * path="/api/publishers/{id}", - * summary="Delete the publisher", - * tags={"publishers"}, - * security={{"passport":{}}}, - * description="Insert the publisher id that you want to delete", - * operationId="PublisherController.destroy", - * @OA\Parameter( - * in="path", - * required=true, - * description="publisher id", - * name="id", - * @OA\Schema( - * type="integer", - * minimum=1 - * ) - * ), - * @OA\Response( - * response=500, - * ref="#/components/responses/Error500" - * ), - * @OA\Response( - * response=404, - * ref="#/components/responses/Error404" - * ), - * @OA\Response( - * response=200, - * ref="#/components/responses/Success200" - * ) - * ) - */ public function destroy(int $id) { $publisher = $this->publisherRepository->find($id); if (!$publisher) { - return response()->error404(__('messages.Publisher') . $id); + return $this->error404(__('messages.Publisher') . $id); } if (!$this->publisherRepository->delete($publisher)) { - return response()->error500(__('messages.DeleteError') . $publisher); + return $this->error500(__('messages.DeleteError') . $publisher->id); } - return response()->success200(__('messages.DeleteSuccess'), [ + return $this->success200(__('messages.DeleteSuccess'), [ 'action' => 'DELETE', 'object_type' => 'publisher', 'object_id' => $publisher->id ]); } - /** - * @OA\Get( - * path="/api/publishers/{id}", - * summary="Find a publisher by id", - * tags={"publishers"}, - * security={{"passport":{}}}, - * description="Use to get a publisher by id", - * operationId="PublisherController.getPublisher", - * @OA\Parameter( - * in="path", - * required=true, - * description="publisher id", - * name="id", - * @OA\Schema( - * type="integer", - * minimum=1 - * ) - * ), - * @OA\Response( - * response=200, - * ref="#/components/responses/Success200" - * ), - * @OA\Response( - * response=404, - * ref="#/components/responses/Error404" - * ) - * ) - * - * Response to route /api/publishers/{id} - * - * @param int $id - * @return $publisher - * - */ - public function getPublisher($id) + public function getPublisher(int $id) { $publisher = $this->publisherRepository->find($id); if (!$publisher) { - return response()->error404(__('messages.Publisher') . $id); + return $this->error404(__('messages.Publisher') . $id); } return new PublisherResource($publisher); } diff --git a/app/Http/Controllers/SubscriberController.php b/app/Http/Controllers/SubscriberController.php index f64bbdf..e277d66 100644 --- a/app/Http/Controllers/SubscriberController.php +++ b/app/Http/Controllers/SubscriberController.php @@ -6,69 +6,20 @@ use App\Http\Requests\SubscriberRequest; use App\Http\Resources\SubscriberResource; use App\Http\Repositories\RepositoryInterface; +use App\Http\Repositories\SubscriberRepository; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Config; +use Dedoc\Scramble\Attributes\Response as ScrambleResponse; class SubscriberController extends Controller { - protected $subscriberRepository; + protected SubscriberRepository $subscriberRepository; public function __construct(RepositoryInterface $subscriberRepository) { $this->subscriberRepository = $subscriberRepository; } - /** - * @OA\Get( - * path="/api/subscribers", - * summary="List of all subscribers", - * tags={"subscribers"}, - * security={{"passport":{}}}, - * description="Use to get the list of all subscribers", - * @OA\Parameter( - * name="q", - * in="query", - * description="values to filter returned data", - * required=false, - * @OA\Schema( - * type="string" - * ) - * ), - * @OA\Parameter( - * name="limit", - * in="query", - * description="maximum number of results to return", - * required=false, - * @OA\Schema( - * type="integer", - * format="int32" - * ) - * ), - * @OA\Parameter( - * name="order", - * in="query", - * description="type of order: ASC, DESC", - * required=false, - * @OA\Schema( - * type="string", - * ) - * ), - * @OA\Parameter( - * name="orderBy", - * in="query", - * description="field to order: id - name(default) - host - created_at - updated_at", - * required=false, - * @OA\Schema( - * type="string", - * ) - * ), - * @OA\Response( - * response=200, - * ref="#/components/responses/Success200" - * ) - * - * ) - */ public function getList(Request $request) { $query = $request->input('q'); @@ -79,47 +30,7 @@ public function getList(Request $request) return SubscriberResource::collection($retriviedSubscribers); } - /** - * @OA\Post( - * path="/api/subscribers", - * summary="Save new subscriber", - * tags={"subscribers"}, - * security={{"passport":{}}}, - * description="Use to store a new subscriber", - * @OA\RequestBody( - * description="Subscriber object that needs to be created", - * @OA\MediaType( - * mediaType="application/json", - * @OA\Schema( - * schema="Subscriber", - * type="object", - * @OA\Property( - * property="name", - * type="string", - * example="subscriber1" - * ), - * @OA\Property( - * property="host", - * type="string", - * example="https://host1-test/" - * ) - * ), - * ) - * ), - * @OA\Response( - * response=201, - * ref="#/components/responses/Success201", - * ), - * @OA\Response( - * response=500, - * ref="#/components/responses/Error500", - * ), - * @OA\Response( - * response=422, - * ref="#/components/responses/Error422", - * ) - * ) - */ + #[ScrambleResponse(status: 201, description: 'Subscriber successfully saved', type: 'array{message: string, subscriber: SubscriberResource}')] public function store(SubscriberRequest $request) { $subscriber = $request->only([ @@ -131,60 +42,29 @@ public function store(SubscriberRequest $request) $subscriber['host'] = trim($subscriber['host'], '/') . '/'; if ($this->subscriberRepository->get($subscriber['host'])) { - return response()->error422('host', "Duplicated subscriber."); + return $this->error422('host', "Duplicated subscriber."); } try { $createdSubscriber = $this->subscriberRepository->save((object) $subscriber); } catch (\Exception $e) { - return response()->error500(__('messages.SaveError') . ' ' . json_encode($subscriber)); + return $this->error500(__('messages.SaveError') . ' ' . json_encode($subscriber)); } - return response()->success201("Subscriber successfully saved", "subscriber", $createdSubscriber); + return $this->success201("Subscriber successfully saved", "subscriber", $createdSubscriber); } - /** - * @OA\Delete( - * path="/api/subscribers/{id}", - * summary="Delete the subscriber", - * tags={"subscribers"}, - * security={{"passport":{}}}, - * description="Insert the subscriber id that you want to delete", - * @OA\Parameter( - * in="path", - * required=true, - * description="subscriber id", - * name="id", - * @OA\Schema( - * type="integer", - * minimum=1 - * ) - * ), - * @OA\Response( - * response=500, - * ref="#/components/responses/Error500" - * ), - * @OA\Response( - * response=404, - * ref="#/components/responses/Error404" - * ), - * @OA\Response( - * response=200, - * ref="#/components/responses/Success200" - * ) - * ) - */ public function destroy(int $id) { $subscriber = $this->subscriberRepository->find($id); if (!$subscriber) { - return response()->error404(__('messages.Subscriber') . $id); + return $this->error404(__('messages.Subscriber') . $id); } if (!$this->subscriberRepository->delete($subscriber)) { - return response()->error500(__('messages.DeleteError') . $subscriber); + return $this->error500(__('messages.DeleteError') . $subscriber->id); } - return response()->success200(__('messages.DeleteSuccess'), [ + return $this->success200(__('messages.DeleteSuccess'), [ 'action' => 'DELETE', // TO DO: add user id key value pair 'object_type' => 'subscriber', @@ -192,132 +72,32 @@ public function destroy(int $id) ]); } - /** - * @OA\Get( - * path="/api/subscribers/{id}", - * summary="Find a subscriber by id", - * tags={"subscribers"}, - * security={{"passport":{}}}, - * description="Use to get a subscriber by id", - * operationId="SubscriberController.getSubscriber", - * @OA\Parameter( - * in="path", - * required=true, - * description="Subscriber id", - * name="id", - * @OA\Schema( - * type="integer", - * minimum=1 - * ) - * ), - * @OA\Response( - * response=200, - * ref="#/components/responses/Success200" - * ), - * @OA\Response( - * response=404, - * ref="#/components/responses/Error404" - * ) - * ) - * - * Response to route /api/subscribers/{id} - * - * @param int $id - * @return $subscriber - * - */ - public function getSubscriber($id) + public function getSubscriber(int $id) { $subscriber = $this->subscriberRepository->find($id); if (!$subscriber) { - return response()->error404(__('messages.Subscriber') . $id); + return $this->error404(__('messages.Subscriber') . $id); } return new SubscriberResource($subscriber); } - /** - * @OA\Post( - * path="/api/subscribers/{id}/pause", - * summary="Pauses a subscriber from receiving messages", - * tags={"subscribers"}, - * security={{"passport":{}}}, - * description="Use to pauses a subscriber from receiving messages for 1 hour", - * operationId="SubscriberController.pauseSubscriber", - * @OA\Parameter( - * in="path", - * required=true, - * description="Subscriber id", - * name="id", - * @OA\Schema( - * type="integer", - * minimum=1 - * ) - * ), - * @OA\Response( - * response=200, - * ref="#/components/responses/Success200" - * ), - * @OA\Response( - * response=404, - * ref="#/components/responses/Error404" - * ) - * ) - * - * Response to route /api/subscribers/{id}/pause - * - * @param int $id - * - */ - public function pauseSubscriber($id) + public function pauseSubscriber(int $id) { $subscriber = $this->subscriberRepository->find($id); if (!$subscriber) { - return response()->error404(__('messages.Subscriber') . $id); + return $this->error404(__('messages.Subscriber') . $id); } Cache::put(config('cache.subscriber_paused_key_prefix') . $id, true, config('cache.subscriber_paused_ttl')); - return response()->success204(); + return $this->success204(); } - /** - * @OA\Post( - * path="/api/subscribers/{id}/restore", - * summary="Restore a subscriber from receiving messages", - * tags={"subscribers"}, - * security={{"passport":{}}}, - * description="Use to restore a subscriber from receiving messages", - * operationId="SubscriberController.restoreSubscriber", - * @OA\Parameter( - * in="path", - * required=true, - * description="Subscriber id", - * name="id", - * @OA\Schema( - * type="integer", - * minimum=1 - * ) - * ), - * @OA\Response( - * response=200, - * ref="#/components/responses/Success200" - * ), - * @OA\Response( - * response=404, - * ref="#/components/responses/Error404" - * ) - * ) - * - * Response to route /api/subscribers/{id}/restore - * - * @param int $id - * - */ - public function restoreSubscriber($id) + public function restoreSubscriber(int $id) { $subscriber = $this->subscriberRepository->find($id); if (!$subscriber) { - return response()->error404(__('messages.Subscriber') . $id); + return $this->error404(__('messages.Subscriber') . $id); } Cache::forget(Config::get('cache.subscriber_paused_key_prefix') . $id); - return response()->success204(); + return $this->success204(); } } diff --git a/app/Providers/ResponseMacroServiceProvider.php b/app/Providers/ResponseMacroServiceProvider.php deleted file mode 100644 index b2a3799..0000000 --- a/app/Providers/ResponseMacroServiceProvider.php +++ /dev/null @@ -1,96 +0,0 @@ - $value], 200); - } - return Response::make('', 200); - }); - - Response::macro('success201', function ($value, $type, $object) { - $response = [ - 'message' => $value, - $type => $object - ]; - Log::info('201* ' . json_encode($response)); - return Response::make($response, 201); - }); - - Response::macro('success204', function () { - return Response::make('', 204); - }); - - Response::macro('error401', function ($value = '') { - $message = $value; - Log::error('401* ' . json_encode(['content' => $message])); - return Response::make(['message' => $message], 401); - }); - - Response::macro('error403', function ($value = '', $details = []) { - $message = ($value ? $value : __('messages.Unauthorized')); - Log::error('403* ' . json_encode([ - 'content' => $message, - 'details' => $details - ])); - return Response::make(['message' => $message], 403); - }); - - Response::macro('error404', function ($value = '') { - $message = ($value ? $value : __('messages.Object')) . __('messages.NotFound'); - Log::error('404* ' . json_encode(['content' => $message])); - return Response::make(['message' => $message], 404); - }); - - Response::macro('error409', function ($value = "", $params = []) { - $params['content'] = $value; - Log::error('409* ' . json_encode($params)); - return Response::make(['message' => $value], 409); - }); - - Response::macro('error422', function ($field, $error) { - - if (!$field) { - return Response::make( - [ - 'message' => 'Data is invalid', - 'errors' => $error - ], - 422 - ); - } - - return Response::make( - [ - 'message' => 'Data is invalid', - 'errors' => [ - $field => [$error] - ] - ], - 422 - ); - }); - - Response::macro('error500', function ($value = '') { - $message = $value ? $value : __('messages.SystemError'); - Log::error('500* ' . json_encode(['content' => $message])); - return Response::make(['message' => $message], 500); - }); - } -} diff --git a/app/Providers/ScrambleServiceProvider.php b/app/Providers/ScrambleServiceProvider.php new file mode 100644 index 0000000..d1b6c80 --- /dev/null +++ b/app/Providers/ScrambleServiceProvider.php @@ -0,0 +1,64 @@ +withDocumentTransformers(function (OpenApi $openApi) { + + // INFO: define security schemas + $openApi->secure(SecurityScheme::http('basic')->as("basicAuth")); + $openApi->secure(SecurityScheme::oauth2() + ->as("passport") + ->flow('clientCredentials', function (OAuthFlow $flow) { + $flow + ->tokenUrl(config('app.url') . '/oauth/token') + ->addScope('*', 'all'); + })); + }) + + ->withOperationTransformers(function (Operation $operation, RouteInfo $routeInfo) { + + // INFO: assign security schema based on middleware + $routeMiddlewares = collect($routeInfo->route->gatherMiddleware()); + if ($routeMiddlewares->contains("basicAuth")) { + $operation->addSecurity(new SecurityRequirement(["basicAuth" => []])); + } elseif ($routeMiddlewares->contains("client")) { + $operation->addSecurity(new SecurityRequirement(["passport" => []])); + } else { + $operation->security = []; + } + + // INFO: improve oauth/token route doc + if ($operation->path == "oauth/token" && $operation->method == "post") { + $operation->addRequestBodyObject(RequestBodyObject::make()->setContent( + 'application/json', + Schema::createFromParameters([ + (new Parameter('grant_type', 'query'))->setSchema(Schema::fromType(new StringType))->example("client_credentials"), + (new Parameter('client_id', 'query'))->setSchema(Schema::fromType(new StringType))->example("1"), + (new Parameter('client_secret', 'query'))->setSchema(Schema::fromType(new StringType))->example("secretOAuth2Example"), + (new Parameter('scope', 'query'))->setSchema(Schema::fromType(new StringType))->example(""), + ]) + )); + } + }) + ; + } +} diff --git a/composer.json b/composer.json index 2ec8877..98cef9b 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "license": "MIT", "require": { "php": "^8.2", - "darkaonline/l5-swagger": "^8.3", + "dedoc/scramble": "^0.13.26", "laravel/framework": "^12.0", "laravel/legacy-factories": "^1.1", "laravel/passport": "^12.0", @@ -17,8 +17,7 @@ "laravel/tinker": "^2.0", "laravel/ui": "^4.0", "zanichelli/healthcheck": "^1.1", - "zanichelli/idp-extensions": "^3.8", - "zircote/swagger-php": "4.*" + "zanichelli/idp-extensions": "^3.8" }, "require-dev": { "beyondcode/laravel-dump-server": "^2.1", diff --git a/composer.lock b/composer.lock index ce94207..beaef43 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "3d4d62613976b9efcddda22e8ebb1b49", + "content-hash": "5dfa88acfe012dfa74afdc0ff89e7ee2", "packages": [ { "name": "aws/aws-crt-php", @@ -287,51 +287,53 @@ "time": "2024-02-09T16:56:22+00:00" }, { - "name": "darkaonline/l5-swagger", - "version": "8.6.5", + "name": "dedoc/scramble", + "version": "v0.13.26", "source": { "type": "git", - "url": "https://github.com/DarkaOnLine/L5-Swagger.git", - "reference": "4cf2b3faae9e9cffd05e4eb6e066741bf56f0a85" + "url": "https://github.com/dedoc/scramble.git", + "reference": "5ca42b5e23b9d5c120607138f790b51e22d8b4a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/DarkaOnLine/L5-Swagger/zipball/4cf2b3faae9e9cffd05e4eb6e066741bf56f0a85", - "reference": "4cf2b3faae9e9cffd05e4eb6e066741bf56f0a85", + "url": "https://api.github.com/repos/dedoc/scramble/zipball/5ca42b5e23b9d5c120607138f790b51e22d8b4a1", + "reference": "5ca42b5e23b9d5c120607138f790b51e22d8b4a1", "shasum": "" }, "require": { - "doctrine/annotations": "^1.0 || ^2.0", - "ext-json": "*", - "laravel/framework": "^11.0 || ^10.0 || ^9.0 || >=8.40.0 || ^7.0", - "php": "^7.2 || ^8.0", - "swagger-api/swagger-ui": "^3.0 || >=4.1.3", - "symfony/yaml": "^5.0 || ^6.0 || ^7.0", - "zircote/swagger-php": "^3.2.0 || ^4.0.0" + "illuminate/contracts": "^10.0|^11.0|^12.0|^13.0", + "myclabs/deep-copy": "^1.12", + "nikic/php-parser": "^5.0", + "php": "^8.1", + "phpstan/phpdoc-parser": "^1.0|^2.0", + "spatie/laravel-package-tools": "^1.9.2" }, "require-dev": { - "mockery/mockery": "1.*", - "orchestra/testbench": "^9.0 || ^8.0 || 7.* || ^6.15 || 5.*", - "php-coveralls/php-coveralls": "^2.0", - "phpunit/phpunit": "^11.0 || ^10.0 || ^9.5" + "larastan/larastan": "^3.3", + "laravel/pint": "^v1.1.0", + "nunomaduro/collision": "^7.0|^8.0", + "orchestra/testbench": "^8.0|^9.0|^10.0|^11.0", + "pestphp/pest": "^2.34|^3.7|^4.4", + "pestphp/pest-plugin-laravel": "^2.3|^3.1|^4.1", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^10.5|^11.5.3|^12.5.12", + "spatie/laravel-permission": "^6.10|^7.2", + "spatie/pest-plugin-snapshots": "^2.1" }, "type": "library", "extra": { "laravel": { - "aliases": { - "L5Swagger": "L5Swagger\\L5SwaggerFacade" - }, "providers": [ - "L5Swagger\\L5SwaggerServiceProvider" + "Dedoc\\Scramble\\ScrambleServiceProvider" ] } }, "autoload": { - "files": [ - "src/helpers.php" - ], "psr-4": { - "L5Swagger\\": "src" + "Dedoc\\Scramble\\": "src", + "Dedoc\\Scramble\\Database\\Factories\\": "database/factories" } }, "notification-url": "https://packagist.org/downloads/", @@ -340,31 +342,29 @@ ], "authors": [ { - "name": "Darius Matulionis", - "email": "darius@matulionis.lt" + "name": "Roman Lytvynenko", + "email": "litvinenko95@gmail.com", + "role": "Developer" } ], - "description": "OpenApi or Swagger integration to Laravel", + "description": "Automatic generation of API documentation for Laravel applications.", + "homepage": "https://github.com/dedoc/scramble", "keywords": [ - "api", "documentation", "laravel", - "openapi", - "specification", - "swagger", - "ui" + "openapi" ], "support": { - "issues": "https://github.com/DarkaOnLine/L5-Swagger/issues", - "source": "https://github.com/DarkaOnLine/L5-Swagger/tree/8.6.5" + "issues": "https://github.com/dedoc/scramble/issues", + "source": "https://github.com/dedoc/scramble/tree/v0.13.26" }, "funding": [ { - "url": "https://github.com/DarkaOnLine", + "url": "https://github.com/romalytvynenko", "type": "github" } ], - "time": "2025-02-06T14:54:32+00:00" + "time": "2026-06-02T14:43:17+00:00" }, { "name": "defuse/php-encryption", @@ -508,82 +508,6 @@ }, "time": "2024-07-08T12:26:09+00:00" }, - { - "name": "doctrine/annotations", - "version": "2.0.2", - "source": { - "type": "git", - "url": "https://github.com/doctrine/annotations.git", - "reference": "901c2ee5d26eb64ff43c47976e114bf00843acf7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/901c2ee5d26eb64ff43c47976e114bf00843acf7", - "reference": "901c2ee5d26eb64ff43c47976e114bf00843acf7", - "shasum": "" - }, - "require": { - "doctrine/lexer": "^2 || ^3", - "ext-tokenizer": "*", - "php": "^7.2 || ^8.0", - "psr/cache": "^1 || ^2 || ^3" - }, - "require-dev": { - "doctrine/cache": "^2.0", - "doctrine/coding-standard": "^10", - "phpstan/phpstan": "^1.10.28", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "symfony/cache": "^5.4 || ^6.4 || ^7", - "vimeo/psalm": "^4.30 || ^5.14" - }, - "suggest": { - "php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "Docblock Annotations Parser", - "homepage": "https://www.doctrine-project.org/projects/annotations.html", - "keywords": [ - "annotations", - "docblock", - "parser" - ], - "support": { - "issues": "https://github.com/doctrine/annotations/issues", - "source": "https://github.com/doctrine/annotations/tree/2.0.2" - }, - "time": "2024-09-05T10:17:24+00:00" - }, { "name": "doctrine/inflector", "version": "2.0.10", @@ -3210,6 +3134,66 @@ }, "time": "2024-09-04T18:46:31+00:00" }, + { + "name": "myclabs/deep-copy", + "version": "1.13.1", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c", + "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.1" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2025-04-29T12:36:36+00:00" + }, { "name": "nesbot/carbon", "version": "3.9.1", @@ -3990,53 +3974,51 @@ "time": "2024-12-14T21:12:59+00:00" }, { - "name": "psr/cache", - "version": "3.0.0", + "name": "phpstan/phpdoc-parser", + "version": "2.3.2", "source": { "type": "git", - "url": "https://github.com/php-fig/cache.git", - "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", - "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/a004701b11273a26cd7955a61d67a7f1e525a45a", + "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a", "shasum": "" }, "require": { - "php": ">=8.0.0" + "php": "^7.4 || ^8.0" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^5.3.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", + "symfony/process": "^5.2" }, + "type": "library", "autoload": { "psr-4": { - "Psr\\Cache\\": "src/" + "PHPStan\\PhpDocParser\\": [ + "src/" + ] } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for caching libraries", - "keywords": [ - "cache", - "psr", - "psr-6" - ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { - "source": "https://github.com/php-fig/cache/tree/3.0.0" + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.2" }, - "time": "2021-02-03T23:26:27+00:00" + "time": "2026-01-25T14:56:51+00:00" }, { "name": "psr/clock", @@ -4742,65 +4724,65 @@ "time": "2024-04-27T21:32:50+00:00" }, { - "name": "swagger-api/swagger-ui", - "version": "v5.21.0", + "name": "spatie/laravel-package-tools", + "version": "1.93.1", "source": { "type": "git", - "url": "https://github.com/swagger-api/swagger-ui.git", - "reference": "fceaec605072fbc717a04895bd19814d9a1c8e6d" + "url": "https://github.com/spatie/laravel-package-tools.git", + "reference": "d5552849801f2642aea710557463234b59ef65eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/swagger-api/swagger-ui/zipball/fceaec605072fbc717a04895bd19814d9a1c8e6d", - "reference": "fceaec605072fbc717a04895bd19814d9a1c8e6d", + "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/d5552849801f2642aea710557463234b59ef65eb", + "reference": "d5552849801f2642aea710557463234b59ef65eb", "shasum": "" }, + "require": { + "illuminate/contracts": "^10.0|^11.0|^12.0|^13.0", + "php": "^8.1" + }, + "require-dev": { + "mockery/mockery": "^1.5", + "orchestra/testbench": "^8.0|^9.2|^10.0|^11.0", + "pestphp/pest": "^2.1|^3.1|^4.0", + "phpunit/php-code-coverage": "^10.0|^11.0|^12.0", + "phpunit/phpunit": "^10.5|^11.5|^12.5", + "spatie/pest-plugin-test-time": "^2.2|^3.0" + }, "type": "library", + "autoload": { + "psr-4": { + "Spatie\\LaravelPackageTools\\": "src" + } + }, "notification-url": "https://packagist.org/downloads/", "license": [ - "Apache-2.0" + "MIT" ], "authors": [ { - "name": "Anna Bodnia", - "email": "anna.bodnia@gmail.com" - }, - { - "name": "Buu Nguyen", - "email": "buunguyen@gmail.com" - }, - { - "name": "Josh Ponelat", - "email": "jponelat@gmail.com" - }, - { - "name": "Kyle Shockey", - "email": "kyleshockey1@gmail.com" - }, - { - "name": "Robert Barnwell", - "email": "robert@robertismy.name" - }, - { - "name": "Sahar Jafari", - "email": "shr.jafari@gmail.com" + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "role": "Developer" } ], - "description": " Swagger UI is a collection of HTML, Javascript, and CSS assets that dynamically generate beautiful documentation from a Swagger-compliant API.", - "homepage": "http://swagger.io", + "description": "Tools for creating Laravel packages", + "homepage": "https://github.com/spatie/laravel-package-tools", "keywords": [ - "api", - "documentation", - "openapi", - "specification", - "swagger", - "ui" + "laravel-package-tools", + "spatie" ], "support": { - "issues": "https://github.com/swagger-api/swagger-ui/issues", - "source": "https://github.com/swagger-api/swagger-ui/tree/v5.21.0" + "issues": "https://github.com/spatie/laravel-package-tools/issues", + "source": "https://github.com/spatie/laravel-package-tools/tree/1.93.1" }, - "time": "2025-04-13T19:37:38+00:00" + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2026-05-19T14:06:37+00:00" }, { "name": "symfony/clock", @@ -7114,78 +7096,6 @@ ], "time": "2025-04-09T08:14:01+00:00" }, - { - "name": "symfony/yaml", - "version": "v7.2.6", - "source": { - "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "0feafffb843860624ddfd13478f481f4c3cd8b23" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/0feafffb843860624ddfd13478f481f4c3cd8b23", - "reference": "0feafffb843860624ddfd13478f481f4c3cd8b23", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3.0", - "symfony/polyfill-ctype": "^1.8" - }, - "conflict": { - "symfony/console": "<6.4" - }, - "require-dev": { - "symfony/console": "^6.4|^7.0" - }, - "bin": [ - "Resources/bin/yaml-lint" - ], - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Yaml\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Loads and dumps YAML files", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/yaml/tree/v7.2.6" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-04-04T10:10:11+00:00" - }, { "name": "tijsverkoyen/css-to-inline-styles", "version": "v2.3.0", @@ -7562,87 +7472,6 @@ "source": "https://github.com/ZanichelliEditore/idp-extension/tree/v3.8.0" }, "time": "2025-05-06T12:44:57+00:00" - }, - { - "name": "zircote/swagger-php", - "version": "4.11.1", - "source": { - "type": "git", - "url": "https://github.com/zircote/swagger-php.git", - "reference": "7df10e8ec47db07c031db317a25bef962b4e5de1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/zircote/swagger-php/zipball/7df10e8ec47db07c031db317a25bef962b4e5de1", - "reference": "7df10e8ec47db07c031db317a25bef962b4e5de1", - "shasum": "" - }, - "require": { - "ext-json": "*", - "php": ">=7.2", - "psr/log": "^1.1 || ^2.0 || ^3.0", - "symfony/deprecation-contracts": "^2 || ^3", - "symfony/finder": ">=2.2", - "symfony/yaml": ">=3.3" - }, - "require-dev": { - "composer/package-versions-deprecated": "^1.11", - "doctrine/annotations": "^1.7 || ^2.0", - "friendsofphp/php-cs-fixer": "^2.17 || 3.62.0", - "phpstan/phpstan": "^1.6", - "phpunit/phpunit": ">=8", - "vimeo/psalm": "^4.23" - }, - "suggest": { - "doctrine/annotations": "^1.7 || ^2.0" - }, - "bin": [ - "bin/openapi" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.x-dev" - } - }, - "autoload": { - "psr-4": { - "OpenApi\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Robert Allen", - "email": "zircote@gmail.com" - }, - { - "name": "Bob Fanger", - "email": "bfanger@gmail.com", - "homepage": "https://bfanger.nl" - }, - { - "name": "Martin Rademacher", - "email": "mano@radebatz.net", - "homepage": "https://radebatz.net" - } - ], - "description": "swagger-php - Generate interactive documentation for your RESTful API using phpdoc annotations", - "homepage": "https://github.com/zircote/swagger-php/", - "keywords": [ - "api", - "json", - "rest", - "service discovery" - ], - "support": { - "issues": "https://github.com/zircote/swagger-php/issues", - "source": "https://github.com/zircote/swagger-php/tree/4.11.1" - }, - "time": "2024-10-15T19:20:02+00:00" } ], "packages-dev": [ @@ -7982,66 +7811,6 @@ }, "time": "2024-05-16T03:13:13+00:00" }, - { - "name": "myclabs/deep-copy", - "version": "1.13.1", - "source": { - "type": "git", - "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c", - "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "conflict": { - "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3 <3.2.2" - }, - "require-dev": { - "doctrine/collections": "^1.6.8", - "doctrine/common": "^2.13.3 || ^3.2.2", - "phpspec/prophecy": "^1.10", - "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" - }, - "type": "library", - "autoload": { - "files": [ - "src/DeepCopy/deep_copy.php" - ], - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Create deep copies (clones) of your objects", - "keywords": [ - "clone", - "copy", - "duplicate", - "object", - "object graph" - ], - "support": { - "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.1" - }, - "funding": [ - { - "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", - "type": "tidelift" - } - ], - "time": "2025-04-29T12:36:36+00:00" - }, { "name": "nunomaduro/collision", "version": "v8.8.0", @@ -9729,5 +9498,5 @@ "php": "^8.2" }, "platform-dev": {}, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } diff --git a/config/app.php b/config/app.php index 310247a..22383b5 100644 --- a/config/app.php +++ b/config/app.php @@ -161,7 +161,6 @@ Illuminate\Translation\TranslationServiceProvider::class, Illuminate\Validation\ValidationServiceProvider::class, Illuminate\View\ViewServiceProvider::class, - L5Swagger\L5SwaggerServiceProvider::class, /* * Package Service Providers... @@ -176,7 +175,7 @@ App\Providers\EventServiceProvider::class, App\Providers\TelescopeServiceProvider::class, App\Providers\RouteServiceProvider::class, - App\Providers\ResponseMacroServiceProvider::class, + App\Providers\ScrambleServiceProvider::class, ], diff --git a/config/l5-swagger.php b/config/l5-swagger.php deleted file mode 100644 index fe4aeea..0000000 --- a/config/l5-swagger.php +++ /dev/null @@ -1,310 +0,0 @@ - 'default', - 'documentations' => [ - 'default' => [ - 'api' => [ - 'title' => 'L5 Swagger UI', - ], - - 'routes' => [ - /* - * Route for accessing api documentation interface - */ - 'api' => 'api/documentation', - ], - 'paths' => [ - /* - * Edit to include full URL in ui for assets - */ - 'use_absolute_path' => env('L5_SWAGGER_USE_ABSOLUTE_PATH', true), - - /* - * File name of the generated json documentation file - */ - 'docs_json' => 'api-docs.json', - - /* - * File name of the generated YAML documentation file - */ - 'docs_yaml' => 'api-docs.yaml', - - /* - * Set this to `json` or `yaml` to determine which documentation file to use in UI - */ - 'format_to_use_for_docs' => env('L5_FORMAT_TO_USE_FOR_DOCS', 'json'), - - /* - * Absolute paths to directory containing the swagger annotations are stored. - */ - 'annotations' => [ - base_path('app'), - ], - - ], - ], - ], - 'defaults' => [ - 'routes' => [ - /* - * Route for accessing parsed swagger annotations. - */ - 'docs' => 'docs', - - /* - * Route for Oauth2 authentication callback. - */ - 'oauth2_callback' => 'api/oauth2-callback', - - /* - * Middleware allows to prevent unexpected access to API documentation - */ - 'middleware' => [ - 'api' => [], - 'asset' => [], - 'docs' => [], - 'oauth2_callback' => [], - ], - - /* - * Route Group options - */ - 'group_options' => [], - ], - - 'paths' => [ - /* - * Absolute path to location where parsed annotations will be stored - */ - 'docs' => storage_path('api-docs'), - - /* - * Absolute path to directory where to export views - */ - 'views' => base_path('resources/views/vendor/l5-swagger'), - - /* - * Edit to set the api's base path - */ - 'base' => env('L5_SWAGGER_BASE_PATH', null), - - /* - * Edit to set path where swagger ui assets should be stored - */ - 'swagger_ui_assets_path' => env('L5_SWAGGER_UI_ASSETS_PATH', 'vendor/swagger-api/swagger-ui/dist/'), - - /* - * Absolute path to directories that should be exclude from scanning - * @deprecated Please use `scanOptions.exclude` - * `scanOptions.exclude` overwrites this - */ - 'excludes' => [], - ], - - 'scanOptions' => [ - /** - * analyser: defaults to \OpenApi\StaticAnalyser . - * - * @see \OpenApi\scan - */ - 'analyser' => null, - - /** - * analysis: defaults to a new \OpenApi\Analysis . - * - * @see \OpenApi\scan - */ - 'analysis' => null, - - /** - * Custom query path processors classes. - * - * @link https://github.com/zircote/swagger-php/tree/master/Examples/schema-query-parameter-processor - * @see \OpenApi\scan - */ - 'processors' => [ - // new \App\SwaggerProcessors\SchemaQueryParameter(), - ], - - /** - * pattern: string $pattern File pattern(s) to scan (default: *.php) . - * - * @see \OpenApi\scan - */ - 'pattern' => null, - - /* - * Absolute path to directories that should be exclude from scanning - * @note This option overwrites `paths.excludes` - * @see \OpenApi\scan - */ - 'exclude' => [], - - /* - * Allows to generate specs either for OpenAPI 3.0.0 or OpenAPI 3.1.0. - * By default the spec will be in version 3.0.0 - */ - 'open_api_spec_version' => env('L5_SWAGGER_OPEN_API_SPEC_VERSION', \L5Swagger\Generator::OPEN_API_DEFAULT_SPEC_VERSION), - ], - - /* - * API security definitions. Will be generated into documentation file. - */ - 'securityDefinitions' => [ - 'securitySchemes' => [ - /* - * Examples of Security schemes - */ - /* - 'api_key_security_example' => [ // Unique name of security - 'type' => 'apiKey', // The type of the security scheme. Valid values are "basic", "apiKey" or "oauth2". - 'description' => 'A short description for security scheme', - 'name' => 'api_key', // The name of the header or query parameter to be used. - 'in' => 'header', // The location of the API key. Valid values are "query" or "header". - ], - 'oauth2_security_example' => [ // Unique name of security - 'type' => 'oauth2', // The type of the security scheme. Valid values are "basic", "apiKey" or "oauth2". - 'description' => 'A short description for oauth2 security scheme.', - 'flow' => 'implicit', // The flow used by the OAuth2 security scheme. Valid values are "implicit", "password", "application" or "accessCode". - 'authorizationUrl' => 'http://example.com/auth', // The authorization URL to be used for (implicit/accessCode) - //'tokenUrl' => 'http://example.com/auth' // The authorization URL to be used for (password/application/accessCode) - 'scopes' => [ - 'read:projects' => 'read your projects', - 'write:projects' => 'modify projects in your account', - ] - ], - */ - 'passport' => [ - 'description' => 'Laravel passport oauth2 security.', - 'type' => 'oauth2', - 'in' => 'header', - 'scheme' => 'https', - 'flows' => [ - "clientCredentials" => [ - "tokenUrl" => config('app.url') . '/oauth/token', - "scopes" => [] - ] - ], - ], - /* Open API 3.0 support - 'passport' => [ // Unique name of security - 'type' => 'oauth2', // The type of the security scheme. Valid values are "basic", "apiKey" or "oauth2". - 'description' => 'Laravel passport oauth2 security.', - 'in' => 'header', - 'scheme' => 'https', - 'flows' => [ - "password" => [ - "authorizationUrl" => config('app.url') . '/oauth/authorize', - "tokenUrl" => config('app.url') . '/oauth/token', - "refreshUrl" => config('app.url') . '/token/refresh', - "scopes" => [] - ], - ], - ],/* - 'sanctum' => [ // Unique name of security - 'type' => 'apiKey', // Valid values are "basic", "apiKey" or "oauth2". - 'description' => 'Enter token in format (Bearer )', - 'name' => 'Authorization', // The name of the header or query parameter to be used. - 'in' => 'header', // The location of the API key. Valid values are "query" or "header". - ],*/ - ], - 'security' => [ - /* - * Examples of Securities - */ - [ - /* - 'oauth2_security_example' => [ - 'read', - 'write' - ], - - 'passport' => [] - */], - ], - ], - - /* - * Set this to `true` in development mode so that docs would be regenerated on each request - * Set this to `false` to disable swagger generation on production - */ - 'generate_always' => env('L5_SWAGGER_GENERATE_ALWAYS', false), - - /* - * Set this to `true` to generate a copy of documentation in yaml format - */ - 'generate_yaml_copy' => env('L5_SWAGGER_GENERATE_YAML_COPY', false), - - /* - * Edit to trust the proxy's ip address - needed for AWS Load Balancer - * string[] - */ - 'proxy' => false, - - /* - * Configs plugin allows to fetch external configs instead of passing them to SwaggerUIBundle. - * See more at: https://github.com/swagger-api/swagger-ui#configs-plugin - */ - 'additional_config_url' => null, - - /* - * Apply a sort to the operation list of each API. It can be 'alpha' (sort by paths alphanumerically), - * 'method' (sort by HTTP method). - * Default is the order returned by the server unchanged. - */ - 'operations_sort' => env('L5_SWAGGER_OPERATIONS_SORT', 'method'), - - /* - * Pass the validatorUrl parameter to SwaggerUi init on the JS side. - * A null value here disables validation. - */ - 'validator_url' => null, - - /* - * Swagger UI configuration parameters - */ - 'ui' => [ - 'display' => [ - /* - * Controls the default expansion setting for the operations and tags. It can be : - * 'list' (expands only the tags), - * 'full' (expands the tags and operations), - * 'none' (expands nothing). - */ - 'doc_expansion' => env('L5_SWAGGER_UI_DOC_EXPANSION', 'none'), - - /** - * If set, enables filtering. The top bar will show an edit box that - * you can use to filter the tagged operations that are shown. Can be - * Boolean to enable or disable, or a string, in which case filtering - * will be enabled using that string as the filter expression. Filtering - * is case-sensitive matching the filter expression anywhere inside - * the tag. - */ - 'filter' => env('L5_SWAGGER_UI_FILTERS', true), // true | false - ], - - 'authorization' => [ - /* - * If set to true, it persists authorization data, and it would not be lost on browser close/refresh - */ - 'persist_authorization' => env('L5_SWAGGER_UI_PERSIST_AUTHORIZATION', false), - - 'oauth2' => [ - /* - * If set to true, adds PKCE to AuthorizationCodeGrant flow - */ - 'use_pkce_with_authorization_code_grant' => false, - ], - ], - ], - /* - * Constants which can be used in annotations - */ - 'constants' => [ - 'L5_SWAGGER_CONST_HOST' => env('APP_URL', 'http://localhost:8085'), - 'L5_SWAGGER_CONST_TOKEN_URL' => env('L5_SWAGGER_CONST_TOKEN_URL', config('app.url') . '/oauth/token'), - ], - ], -]; diff --git a/config/scramble.php b/config/scramble.php new file mode 100644 index 0000000..56714dd --- /dev/null +++ b/config/scramble.php @@ -0,0 +1,184 @@ + [ + * 'include' => 'api', + * 'exclude' => ['api/internal'], + * ], + * + * Without *, patterns match path segments (api matches api and api/users, not apiary). + * With *, Str::is is used (e.g. api/v*). + * + * One static include → default server is /{include} and paths are stripped (/users). + * Multiple includes or wildcards → server defaults to / and paths stay full (/api/users). + * Override with `servers`, or use Scramble::registerApi() for separate bases. + */ + 'api_path' => [ + 'include' => [ + 'api', + 'oauth/token' + ], + 'exclude' => [ + 'api/documentation', // l5-swagger UI + 'api/oauth2-callback', // l5-swagger OAuth callback + 'docs', // l5-swagger docs JSON + 'api/debug', // Debug endpoint + 'api/basic', // Test endpoint + 'api/oauth', // Test endpoint + 'api/none', // Test endpoint + 'oauth/token/refresh' + ], + ], + + /* + * Your API domain. By default, app domain is used. This is also a part of the default API routes + * matcher, so when implementing your own, make sure you use this config if needed. + */ + 'api_domain' => null, + + /* + * The path where your OpenAPI specification will be exported. + */ + 'export_path' => 'api.json', + + 'info' => [ + /* + * API version. + */ + 'version' => env('API_VERSION', '1.0.0'), + + /* + * Description rendered on the home page of the API documentation (`/docs/api`). + */ + 'description' => 'Auto-generated API documentation for Buzzer - A publish/subscribe messaging system', + ], + + 'ui' => [ + 'title' => 'Buzzer API - Scramble Documentation', + ], + + 'renderer' => 'elements', + + 'renderers' => [ + /* + * Stoplight Elements config options: https://docs.stoplight.io/docs/elements/b074dc47b2826-elements-configuration-options + */ + 'elements' => [ + 'view' => 'scramble::docs', + 'theme' => 'light', + 'hideTryIt' => false, + 'hideSchemas' => false, + 'logo' => '', + 'tryItCredentialsPolicy' => 'include', + 'layout' => 'responsive', + 'router' => 'hash', + ], + /* + * Scalar API reference config options: https://scalar.com/products/api-references/configuration + */ + 'scalar' => [ + 'view' => 'scramble::scalar', + 'cdn' => 'https://cdn.jsdelivr.net/npm/@scalar/api-reference', + 'theme' => 'laravel', + 'proxyUrl' => 'https://proxy.scalar.com', + 'darkMode' => false, + 'showDeveloperTools' => 'never', + 'agent' => ['disabled' => true], + 'credentials' => 'include', + ], + ], + + /* + * The list of servers of the API. By default, when `null`, server URL will be created from + * `scramble.api_path` and `scramble.api_domain` config variables. When providing an array, you + * will need to specify the local server URL manually (if needed). + * + * Example of non-default config (final URLs are generated using Laravel `url` helper): + * + * ```php + * 'servers' => [ + * 'Live' => 'api', + * 'Prod' => 'https://scramble.dedoc.co/api', + * ], + * ``` + */ + 'servers' => [], + + /** + * Determines how Scramble stores the descriptions of enum cases. + * Available options: + * - 'description' – Case descriptions are stored as the enum schema's description using table formatting. + * - 'extension' – Case descriptions are stored in the `x-enumDescriptions` enum schema extension. + * + * @see https://redocly.com/docs-legacy/api-reference-docs/specification-extensions/x-enum-descriptions + * - false - Case descriptions are ignored. + */ + 'enum_cases_description_strategy' => 'description', + + /** + * Determines how Scramble stores the names of enum cases. + * Available options: + * - 'names' – Case names are stored in the `x-enumNames` enum schema extension. + * - 'varnames' - Case names are stored in the `x-enum-varnames` enum schema extension. + * - false - Case names are not stored. + */ + 'enum_cases_names_strategy' => false, + + /** + * When Scramble encounters deep objects in query parameters, it flattens the parameters so the generated + * OpenAPI document correctly describes the API. Flattening deep query parameters is relevant until + * OpenAPI 3.2 is released and query string structure can be described properly. + * + * For example, this nested validation rule describes the object with `bar` property: + * `['foo.bar' => ['required', 'int']]`. + * + * When `flatten_deep_query_parameters` is `true`, Scramble will document the parameter like so: + * `{"name":"foo[bar]", "schema":{"type":"int"}, "required":true}`. + * + * When `flatten_deep_query_parameters` is `false`, Scramble will document the parameter like so: + * `{"name":"foo", "schema": {"type":"object", "properties":{"bar":{"type": "int"}}, "required": ["bar"]}, "required":true}`. + */ + 'flatten_deep_query_parameters' => true, + + 'middleware' => [ + // Empty to allow public access like l5-swagger + // 'web' middleware would trigger IDP authentication + ], + + 'extensions' => [], + + /* + * Security schemes for the API. Defines the authentication methods available. + */ + 'security_schemes' => [], + + /* + * Automatically document API security (OpenAPI `security` / `securitySchemes`) based on route + * middleware. + * + * Disabled by default. Uncomment the line below to enable `MiddlewareAuthSecurityStrategy`. + * When at least one documented route uses middleware matching the configured patterns (by default + * `auth` and `auth:*`), bearer auth is applied globally. Routes without matching middleware are + * marked as public (`security: []`). + * + * Set to `null` explicitly to disable. If you already configure security manually via + * `afterOpenApiGenerated` / `extendOpenApi`, keep this disabled to avoid duplicate schemes. + * + * Customize with a class-string or [class, options]: + * + * 'security_strategy' => [ + * \Dedoc\Scramble\SecurityDocumentation\MiddlewareAuthSecurityStrategy::class, + * [ + * 'middleware' => ['auth', 'auth:*'], + * 'scheme' => \Dedoc\Scramble\Support\Generator\SecurityScheme::http('bearer'), + * ], + * ], + */ + // 'security_strategy' => \Dedoc\Scramble\SecurityDocumentation\MiddlewareAuthSecurityStrategy::class, + 'security_strategy' => null, +]; diff --git a/resources/views/vendor/scramble/docs.blade.php b/resources/views/vendor/scramble/docs.blade.php new file mode 100644 index 0000000..a79fca8 --- /dev/null +++ b/resources/views/vendor/scramble/docs.blade.php @@ -0,0 +1,510 @@ + + + + + + + {{ $config->get('ui.title') ?? config('app.name') . ' - API Docs' }} + + + + + + + + + + + + + + + + +renderer()->all(except: ['theme']) as $key => $value) + @continue(! $value) + {{ $key }}="{{ $value === true ? 'true' : ($value === false ? 'false' : $value) }}" + @endforeach +/> + + +@if($config->renderer()->get('theme', 'light') === 'system') + +@endif + + + + + diff --git a/routes/api.php b/routes/api.php index 42c8a78..cf22fef 100644 --- a/routes/api.php +++ b/routes/api.php @@ -5,8 +5,8 @@ use Illuminate\Support\Facades\Route; Route::middleware(['basicAuth'])->group(function () { - Route::post('/sendMessage', 'ChannelController@SendMessage'); - Route::post('/sendMessage/{channelName}', 'ChannelController@SendMessageToChannel'); + Route::post('/sendMessage', 'ChannelController@sendMessage'); + Route::post('/sendMessage/{channelName}', 'ChannelController@sendMessageToChannel'); }); Route::post('/logout-idp', 'Auth\LoginController@logoutIdp')->name('logoutIdp'); diff --git a/routes/web.php b/routes/web.php index 51bf6b7..43740a6 100644 --- a/routes/web.php +++ b/routes/web.php @@ -70,7 +70,7 @@ Route::get('/{any}', function () { return view('app'); - })->where("any", ".*"); + })->where("any", "^(?!docs).*"); // Exclude /docs/* paths (for Scramble documentation) }); }); diff --git a/tests/Feature/ScrambleTest.php b/tests/Feature/ScrambleTest.php new file mode 100644 index 0000000..fab1b6c --- /dev/null +++ b/tests/Feature/ScrambleTest.php @@ -0,0 +1,20 @@ +withMiddleware()->get('docs/api'); + $response->assertStatus(200); + } + + public function testScrambleJsonRoute(): void + { + $response = $this->withMiddleware()->get('docs/api.json'); + $response->assertStatus(200); + } +} diff --git a/tests/Feature/SwaggerTest.php b/tests/Feature/SwaggerTest.php deleted file mode 100644 index c766c62..0000000 --- a/tests/Feature/SwaggerTest.php +++ /dev/null @@ -1,26 +0,0 @@ -withMiddleware()->get('/docs?api-docs.json'); - $response->assertStatus(200); - } - - public function testSwaggerRoute() - { - $response = $this->withMiddleware()->get('/api/documentation'); - $response->assertStatus(200); - } -}