fix : API Version 관리 추가 - 초기에는 v1으로 개발

(cherry picked from commit 09bf8ac599)
This commit is contained in:
2025-07-18 11:37:07 +09:00
committed by 권혁성
parent a9bd99b255
commit 40fca3295e
14 changed files with 132 additions and 281 deletions

View File

@@ -1,6 +1,6 @@
<?php
namespace App\Http\Controllers\Api;
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
@@ -12,7 +12,7 @@ class AdminApiController extends Controller
/* /**
* @OA\Post(
* path="/api/admin/list",
* path="/api/v1/admin/list",
* summary="관리자 리스트",
* tags={"Admin"},
* security={{"ApiKeyAuth":{}}},

View File

@@ -1,6 +1,6 @@
<?php
namespace App\Http\Controllers\Api;
namespace App\Http\Controllers\Api\V1;
use App\Models\Member;
use Illuminate\Http\Request;
@@ -9,27 +9,12 @@
use App\Http\Controllers\Controller;
/**
* @OA\Info(
* version="1.0.0",
* title="SAM API Documentation",
* description="SAM(Semi-Automatics Management) API 입니다.",
* @OA\Contact(
* email="shine1324@gmail.com"
* )
* )
*
* @OA\Server(
* url="https://api.5130.co.kr",
* description="SAM API 서버"
* )
*/
class ApiController extends Controller
{
/**
* @OA\Get(
* path="/api/debug-apikey",
* path="/api/v1/debug-apikey",
* tags={"API Key 인증"},
* summary="API Key 인증 확인",
* security={{"ApiKeyAuth":{}}},
@@ -52,7 +37,7 @@ public function debugApikey()
/**
* @OA\Post(
* path="/api/login",
* path="/api/v1/login",
* summary="회원 토큰 정보확인",
* tags={"Auth"},
* @OA\RequestBody(
@@ -118,7 +103,7 @@ public function login(Request $request)
/**
* @OA\Post(
* path="/api/logout",
* path="/api/v1/logout",
* summary="로그아웃 (Access 및 Token 무효화)",
* tags={"Auth"},
* security={{"ApiKeyAuth":{}}},

View File

@@ -1,6 +1,6 @@
<?php
namespace App\Http\Controllers\Api;
namespace App\Http\Controllers\Api\V1;
use App\Helpers\ApiResponse;
use Illuminate\Support\Facades\DB;

View File

@@ -1,6 +1,6 @@
<?php
namespace App\Http\Controllers\Api;
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

View File

@@ -1,6 +1,6 @@
<?php
namespace App\Http\Controllers\Api;
namespace App\Http\Controllers\Api\V1;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
@@ -11,7 +11,7 @@ class MemberController extends Controller
{
/**
* @OA\Get(
* path="/api/member/index",
* path="/api/v1/member/index",
* summary="회원 목록 조회",
* description="회원 목록을 조회합니다.",
* tags={"Member"},
@@ -98,7 +98,7 @@ public function store(Request $request)
/**
* @OA\Get (
* path="/api/member/show/{user_no}",
* path="/api/member/V1/show/{user_no}",
* summary="회원 상세조회",
* description="user_no 기준으로 회원 상세 정보를 조회합니다.",
* tags={"Member"},

View File

@@ -0,0 +1,20 @@
<?php
namespace App\Swagger\v1;
/**
* @OA\Info(
* version="1.0.0",
* title="SAM API Documentation",
* description="SAM(Semi-Automatics Management) API 입니다.",
* @OA\Contact(
* email="shine1324@gmail.com"
* )
* )
*
* @OA\Server(
* url=L5_SWAGGER_CONST_HOST,
* description=L5_SWAGGER_CONST_NAME
* )
*/
class SAMInfo {}

View File

@@ -1,283 +1,112 @@
<?php
return [
'default' => 'default',
'documentations' => [
'default' => [
'api' => [
'title' => 'L5 Swagger UI',
'version' => '1.0.0',
'default' => env('L5_SWAGGER_DEFAULT_API', '/api/documentation'),
],
'default' => 'v1', // 기본 문서 버전
'documentations' => [
'v1' => [
'api' => [
'title' => 'SAM API V1 문서',
'version' => '1.0.0',
'default' => env('L5_SWAGGER_DEFAULT_API_V1', '/api/documentation/v1'),
],
'routes' => [
/*
* Route for accessing api documentation interface
*/
'api' => 'api/documentation',
'api' => 'api/documentation/v1',
],
'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',
'docs' => 'storage/api-docs',
/*
* 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.
*/
'docs_json' => 'api-docs-v1.json',
'docs_yaml' => 'api-docs-v1.yaml',
'annotations' => [
base_path('app'),
base_path('app/Http/Controllers/Api/V1'),
base_path('app/Swagger/v1'),
],
],
],
],
'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
*/
'use_absolute_path' => env('L5_SWAGGER_USE_ABSOLUTE_PATH', true),
'swagger_ui_assets_path' => env('L5_SWAGGER_UI_ASSETS_PATH', 'vendor/swagger-api/swagger-ui/dist/'),
'docs' => storage_path('api-docs'),
/*
* Absolute path to directory where to export views
*/
'format_to_use_for_docs' => env('L5_FORMAT_TO_USE_FOR_DOCS', 'json'),
'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
*/
'default_processors_configuration' => [],
'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
*/
'processors' => [],
'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' => [
'ApiKeyAuth' => [
'type' => 'apiKey',
'in' => 'header',
'name' => 'X-API-KEY',
'description' => 'API Key 인증: X-API-KEY: {API_KEY}}',
'description' => 'API Key 인증: X-API-KEY: {API_KEY}',
],
'securitySchemes' => [
'ApiKeyAuth' => [
'type' => 'apiKey',
'in' => 'header',
'name' => 'X-API-KEY',
'description' => 'API Key 인증: X-API-KEY: {API_KEY}}',
'description' => 'API Key 인증: X-API-KEY: {API_KEY}',
],
],
],
'security' => [
[
'ApiKeyAuth' => []
],
['ApiKeyAuth' => []],
],
/*
* 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_always' => env('APP_ENV') !== 'production',
'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
'filter' => env('L5_SWAGGER_UI_FILTERS', true),
],
'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'),
'L5_SWAGGER_CONST_HOST' => env('L5_SWAGGER_CONST_HOST', 'http://localhost'),
'L5_SWAGGER_CONST_NAME' => env('L5_SWAGGER_CONST_NAME', 'API 서버'),
],
],
];

View File

@@ -9566,14 +9566,22 @@ function replace(url, key, object) {
if (!state.alpine)
state.alpine = {};
state.alpine[key] = unwrap(object);
window.history.replaceState(state, "", url.toString());
try {
window.history.replaceState(state, "", url.toString());
} catch (e) {
console.error(e);
}
}
function push(url, key, object) {
let state = window.history.state || {};
if (!state.alpine)
state.alpine = {};
state = { alpine: { ...state.alpine, ...{ [key]: unwrap(object) } } };
window.history.pushState(state, "", url.toString());
try {
window.history.pushState(state, "", url.toString());
} catch (e) {
console.error(e);
}
}
function unwrap(object) {
if (object === void 0)
@@ -10816,8 +10824,8 @@ import_alpinejs15.default.interceptInit((el) => {
// js/directives/wire-dirty.js
var refreshDirtyStatesByComponent = new WeakBag();
on("commit", ({ component, succeed }) => {
succeed(() => {
on("commit", ({ component, respond }) => {
respond(() => {
setTimeout(() => {
refreshDirtyStatesByComponent.each(component, (i) => i(false));
});
@@ -10825,7 +10833,6 @@ on("commit", ({ component, succeed }) => {
});
directive("dirty", ({ el, directive: directive2, component }) => {
let targets = dirtyTargets(el);
let dirty = Alpine.reactive({ state: false });
let oldIsDirty = false;
let initialDisplay = el.style.display;
let refreshDirtyState = (isDirty) => {

File diff suppressed because one or more lines are too long

View File

@@ -8162,14 +8162,22 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
if (!state.alpine)
state.alpine = {};
state.alpine[key] = unwrap(object);
window.history.replaceState(state, "", url.toString());
try {
window.history.replaceState(state, "", url.toString());
} catch (e) {
console.error(e);
}
}
function push(url, key, object) {
let state = window.history.state || {};
if (!state.alpine)
state.alpine = {};
state = { alpine: { ...state.alpine, ...{ [key]: unwrap(object) } } };
window.history.pushState(state, "", url.toString());
try {
window.history.pushState(state, "", url.toString());
} catch (e) {
console.error(e);
}
}
function unwrap(object) {
if (object === void 0)
@@ -9914,8 +9922,8 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
// js/directives/wire-dirty.js
var refreshDirtyStatesByComponent = new WeakBag();
on2("commit", ({ component, succeed }) => {
succeed(() => {
on2("commit", ({ component, respond }) => {
respond(() => {
setTimeout(() => {
refreshDirtyStatesByComponent.each(component, (i) => i(false));
});
@@ -9923,7 +9931,6 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
});
directive2("dirty", ({ el, directive: directive3, component }) => {
let targets = dirtyTargets(el);
let dirty = Alpine.reactive({ state: false });
let oldIsDirty = false;
let initialDisplay = el.style.display;
let refreshDirtyState = (isDirty) => {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,2 +1,2 @@
{"/livewire.js":"fcf8c2ad"}
{"/livewire.js":"df3a17f2"}

View File

@@ -2,50 +2,53 @@
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Api\CommonController;
use App\Http\Controllers\Api\ApiController;
use App\Http\Controllers\Api\MemberController;
use App\Http\Controllers\Api\AdminApiController;
use App\Http\Controllers\Api\FileController;
use App\Http\Controllers\Api\V1\CommonController;
use App\Http\Controllers\Api\V1\ApiController;
use App\Http\Controllers\Api\V1\MemberController;
use App\Http\Controllers\Api\V1\AdminApiController;
use App\Http\Controllers\Api\V1\FileController;
// V1 초기 개발
Route::prefix('v1')->group(function () {
# 회원관련
Route::post('/login', [ApiController::class, 'login']);
Route::get('/login', [ApiController::class, 'login']);
Route::middleware('auth.apikey')->post('/logout', [ApiController::class, 'logout']);
# API KEY 인증
Route::middleware('auth.apikey')->get('/debug-apikey', [ApiController::class, 'debugApikey']);
# 회원관련
Route::post('/login', [ApiController::class, 'login']);
Route::get('/login', [ApiController::class, 'login']);
Route::middleware('auth.apikey')->post('/logout', [ApiController::class, 'logout']);
# SAM API
Route::middleware('auth.apikey')->group(function () {
# API KEY 인증
Route::middleware('auth.apikey')->get('/debug-apikey', [ApiController::class, 'debugApikey']);
// Admin API
Route::post('/admin/list', [AdminApiController::class, 'list'])->middleware('permission:SR'); // 관리자 리스트 조회
// Common API
Route::prefix('common')->group(function () {
Route::get('/code', [CommonController::class, 'getComeCode'])->name('common.code'); // 공통코드 조회
});
# SAM API
Route::middleware('auth.apikey')->group(function () {
// Member API
Route::prefix('member')->group(function () {
Route::get('/index', [MemberController::class, 'index'])->name('member.index'); // 회원 목록 조회
Route::get('/show/{user_no}', [MemberController::class, 'show'])->name('member.show'); // 회원 상세 조회
Route::post('/store', [MemberController::class, 'store'])->name('member.store')->middleware('permission:AC'); // 회원 저장 (등록/수정)
});
// File API
Route::prefix('file')->group(function () {
Route::post('/upload', [FileController::class, 'upload'])->name('file.upload'); // 파일 업로드 (등록/수정)
Route::get('/list', [FileController::class, 'list'])->name('file.list'); // 파일 목록 조회
Route::delete('/delete', [FileController::class, 'delete'])->name('file.delete'); // 파일 삭제
Route::get('/info', [FileController::class, 'findFile'])->name('file.info'); // 파일 정보 조회
});
// Admin API
Route::post('/admin/list', [AdminApiController::class, 'list'])->middleware('permission:SR'); // 관리자 리스트 조회
// Common API
Route::prefix('common')->group(function () {
Route::get('/code', [CommonController::class, 'getComeCode'])->name('common.code'); // 공통코드 조회
});
// Member API
Route::prefix('member')->group(function () {
Route::get('/index', [MemberController::class, 'index'])->name('member.index'); // 회원 목록 조회
Route::get('/show/{user_no}', [MemberController::class, 'show'])->name('member.show'); // 회원 상세 조회
Route::post('/store', [MemberController::class, 'store'])->name('member.store')->middleware('permission:AC'); // 회원 저장 (등록/수정)
});
// File API
Route::prefix('file')->group(function () {
Route::post('/upload', [FileController::class, 'upload'])->name('file.upload'); // 파일 업로드 (등록/수정)
Route::get('/list', [FileController::class, 'list'])->name('file.list'); // 파일 목록 조회
Route::delete('/delete', [FileController::class, 'delete'])->name('file.delete'); // 파일 삭제
Route::get('/info', [FileController::class, 'findFile'])->name('file.info'); // 파일 정보 조회
});
});