安裝 Swagger 並完成 取個人資料的樣版 20250516

This commit is contained in:
allen.yan 2025-05-16 16:52:50 +08:00
parent 5b5ae1b56e
commit 3e9d02451e
16 changed files with 1353 additions and 79 deletions

View File

@ -4,6 +4,14 @@ namespace App\Enums;
use App\Enums\Traits\HasLabels;
/**
* @OA\Schema(
* schema="UserGender",
* type="string",
* enum={"male", "female", "other", "unset"},
* example="male"
* )
*/
enum UserGender: string
{
use HasLabels;

View File

@ -4,6 +4,15 @@ namespace App\Enums;
use App\Enums\Traits\HasLabels;
/**
* @OA\Schema(
* schema="UserStatus",
* type="string",
* enum={"0", "1", "2"},
* description="User status: 0=Active, 1=Suspended, 2=Deleting",
* example="0"
* )
*/
enum UserStatus: int
{
use HasLabels;

View File

@ -4,10 +4,41 @@ namespace App\Http\Controllers;
use App\Models\Artist;
use Illuminate\Http\Request;
use OpenApi\Annotations as OA;
/**
* @OA\Tag(
* name="Artist",
* description="藝人資料搜尋"
* )
*/
class ArtistController extends Controller
{
/**
* @OA\Get(
* path="/api/artists/search",
* tags={"Artist"},
* summary="搜尋藝人名稱",
* @OA\Parameter(
* name="search",
* in="query",
* description="關鍵字搜尋藝人名稱",
* required=false,
* @OA\Schema(type="string")
* ),
* @OA\Parameter(
* name="selected",
* in="query",
* description="已選擇藝人 ID 陣列",
* required=false,
* @OA\Schema(type="array", @OA\Items(type="integer"))
* ),
* @OA\Response(
* response=200,
* description="搜尋結果"
* )
* )
*/
public function search(Request $request)
{
$query = Artist::query();

View File

@ -0,0 +1,63 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use App\Models\User;
use OpenApi\Annotations as OA;
/**
* @OA\Tag(
* name="Auth",
* description="用戶註冊、登入、登出與個人資料"
* )
*/
class AuthController extends Controller
{
/**
* @OA\Get(
* path="/api/profile",
* summary="Get current user profile",
* tags={"Auth"},
* security={{"Authorization":{}}},
* @OA\Response(
* response=200,
* description="User profile",
* @OA\JsonContent(
* allOf={
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
* @OA\Schema(
* @OA\Property(property="data", ref="#/components/schemas/User")
* )
* }
* )
* ),
* @OA\Response(
* response=401,
* description="Unauthorized",
* @OA\JsonContent(
* allOf={
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
* @OA\Schema(
* @OA\Property(property="code", type="string", example="UNAUTHORIZED"),
* @OA\Property(property="message", type="string", example="Unauthorized"),
* @OA\Property(property="data", type="null")
* )
* }
* )
* ),
* @OA\Parameter(
* name="Accept",
* in="header",
* required=true,
* @OA\Schema(type="string", default="application/json")
* )
* )
*/
public function profile(Request $request)
{
return response()->json($request->user());
}
}

View File

@ -7,7 +7,40 @@ use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
/**
* @OA\Info(
* title="My API",
* version="1.0.0",
* description="API documentation using L5 Swagger"
* )
*
* @OA\Server(
* url=L5_SWAGGER_CONST_HOST,
* description="API Server"
* )
*
* @OA\SecurityScheme(
* securityScheme="Authorization",
* type="http",
* scheme="bearer",
* bearerFormat="JWT"
* )
*/
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
/**
* @OA\Schema(
* schema="UnauthorizedResponse",
* allOf={
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
* @OA\Schema(
* @OA\Property(property="code", type="string", example="UNAUTHORIZED"),
* @OA\Property(property="message", type="string", example="Unauthorized"),
* @OA\Property(property="data", type="null", nullable=true)
* )
* }
* )
*/
}

View File

@ -0,0 +1,39 @@
<?php
namespace App\Http\Responses;
use Illuminate\Http\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
/**
* @OA\Schema(
* schema="ApiResponse",
* type="object",
* @OA\Property(property="code", type="string", example="OK"),
* @OA\Property(property="message", type="string", example="Success"),
* @OA\Property(property="data", type="object", nullable=true)
* )
*/
class ApiResponse
{
public static function success($data = null, string $message = 'Success', string $code = 'OK', int $status = Response::HTTP_OK): JsonResponse
{
return self::respond($code, $message, $data, $status);
}
public static function error(string $message = 'Error', string $code = 'ERROR', int $status = Response::HTTP_BAD_REQUEST): JsonResponse
{
return self::respond($code, $message, null, $status);
}
public static function unauthorized(string $message = 'Unauthorized'): JsonResponse
{
return self::error($message, 'UNAUTHORIZED', Response::HTTP_UNAUTHORIZED);
}
private static function respond(string $code, string $message, $data, int $status): JsonResponse
{
return response()->json(['code' => $code,'message' => $message,'data' => $data,], $status);
}
}

View File

@ -11,6 +11,19 @@ use App\Traits\LogsModelActivity;
use Spatie\Activitylog\Traits\CausesActivity;
use Laravel\Sanctum\HasApiTokens;
/**
* @OA\Schema(
* schema="User",
* type="object",
* @OA\Property(property="id", type="integer", example=1),
* @OA\Property(property="name", type="string", example="John Doe"),
* @OA\Property(property="email", type="string", example="john@example.com"),
* @OA\Property(property="phone", type="string", example="0900000000"),
* @OA\Property(property="birthday", type="string", format="date-time", example="2025-05-11T16:00:00.000000Z"),
* @OA\Property(property="gender", ref="#/components/schemas/UserGender"),
* @OA\Property(property="status", ref="#/components/schemas/UserStatus"),
* )
*/
class User extends Authenticatable
{
/** @use HasFactory<\Database\Factories\UserFactory> */

View File

@ -19,5 +19,11 @@ return Application::configure(basePath: dirname(__DIR__))
]);
})
->withExceptions(function (Exceptions $exceptions) {
//
$exceptions->render(function (\Illuminate\Auth\AuthenticationException $e, $request) {
if ($request->expectsJson()) {
return \App\Http\Responses\ApiResponse::unauthorized();
}
// 其他非 JSON 請求的處理方式(可選)
return redirect()->guest(route('login'));
});
})->create();

