Merge pull request 'codex/public-api-docs' (#38) from codex/public-api-docs into main
linter / quality (push) Successful in 1m19s
tests / ci (8.4) (push) Successful in 1m28s

Reviewed-on: OpenSource/KanbanGit#38
Reviewed-by: Koda <koda@twyco.de>
This commit was merged in pull request #38.
This commit is contained in:
2026-04-24 09:11:48 +00:00
5 changed files with 397 additions and 2 deletions
+2 -1
View File
@@ -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",
Generated
+131 -1
View File
@@ -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",
+210
View File
@@ -0,0 +1,210 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Scalar Domain
|--------------------------------------------------------------------------
|
| This is the subdomain where Scalar will be accessible from. If this
| setting is null, Scalar will reside under the same domain as the
| application. Otherwise, this value will serve as the subdomain.
|
*/
'domain' => 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 API client button in the sidebar and modal.
*/
'hideClientButton' => true,
/**
* MCP will be introduced as a first-party KanbanGit feature later.
*/
'mcp' => [
'disabled' => 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,
],
];
+12
View File
@@ -1,5 +1,7 @@
<?php
use Illuminate\Http\Response;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Route;
use Laravel\Fortify\Features;
@@ -7,6 +9,16 @@ Route::inertia('/', 'Welcome', [
'canRegister' => 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');
@@ -0,0 +1,42 @@
<?php
namespace Tests\Feature\Api;
use Tests\TestCase;
class PublicApiDocumentationTest extends TestCase
{
public function test_scalar_api_docs_are_public(): void
{
$response = $this->get('/api/docs')
->assertOk();
$content = (string) $response->getContent();
$this->assertMatchesRegularExpression('/"hideClientButton"\s*:\s*true/', $content);
$this->assertMatchesRegularExpression('/"mcp"\s*:\s*\{[^}]*"disabled"\s*:\s*true/', $content);
}
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();
}
}