diff --git a/composer.json b/composer.json index cc4e39c..da9a5c3 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,8 @@ "laravel/fortify": "^1.34", "laravel/framework": "^13.0", "laravel/tinker": "^3.0", - "laravel/wayfinder": "^0.1.14" + "laravel/wayfinder": "^0.1.14", + "scalar/laravel": "^0.2.1" }, "require-dev": { "fakerphp/faker": "^1.24", diff --git a/composer.lock b/composer.lock index 8f8e5d1..aa93906 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": "c55625a323719bb164dc4b9b763d6ede", + "content-hash": "896a8244956aa86a482b054e2616a90f", "packages": [ { "name": "bacon/bacon-qr-code", @@ -3770,6 +3770,136 @@ }, "time": "2025-12-14T04:43:48+00:00" }, + { + "name": "scalar/laravel", + "version": "0.2.1", + "source": { + "type": "git", + "url": "https://github.com/scalar/laravel.git", + "reference": "5f6dace4740a082df5058804430f12af01925810" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/scalar/laravel/zipball/5f6dace4740a082df5058804430f12af01925810", + "reference": "5f6dace4740a082df5058804430f12af01925810", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^10.0||^11.0||^12.0||^13.0", + "php": "^8.2||^8.3", + "spatie/laravel-package-tools": "^1.16" + }, + "require-dev": { + "laravel/pint": "^1.14", + "nunomaduro/collision": "^8.7.0||^7.10.0", + "orchestra/testbench": "^8.0||^9.0||^10.0||^11.0", + "pestphp/pest": "^2.3.4||^3.7.4||^4.0", + "pestphp/pest-plugin-arch": "^2.7.0||^3.0.0||^4.0", + "pestphp/pest-plugin-laravel": "^2.3.0||^3.1.0||^4.0" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "Scalar": "Scalar\\Facades\\Scalar" + }, + "providers": [ + "Scalar\\ScalarServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Scalar\\": "src/", + "Scalar\\Database\\Factories\\": "database/factories/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Hans Pagel", + "email": "mail@hanspagel.com", + "role": "Developer" + } + ], + "description": "Render your OpenAPI-based API reference", + "homepage": "https://github.com/scalar/laravel", + "keywords": [ + "laravel", + "openapi", + "scalar", + "swagger" + ], + "support": { + "issues": "https://github.com/scalar/laravel/issues", + "source": "https://github.com/scalar/laravel/tree/0.2.1" + }, + "time": "2026-03-23T14:14:34+00:00" + }, + { + "name": "spatie/laravel-package-tools", + "version": "1.93.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-package-tools.git", + "reference": "0d097bce95b2bf6802fb1d83e1e753b0f5a948e7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/0d097bce95b2bf6802fb1d83e1e753b0f5a948e7", + "reference": "0d097bce95b2bf6802fb1d83e1e753b0f5a948e7", + "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": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "role": "Developer" + } + ], + "description": "Tools for creating Laravel packages", + "homepage": "https://github.com/spatie/laravel-package-tools", + "keywords": [ + "laravel-package-tools", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-package-tools/issues", + "source": "https://github.com/spatie/laravel-package-tools/tree/1.93.0" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2026-02-21T12:49:54+00:00" + }, { "name": "symfony/clock", "version": "v8.0.8", diff --git a/config/scalar.php b/config/scalar.php new file mode 100644 index 0000000..23e1e05 --- /dev/null +++ b/config/scalar.php @@ -0,0 +1,198 @@ + null, + + /* + |-------------------------------------------------------------------------- + | Scalar Path + |-------------------------------------------------------------------------- + | + | This is the URI path where Scalar will be accessible from. Feel free + | to change this path to anything you like. Note that the URI will not + | affect the paths of its internal API that aren't exposed to users. + | + */ + 'path' => '/api/docs', + + /* + |-------------------------------------------------------------------------- + | Scalar Route Middleware + |-------------------------------------------------------------------------- + | + | These middleware will get attached onto each Scalar route, giving you + | the chance to add your own middleware to this list or change any of + | the existing middleware. Or, you can simply stick with this list. + | + */ + 'middleware' => ['web'], + + /* + |-------------------------------------------------------------------------- + | Scalar OpenAPI Document URL + |-------------------------------------------------------------------------- + | + | This is the URL to the OpenAPI document that Scalar will use to generate + | the API reference. By default, it points to the latest version of the + | Scalar Galaxy package. You can change this to use a custom OpenAPI file. + | + */ + 'url' => '/api/docs/openapi.json', + + /* + |-------------------------------------------------------------------------- + | Scalar CDN URL + |-------------------------------------------------------------------------- + | + | This is the URL to the CDN where Scalar's API reference assets are hosted. + | By default, it points to the jsDelivr CDN for the @scalar/api-reference + | package. You can change this if you want to use a different CDN. + | + */ + 'cdn' => 'https://cdn.jsdelivr.net/npm/@scalar/api-reference', + + /* + |-------------------------------------------------------------------------- + | Scalar Configuration + |-------------------------------------------------------------------------- + | + | The configuration options for the Scalar API reference. This array + | contains all the settings that control the behavior and appearance + | of the API documentation. + | + */ + 'configuration' => [ + /** A string to use one of the color presets */ + 'theme' => + // 'alternate', + // 'bluePlanet', + // 'deepSpace', + // 'default', + // 'kepler', + 'laravel', + // 'mars', + // 'moon', + // 'purple', + // 'saturn', + // 'solarized', + // 'none', + + /** The layout to use for the references */ + 'layout' => 'modern', + + /** URL to a request proxy for the API client */ + 'proxyUrl' => 'https://proxy.scalar.com', + + /** Whether to show the sidebar */ + 'showSidebar' => true, + + /** + * Whether to show models in the sidebar, search, and content. + */ + 'hideModels' => false, + + /** + * Whether to show the “Download OpenAPI Document” button + */ + 'hideDownloadButton' => false, + + /** + * Whether to show the “Test Request” button + */ + 'hideTestRequestButton' => true, + + /** + * Whether to show the sidebar search bar + */ + 'hideSearch' => false, + + /** Whether dark mode is on or off initially (light mode) */ + 'darkMode' => false, + + /** forceDarkModeState makes it always this state no matter what*/ + 'forceDarkModeState' => 'dark', + + /** Whether to show the dark mode toggle */ + 'hideDarkModeToggle' => false, + + /** Key used with CTRL/CMD to open the search modal (defaults to 'k' e.g. CMD+k) */ + 'searchHotKey' => 'k', + + /** + * If used, passed data will be added to the HTML header + * + * @see https://unhead.unjs.io/usage/composables/use-seo-meta + */ + 'metaData' => [ + 'title' => config('app.name').' API Reference', + ], + + /** + * Path to a favicon image + * + * @example '/favicon.svg' + */ + 'favicon' => '', + + /** + * List of httpsnippet clients to hide from the clients menu + * By default hides Unirest, pass `[]` to show all clients + */ + 'hiddenClients' => [ + + ], + + /** Determine the HTTP client that’s selected by default */ + 'defaultHttpClient' => [ + 'targetId' => 'shell', + 'clientKey' => 'curl', + ], + + /** Custom CSS to be added to the page */ + // 'customCss' => '', + + /** Prefill authentication */ + // 'authentication' => [ + // // TODO + // ], + + /** + * The baseServerURL is used when the spec servers are relative paths and we are using SSR. + * On the client we can grab the window.location.origin but on the server we need + * to use this prop. + */ + // 'baseServerURL' => '', + + /** + * List of servers to override the openapi spec servers + */ + // 'servers' => [ + // [ + // 'url' => 'https://api.scalar.com', + // 'description' => 'Production server', + // ], + // ], + + /** + * We’re using Inter and JetBrains Mono as the default fonts. If you want to use your own fonts, set this to false. + */ + 'withDefaultFonts' => true, + + /** + * By default we only open the relevant tag based on the url, however if you want all the tags open by default then set this configuration option :) + */ + 'defaultOpenAllTags' => false, + ], + +]; diff --git a/routes/web.php b/routes/web.php index d12eeb7..ec9722b 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,5 +1,7 @@ Features::enabled(Features::registration()), ])->name('home'); +Route::get('api/docs/openapi.json', function (): Response { + $path = base_path('docs/openapi/kanbangit-api.openapi.json'); + + abort_unless(File::exists($path), 404); + + return response(File::get($path), 200, [ + 'Content-Type' => 'application/vnd.oai.openapi+json; charset=utf-8', + ]); +})->name('api.docs.openapi'); + Route::middleware(['auth', 'verified'])->group(function () { Route::inertia('dashboard', 'Dashboard')->name('dashboard'); diff --git a/tests/Feature/Api/PublicApiDocumentationTest.php b/tests/Feature/Api/PublicApiDocumentationTest.php new file mode 100644 index 0000000..3623399 --- /dev/null +++ b/tests/Feature/Api/PublicApiDocumentationTest.php @@ -0,0 +1,37 @@ +get('/api/docs') + ->assertOk(); + } + + public function test_openapi_document_is_publicly_served(): void + { + $response = $this->get('/api/docs/openapi.json') + ->assertOk(); + + $this->assertStringStartsWith( + 'application/vnd.oai.openapi+json', + (string) $response->headers->get('Content-Type'), + ); + + $document = json_decode((string) $response->getContent(), true, 512, JSON_THROW_ON_ERROR); + + $this->assertSame('3.1.0', $document['openapi']); + $this->assertSame([], $document['security']); + $this->assertArrayHasKey('/projects', $document['paths']); + } + + public function test_application_api_still_requires_an_authenticated_user(): void + { + $this->getJson('/api/projects') + ->assertUnauthorized(); + } +}