View File

@ -7,6 +7,7 @@
"license": "MIT",
"require": {
"php": "^8.2",
"darkaonline/l5-swagger": "^9.0",
"laravel/framework": "^12.0",
"laravel/sanctum": "^4.0",
"laravel/tinker": "^2.10.1",

499
composer.lock generated
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": "44d754909b56bf60209d1cd7bb2b3e87",
"content-hash": "3d96136c4b2709b3558d30eafa66bcfc",
"packages": [
{
"name": "brick/math",
@ -295,6 +295,87 @@
],
"time": "2024-09-19T14:15:21+00:00"
},
{
"name": "darkaonline/l5-swagger",
"version": "9.0.1",
"source": {
"type": "git",
"url": "https://github.com/DarkaOnLine/L5-Swagger.git",
"reference": "2c26427f8c41db8e72232415e7287313e6b6a2e2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/DarkaOnLine/L5-Swagger/zipball/2c26427f8c41db8e72232415e7287313e6b6a2e2",
"reference": "2c26427f8c41db8e72232415e7287313e6b6a2e2",
"shasum": ""
},
"require": {
"doctrine/annotations": "^1.0 || ^2.0",
"ext-json": "*",
"laravel/framework": "^12.0 || ^11.0",
"php": "^8.2",
"swagger-api/swagger-ui": ">=5.18.3",
"symfony/yaml": "^5.0 || ^6.0 || ^7.0",
"zircote/swagger-php": "^5.0.0"
},
"require-dev": {
"mockery/mockery": "1.*",
"orchestra/testbench": "^10.0 || ^9.0 || ^8.0 || 7.* || ^6.15 || 5.*",
"php-coveralls/php-coveralls": "^2.0",
"phpstan/phpstan": "^2.1",
"phpunit/phpunit": "^11.0"
},
"type": "library",
"extra": {
"laravel": {
"aliases": {
"L5Swagger": "L5Swagger\\L5SwaggerFacade"
},
"providers": [
"L5Swagger\\L5SwaggerServiceProvider"
]
}
},
"autoload": {
"files": [
"src/helpers.php"
],
"psr-4": {
"L5Swagger\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Darius Matulionis",
"email": "darius@matulionis.lt"
}
],
"description": "OpenApi or Swagger integration to Laravel",
"keywords": [
"api",
"documentation",
"laravel",
"openapi",
"specification",
"swagger",
"ui"
],
"support": {
"issues": "https://github.com/DarkaOnLine/L5-Swagger/issues",
"source": "https://github.com/DarkaOnLine/L5-Swagger/tree/9.0.1"
},
"funding": [
{
"url": "https://github.com/DarkaOnLine",
"type": "github"
}
],
"time": "2025-02-28T06:25:02+00:00"
},
{
"name": "dflydev/dot-access-data",
"version": "v3.0.3",
@ -370,6 +451,82 @@
},
"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",
@ -3707,6 +3864,55 @@
],
"time": "2025-04-28T19:24:40+00:00"
},
{
"name": "psr/cache",
"version": "3.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/cache.git",
"reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf",
"reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf",
"shasum": ""
},
"require": {
"php": ">=8.0.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Cache\\": "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"
],
"support": {
"source": "https://github.com/php-fig/cache/tree/3.0.0"
},
"time": "2021-02-03T23:26:27+00:00"
},
{
"name": "psr/clock",
"version": "1.0.0",
@ -4645,6 +4851,67 @@
],
"time": "2025-05-14T03:32:23+00:00"
},
{
"name": "swagger-api/swagger-ui",
"version": "v5.21.0",
"source": {
"type": "git",
"url": "https://github.com/swagger-api/swagger-ui.git",
"reference": "fceaec605072fbc717a04895bd19814d9a1c8e6d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/swagger-api/swagger-ui/zipball/fceaec605072fbc717a04895bd19814d9a1c8e6d",
"reference": "fceaec605072fbc717a04895bd19814d9a1c8e6d",
"shasum": ""
},
"type": "library",
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0"
],
"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"
}
],
"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",
"keywords": [
"api",
"documentation",
"openapi",
"specification",
"swagger",
"ui"
],
"support": {
"issues": "https://github.com/swagger-api/swagger-ui/issues",
"source": "https://github.com/swagger-api/swagger-ui/tree/v5.21.0"
},
"time": "2025-04-13T19:37:38+00:00"
},
{
"name": "symfony/clock",
"version": "v7.2.0",
@ -6874,6 +7141,78 @@
],
"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",
@ -7337,6 +7676,92 @@
"source": "https://github.com/wireui/wireui/tree/v2.4.3"
},
"time": "2025-04-29T22:34:44+00:00"
},
{
"name": "zircote/swagger-php",
"version": "5.1.1",
"source": {
"type": "git",
"url": "https://github.com/zircote/swagger-php.git",
"reference": "7a6544c60441ddb5959b91266b3a290dc28537ba"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/zircote/swagger-php/zipball/7a6544c60441ddb5959b91266b3a290dc28537ba",
"reference": "7a6544c60441ddb5959b91266b3a290dc28537ba",
"shasum": ""
},
"require": {
"ext-json": "*",
"nikic/php-parser": "^4.19 || ^5.0",
"php": ">=7.4",
"psr/log": "^1.1 || ^2.0 || ^3.0",
"symfony/deprecation-contracts": "^2 || ^3",
"symfony/finder": "^5.0 || ^6.0 || ^7.0",
"symfony/yaml": "^5.0 || ^6.0 || ^7.0"
},
"conflict": {
"symfony/process": ">=6, <6.4.14"
},
"require-dev": {
"composer/package-versions-deprecated": "^1.11",
"doctrine/annotations": "^2.0",
"friendsofphp/php-cs-fixer": "^3.62.0",
"phpstan/phpstan": "^1.6 || ^2.0",
"phpunit/phpunit": "^9.0",
"rector/rector": "^1.0 || ^2.0",
"vimeo/psalm": "^4.30 || ^5.0"
},
"suggest": {
"doctrine/annotations": "^2.0"
},
"bin": [
"bin/openapi"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "5.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": "Generate interactive documentation for your RESTful API using PHP attributes (preferred) or 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/5.1.1"
},
"time": "2025-04-27T10:02:08+00:00"
}
],
"packages-dev": [
@ -9563,78 +9988,6 @@
],
"time": "2024-10-20T05:08:20+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": "theseer/tokenizer",
"version": "1.2.3",

318
config/l5-swagger.php Normal file
View File

@ -0,0 +1,318 @@
<?php
return [
'default' => '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),
/*
* 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/'),
/*
* 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),
/*
* Absolute path to directories that should be excluded from scanning
* @deprecated Please use `scanOptions.exclude`
* `scanOptions.exclude` overwrites this
*/
'excludes' => [],
],
'scanOptions' => [
/**
* Configuration for default processors. Allows to pass processors configuration to swagger-php.
*
* @link https://zircote.github.io/swagger-php/reference/processors.html
*/
'default_processors_configuration' => [
/** Example */
/**
* 'operationId.hash' => true,
* 'pathFilter' => [
* 'tags' => [
* '/pets/',
* '/store/',
* ],
* ],.
*/
],
/**
* 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/processors/schema-query-parameter
* @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 excluded 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',
]
],
*/
/* 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 <token>)',
'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', null),
/*
* 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' => [
'dark_mode' => env('L5_SWAGGER_UI_DARK_MODE', false),
/*
* 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('L5_SWAGGER_CONST_HOST', 'http://my-default-host.com'),
],
],
];

View File

View File

@ -0,0 +1,174 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ $documentationTitle }}</title>
<link rel="stylesheet" type="text/css" href="{{ l5_swagger_asset($documentation, 'swagger-ui.css') }}">
<link rel="icon" type="image/png" href="{{ l5_swagger_asset($documentation, 'favicon-32x32.png') }}" sizes="32x32"/>
<link rel="icon" type="image/png" href="{{ l5_swagger_asset($documentation, 'favicon-16x16.png') }}" sizes="16x16"/>
<style>
html
{
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after
{
box-sizing: inherit;
}
body {
margin:0;
background: #fafafa;
}
</style>
@if(config('l5-swagger.defaults.ui.display.dark_mode'))
<style>
body#dark-mode,
#dark-mode .scheme-container {
background: #1b1b1b;
}
#dark-mode .scheme-container,
#dark-mode .opblock .opblock-section-header{
box-shadow: 0 1px 2px 0 rgba(255, 255, 255, 0.15);
}
#dark-mode .operation-filter-input,
#dark-mode .dialog-ux .modal-ux,
#dark-mode input[type=email],
#dark-mode input[type=file],
#dark-mode input[type=password],
#dark-mode input[type=search],
#dark-mode input[type=text],
#dark-mode textarea{
background: #343434;
color: #e7e7e7;
}
#dark-mode .title,
#dark-mode li,
#dark-mode p,
#dark-mode table,
#dark-mode label,
#dark-mode .opblock-tag,
#dark-mode .opblock .opblock-summary-operation-id,
#dark-mode .opblock .opblock-summary-path,
#dark-mode .opblock .opblock-summary-path__deprecated,
#dark-mode h1,
#dark-mode h2,
#dark-mode h3,
#dark-mode h4,
#dark-mode h5,
#dark-mode .btn,
#dark-mode .tab li,
#dark-mode .parameter__name,
#dark-mode .parameter__type,
#dark-mode .prop-format,
#dark-mode .loading-container .loading:after{
color: #e7e7e7;
}
#dark-mode .opblock-description-wrapper p,
#dark-mode .opblock-external-docs-wrapper p,
#dark-mode .opblock-title_normal p,
#dark-mode .response-col_status,
#dark-mode table thead tr td,
#dark-mode table thead tr th,
#dark-mode .response-col_links,
#dark-mode .swagger-ui{
color: wheat;
}
#dark-mode .parameter__extension,
#dark-mode .parameter__in,
#dark-mode .model-title{
color: #949494;
}
#dark-mode table thead tr td,
#dark-mode table thead tr th{
border-color: rgba(120,120,120,.2);
}
#dark-mode .opblock .opblock-section-header{
background: transparent;
}
#dark-mode .opblock.opblock-post{
background: rgba(73,204,144,.25);
}
#dark-mode .opblock.opblock-get{
background: rgba(97,175,254,.25);
}
#dark-mode .opblock.opblock-put{
background: rgba(252,161,48,.25);
}
#dark-mode .opblock.opblock-delete{
background: rgba(249,62,62,.25);
}
#dark-mode .loading-container .loading:before{
border-color: rgba(255,255,255,10%);
border-top-color: rgba(255,255,255,.6);
}
#dark-mode svg:not(:root){
fill: #e7e7e7;
}
#dark-mode .opblock-summary-description {
color: #fafafa;
}
</style>
@endif
</head>
<body @if(config('l5-swagger.defaults.ui.display.dark_mode')) id="dark-mode" @endif>
<div id="swagger-ui"></div>
<script src="{{ l5_swagger_asset($documentation, 'swagger-ui-bundle.js') }}"></script>
<script src="{{ l5_swagger_asset($documentation, 'swagger-ui-standalone-preset.js') }}"></script>
<script>
window.onload = function() {
const urls = [];
@foreach($urlsToDocs as $title => $url)
urls.push({name: "{{ $title }}", url: "{{ $url }}"});
@endforeach
// Build a system
const ui = SwaggerUIBundle({
dom_id: '#swagger-ui',
urls: urls,
"urls.primaryName": "{{ $documentationTitle }}",
operationsSorter: {!! isset($operationsSorter) ? '"' . $operationsSorter . '"' : 'null' !!},
configUrl: {!! isset($configUrl) ? '"' . $configUrl . '"' : 'null' !!},
validatorUrl: {!! isset($validatorUrl) ? '"' . $validatorUrl . '"' : 'null' !!},
oauth2RedirectUrl: "{{ route('l5-swagger.'.$documentation.'.oauth2_callback', [], $useAbsolutePath) }}",
requestInterceptor: function(request) {
request.headers['X-CSRF-TOKEN'] = '{{ csrf_token() }}';
return request;
},
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout",
docExpansion : "{!! config('l5-swagger.defaults.ui.display.doc_expansion', 'none') !!}",
deepLinking: true,
filter: {!! config('l5-swagger.defaults.ui.display.filter') ? 'true' : 'false' !!},
persistAuthorization: "{!! config('l5-swagger.defaults.ui.authorization.persist_authorization') ? 'true' : 'false' !!}",
})
window.ui = ui
@if(in_array('oauth2', array_column(config('l5-swagger.defaults.securityDefinitions.securitySchemes'), 'type')))
ui.initOAuth({
usePkceWithAuthorizationCodeGrant: "{!! (bool)config('l5-swagger.defaults.ui.authorization.oauth2.use_pkce_with_authorization_code_grant') !!}"
})
@endif
}
</script>
</body>
</html>

View File

@ -2,6 +2,11 @@
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ArtistController;
use App\Http\Controllers\AuthController;
Route::get('/artists/search', [App\Http\Controllers\ArtistController::class, 'search'])->name('api.artists.search');
Route::middleware('auth:sanctum')->group(function () {
Route::get('/profile', [AuthController::class, 'profile']);
});

View File

@ -0,0 +1,222 @@
{
"openapi": "3.0.0",
"info": {
"title": "My API",
"description": "API documentation using L5 Swagger",
"version": "1.0.0"
},
"servers": [
{
"url": "https://KTV.test/",
"description": "API Server"
}
],
"paths": {
"/api/artists/search": {
"get": {
"tags": [
"Artist"
],
"summary": "搜尋藝人名稱",
"operationId": "f96ef111c1fadd6241138fd9840bf697",
"parameters": [
{
"name": "search",
"in": "query",
"description": "關鍵字搜尋藝人名稱",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "selected",
"in": "query",
"description": "已選擇藝人 ID 陣列",
"required": false,
"schema": {
"type": "array",
"items": {
"type": "integer"
}
}
}
],
"responses": {
"200": {
"description": "搜尋結果"
}
}
}
},
"/api/profile": {
"get": {
"tags": [
"Auth"
],
"summary": "Get current user profile",
"operationId": "d32e56b6274eb68e980da6d91fbe9fd8",
"parameters": [
{
"name": "Accept",
"in": "header",
"required": true,
"schema": {
"type": "string",
"default": "application/json"
}
}
],
"responses": {
"200": {
"description": "User profile",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/ApiResponse"
},
{
"properties": {
"data": {
"$ref": "#/components/schemas/User"
}
},
"type": "object"
}
]
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/ApiResponse"
},
{
"properties": {
"code": {
"type": "string",
"example": "UNAUTHORIZED"
},
"message": {
"type": "string",
"example": "Unauthorized"
},
"data": {
"type": "null"
}
},
"type": "object"
}
]
}
}
}
}
},
"security": [
{
"Authorization": []
}
]
}
}
},
"components": {
"schemas": {
"UserGender": {
"type": "string",
"enum": [
"male",
"female",
"other",
"unset"
],
"example": "male"
},
"UserStatus": {
"description": "User status: 0=Active, 1=Suspended, 2=Deleting",
"type": "string",
"enum": [
"Active",
"Suspended",
"Deleting"
],
"example": "0"
},
"ApiResponse": {
"properties": {
"code": {
"type": "string",
"example": "OK"
},
"message": {
"type": "string",
"example": "Success"
},
"data": {
"type": "object",
"nullable": true
}
},
"type": "object"
},
"User": {
"properties": {
"id": {
"type": "integer",
"example": 1
},
"name": {
"type": "string",
"example": "John Doe"
},
"email": {
"type": "string",
"example": "john@example.com"
},
"phone": {
"type": "string",
"example": "0900000000"
},
"birthday": {
"type": "string",
"format": "date-time",
"example": "2025-05-11T16:00:00.000000Z"
},
"gender": {
"$ref": "#/components/schemas/UserGender"
},
"status": {
"$ref": "#/components/schemas/UserStatus"
}
},
"type": "object"
}
},
"securitySchemes": {
"Authorization": {
"type": "http",
"bearerFormat": "JWT",
"scheme": "bearer"
}
}
},
"tags": [
{
"name": "Artist",
"description": "藝人資料搜尋"
},
{
"name": "Auth",
"description": "用戶註冊、登入、登出與個人資料"
}
]
}

View File

@ -125,6 +125,5 @@ composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate
composer require knuckleswtf/scribe
php artisan vendor:publish --tag=scribe-config
php artisan scribe:generate
composer require "darkaonline/l5-swagger"
php artisan vendor:publish --provider "L5Swagger\L5SwaggerServiceProvider"