feat: Integrate L5-Swagger for API documentation and enhance VM synchronization to include host-level details.

This commit is contained in:
pbao9
2026-02-15 12:31:06 +07:00
parent eb123ba97c
commit ef53ea3207
19 changed files with 3040 additions and 13433 deletions
@@ -9,6 +9,12 @@ use App\Http\Resources\Api\Client\ClientResource;
use App\Services\Api\Client\ClientAuthService;
use Illuminate\Support\Facades\Auth;
/**
* @OA\Tag(
* name="Client Authentication",
* description="API Endpoints for Client Authentication"
* )
*/
class ClientController extends Controller
{
protected $authService;
@@ -19,7 +25,59 @@ class ClientController extends Controller
}
/**
* Lấy thông tin khách hàng hiện tại
* @OA\Get(
* path="/client/me",
* operationId="getClientMe",
* tags={"Client Authentication"},
* summary="Get current client information",
* description="Returns the currently authenticated client's details",
* security={{"bearerAuth":{}}},
* @OA\Response(
* response=200,
* description="Successful operation",
* @OA\JsonContent(
* @OA\Property(property="customer", type="object",
* @OA\Property(property="fullname", type="string", example="Nguyen Van A"),
* @OA\Property(property="email", type="string", example="client@example.com"),
* @OA\Property(property="phone", type="string", example="0987654321"),
* @OA\Property(property="address", type="string", example="123 Street"),
* @OA\Property(property="city", type="string", example="Hanoi"),
* @OA\Property(property="ward", type="string", example="Ward 1"),
* @OA\Property(property="province", type="string", example="Hanoi"),
* @OA\Property(property="gender", type="string", example="Nam"),
* @OA\Property(property="date", type="string", format="date-time", example="2024-01-01 12:00:00"),
* @OA\Property(property="company", type="string", example="Company Ltd"),
* @OA\Property(property="avatar", type="string", example="/uploads/avatar.jpg"),
* @OA\Property(property="cmnd_after", type="string", example="cmnd_back.jpg"),
* @OA\Property(property="cmnd_before", type="string", example="cmnd_front.jpg"),
* @OA\Property(property="cmnd", type="string", example="123456789"),
* @OA\Property(property="fb_link", type="string", nullable=true),
* @OA\Property(property="active_2fa", type="integer", example=0),
* @OA\Property(property="off_mail_marketing", type="integer", example=0),
* @OA\Property(property="is_recharge_zalo", type="integer", example=0),
* @OA\Property(property="is_verify_telegram", type="integer", example=0)
* ),
* @OA\Property(property="credit", type="object",
* @OA\Property(property="credit", type="number", example=1000000),
* @OA\Property(property="total", type="number", example=5000000),
* @OA\Property(property="point", type="number", example=100),
* @OA\Property(property="point_used", type="integer", example=0),
* @OA\Property(property="point_total", type="number", example=100)
* ),
* @OA\Property(property="service", type="object",
* @OA\Property(property="vps", type="integer", example=2),
* @OA\Property(property="proxy", type="integer", example=0),
* @OA\Property(property="server", type="integer", example=0),
* @OA\Property(property="hosting", type="integer", example=1),
* @OA\Property(property="domain", type="integer", example=3)
* )
* )
* ),
* @OA\Response(
* response=401,
* description="Unauthenticated"
* )
* )
*/
public function me()
{
@@ -27,7 +85,36 @@ class ClientController extends Controller
}
/**
* Đăng nhập khách hàng
* @OA\Post(
* path="/client/login",
* operationId="clientLogin",
* tags={"Client Authentication"},
* summary="Client login",
* description="Authenticate a client and return access token",
* @OA\RequestBody(
* required=true,
* @OA\JsonContent(
* required={"email","password"},
* @OA\Property(property="email", type="string", format="email", example="client@example.com"),
* @OA\Property(property="password", type="string", format="password", example="password123")
* )
* ),
* @OA\Response(
* response=200,
* description="Login successful",
* @OA\JsonContent(
* @OA\Property(property="error", type="boolean", example=false),
* @OA\Property(property="data", type="object",
* @OA\Property(property="client", type="object"),
* @OA\Property(property="token", type="string", example="1|laravel_sanctum_token...")
* )
* )
* ),
* @OA\Response(
* response=422,
* description="Validation error"
* )
* )
*/
public function login(ClientAuthRequest $request)
{
@@ -43,7 +130,39 @@ class ClientController extends Controller
}
/**
* Đăng ký khách hàng mới
* @OA\Post(
* path="/client/register",
* operationId="clientRegister",
* tags={"Client Authentication"},
* summary="Client registration",
* description="Register a new client account",
* @OA\RequestBody(
* required=true,
* @OA\JsonContent(
* required={"fullname","phone","email","password","password_confirmation"},
* @OA\Property(property="fullname", type="string", example="Nguyen Van A"),
* @OA\Property(property="phone", type="string", example="0987654321"),
* @OA\Property(property="email", type="string", format="email", example="client@example.com"),
* @OA\Property(property="password", type="string", format="password", example="password123"),
* @OA\Property(property="password_confirmation", type="string", format="password", example="password123")
* )
* ),
* @OA\Response(
* response=200,
* description="Registration successful",
* @OA\JsonContent(
* @OA\Property(property="error", type="boolean", example=false),
* @OA\Property(property="data", type="object",
* @OA\Property(property="client", type="object"),
* @OA\Property(property="token", type="string", example="1|laravel_sanctum_token...")
* )
* )
* ),
* @OA\Response(
* response=422,
* description="Validation error"
* )
* )
*/
public function register(ClientRegisterRequest $request)
{
@@ -59,7 +178,26 @@ class ClientController extends Controller
}
/**
* Đăng xuất khách hàng
* @OA\Post(
* path="/client/logout",
* operationId="clientLogout",
* tags={"Client Authentication"},
* summary="Client logout",
* description="Logout the currently authenticated client",
* security={{"bearerAuth":{}}},
* @OA\Response(
* response=200,
* description="Logout successful",
* @OA\JsonContent(
* @OA\Property(property="error", type="boolean", example=false),
* @OA\Property(property="message", type="string", example="Đã đăng xuất thành công.")
* )
* ),
* @OA\Response(
* response=401,
* description="Unauthenticated"
* )
* )
*/
public function logout()
{
@@ -10,6 +10,12 @@ use App\Services\Api\Order\OrderService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
/**
* @OA\Tag(
* name="Orders",
* description="API Endpoints for Order Management"
* )
*/
class OrderController extends Controller
{
public function __construct(
@@ -17,6 +23,107 @@ class OrderController extends Controller
protected OrderRepository $orderRepo
) {}
/**
* @OA\Get(
* path="/orders",
* operationId="getOrdersList",
* tags={"Orders"},
* summary="Get list of orders",
* description="Returns a list of orders for the authenticated customer",
* security={{"bearerAuth":{}}},
* @OA\Parameter(
* name="total_price",
* in="query",
* description="Filter by total price",
* required=false,
* @OA\Schema(type="number")
* ),
* @OA\Parameter(
* name="pending",
* in="query",
* description="Filter by pending status",
* required=false,
* @OA\Schema(type="boolean")
* ),
* @OA\Response(
* response=200,
* description="Successful operation",
* @OA\JsonContent(
* @OA\Property(property="success", type="boolean", example=true),
* @OA\Property(property="data", type="array", @OA\Items(type="object"))
* )
* ),
* @OA\Response(
* response=401,
* description="Unauthenticated"
* )
* )
*/
public function getList(Request $request): JsonResponse
{
try {
$customer = $request->user();
$filters = [
'total_price' => $request->query('total_price'),
'pending' => $request->query('pending')
];
$orders = $this->orderRepo->getOrders($customer->id, $filters);
return response()->json([
'success' => true,
'data' => $orders
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'message' => $e->getMessage()
], 400);
}
}
/**
* @OA\Post(
* path="/orders",
* operationId="storeOrder",
* tags={"Orders"},
* summary="Create a new order",
* description="Create a new order for a product",
* security={{"bearerAuth":{}}},
* @OA\RequestBody(
* required=true,
* @OA\JsonContent(
* required={"product_id","billing_cycle"},
* @OA\Property(property="product_id", type="string", example="550e8400-e29b-41d4-a716-446655440000", description="Product UUID"),
* @OA\Property(property="billing_cycle", type="string", example="monthly", enum={"monthly", "quarterly", "semi_annually", "annually", "biennially"}),
* @OA\Property(property="server_name", type="string", example="My VPS Server"),
* @OA\Property(property="os_id", type="integer", example=1, description="Operating System ID"),
* @OA\Property(property="addons", type="array", @OA\Items(
* @OA\Property(property="addon_id", type="integer", example=1),
* @OA\Property(property="qty", type="integer", example=1)
* ))
* )
* ),
* @OA\Response(
* response=201,
* description="Order created successfully",
* @OA\JsonContent(
* @OA\Property(property="success", type="boolean", example=true),
* @OA\Property(property="data", type="object"),
* @OA\Property(property="message", type="string", example="Tạo đơn hàng thành công")
* )
* ),
* @OA\Response(
* response=401,
* description="Unauthenticated"
* ),
* @OA\Response(
* response=422,
* description="Validation error"
* )
* )
*/
public function storeOrder(StoreOrderRequest $request): JsonResponse
{
try {
@@ -39,7 +146,7 @@ class OrderController extends Controller
if (!$customer) {
return response()->json([
'success' => false,
'message' => 'Người dùng chưa đăng nhập hoặc phiên đăng nhập hết hạn.'
'message' => 'Ngườ;i dùng chưa đăng nhập hoặc phiên đăng nhập hết hạn.'
], 401);
}
@@ -59,6 +166,50 @@ class OrderController extends Controller
}
}
/**
* @OA\Post(
* path="/orders/{id}/pay",
* operationId="payOrder",
* tags={"Orders"},
* summary="Pay for an order",
* description="Process payment for a specific order",
* security={{"bearerAuth":{}}},
* @OA\Parameter(
* name="id",
* in="path",
* description="Order ID",
* required=true,
* @OA\Schema(type="integer")
* ),
* @OA\RequestBody(
* required=true,
* @OA\JsonContent(
* required={"payment_method"},
* @OA\Property(property="payment_method", type="string", example="credit", enum={"credit", "vnpay", "momo", "bank_transfer"})
* )
* ),
* @OA\Response(
* response=200,
* description="Payment successful",
* @OA\JsonContent(
* @OA\Property(property="success", type="boolean", example=true),
* @OA\Property(property="message", type="string", example="Thanh toán thành công")
* )
* ),
* @OA\Response(
* response=401,
* description="Unauthenticated"
* ),
* @OA\Response(
* response=403,
* description="Forbidden - Not your order"
* ),
* @OA\Response(
* response=404,
* description="Order not found"
* )
* )
*/
public function payOrder(int $id, PayOrderRequest $request): JsonResponse
{
try {
@@ -86,28 +237,4 @@ class OrderController extends Controller
], 400);
}
}
public function getList(Request $request): JsonResponse
{
try {
$customer = $request->user();
$filters = [
'total_price' => $request->query('total_price'),
'pending' => $request->query('pending')
];
$orders = $this->orderRepo->getOrders($customer->id, $filters);
return response()->json([
'success' => true,
'data' => $orders
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'message' => $e->getMessage()
], 400);
}
}
}
}
@@ -23,6 +23,12 @@ use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;
/**
* @OA\Tag(
* name="Services",
* description="API Endpoints for Services/Products"
* )
*/
class ServiceController extends Controller
{
protected $serviceService;
@@ -37,6 +43,56 @@ class ServiceController extends Controller
$this->serviceRepository = $serviceRepository;
}
/**
* @OA\Get(
* path="/services/get-list",
* operationId="getServiceList",
* tags={"Services"},
* summary="Get list of services",
* description="Returns a paginated list of active services/products",
* @OA\Parameter(
* name="limit",
* in="query",
* description="Number of items per page",
* required=false,
* @OA\Schema(type="integer", default=10)
* ),
* @OA\Response(
* response=200,
* description="Successful operation",
* @OA\JsonContent(
* @OA\Property(property="error", type="boolean", example=false),
* @OA\Property(property="message", type="string", example="Lấy dữ liệu thành công"),
* @OA\Property(property="page_type", type="string", example="service"),
* @OA\Property(property="data", type="array", @OA\Items(
* @OA\Property(property="uuid", type="string", example="550e8400-e29b-41d4-a716-446655440000"),
* @OA\Property(property="name", type="string", example="Cloud VPS Basic"),
* @OA\Property(property="slug", type="string", example="cloud-vps-basic"),
* @OA\Property(property="short_description", type="string", example="Basic VPS plan"),
* @OA\Property(property="description", type="string"),
* @OA\Property(property="thumbnail", type="string", example="https://example.com/images/vps.jpg"),
* @OA\Property(property="product_type", type="string", example="vps"),
* @OA\Property(property="specs", type="object",
* @OA\Property(property="cpu", type="integer", example=2),
* @OA\Property(property="ram", type="integer", example=4),
* @OA\Property(property="disk", type="integer", example=50),
* @OA\Property(property="bandwidth", type="integer", example=1000),
* @OA\Property(property="gpu", type="string", nullable=true)
* ),
* @OA\Property(property="stock", type="integer", example=100),
* @OA\Property(property="price", type="number", example=299000),
* @OA\Property(property="price_sale", type="number", example=199000),
* @OA\Property(property="pricing", type="object"),
* @OA\Property(property="pricing_sale", type="object"),
* @OA\Property(property="group", type="string", example="VPS"),
* @OA\Property(property="os", type="array", @OA\Items(
* @OA\Property(property="group", type="string", example="Ubuntu")
* ))
* ))
* )
* )
* )
*/
public function getService(Request $request)
{
$limit = $request->get('limit', 10);
@@ -49,6 +105,63 @@ class ServiceController extends Controller
]);
}
/**
* @OA\Get(
* path="/services/{slug}",
* operationId="getServiceDetail",
* tags={"Services"},
* summary="Get service details",
* description="Returns detailed information about a specific service/product",
* @OA\Parameter(
* name="slug",
* in="path",
* description="Service slug",
* required=true,
* @OA\Schema(type="string")
* ),
* @OA\Response(
* response=200,
* description="Successful operation",
* @OA\JsonContent(
* @OA\Property(property="error", type="boolean", example=false),
* @OA\Property(property="message", type="string", example="Lấy dữ liệu thành công"),
* @OA\Property(property="page_type", type="string", example="service"),
* @OA\Property(property="data", type="object",
* @OA\Property(property="uuid", type="string", example="550e8400-e29b-41d4-a716-446655440000"),
* @OA\Property(property="name", type="string", example="Cloud VPS Basic"),
* @OA\Property(property="slug", type="string", example="cloud-vps-basic"),
* @OA\Property(property="short_description", type="string", example="Basic VPS plan"),
* @OA\Property(property="description", type="string"),
* @OA\Property(property="thumbnail", type="string", example="https://example.com/images/vps.jpg"),
* @OA\Property(property="product_type", type="string", example="vps"),
* @OA\Property(property="specs", type="object",
* @OA\Property(property="cpu", type="integer", example=2),
* @OA\Property(property="ram", type="integer", example=4),
* @OA\Property(property="disk", type="integer", example=50),
* @OA\Property(property="bandwidth", type="integer", example=1000),
* @OA\Property(property="gpu", type="string", nullable=true)
* ),
* @OA\Property(property="stock", type="integer", example=100),
* @OA\Property(property="price", type="number", example=299000),
* @OA\Property(property="price_sale", type="number", example=199000),
* @OA\Property(property="group", type="string", example="VPS"),
* @OA\Property(property="os", type="array", @OA\Items(
* @OA\Property(property="name", type="string", example="Ubuntu"),
* @OA\Property(property="thumbnail", type="string"),
* @OA\Property(property="versions", type="array", @OA\Items(
* @OA\Property(property="version", type="string", example="22.04"),
* @OA\Property(property="name", type="string", example="Ubuntu 22.04 LTS")
* ))
* ))
* )
* )
* ),
* @OA\Response(
* response=404,
* description="Service not found"
* )
* )
*/
public function getDetailService(Product $product)
{
$product->load([
@@ -5,8 +5,32 @@ namespace App\Http\Controllers\Api\Setting;
use App\Http\Controllers\Controller;
use App\Models\Setting;
/**
* @OA\Tag(
* name="Settings",
* description="API Endpoints for Application Settings"
* )
*/
class SettingController extends Controller
{
/**
* @OA\Get(
* path="/setting",
* operationId="getSetting",
* tags={"Settings"},
* summary="Get application settings",
* description="Returns the application settings",
* @OA\Response(
* response=200,
* description="Successful operation",
* @OA\JsonContent(
* @OA\Property(property="data", type="object"),
* @OA\Property(property="message", type="string", example="Get setting successfully"),
* @OA\Property(property="error", type="boolean", example=false)
* )
* )
* )
*/
public function getSetting()
{
$setting = Setting::where('key', 'setting')->first();
+26
View File
@@ -8,6 +8,32 @@ use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
/**
* @OA\Info(
* version="1.0.0",
* title="Portal API",
* description="API Documentation for Portal Application",
* @OA\Contact(
* email="admin@portal.com"
* ),
* @OA\License(
* name="MIT",
* url="https://opensource.org/licenses/MIT"
* )
* )
*
* @OA\Server(
* url="/api/v1",
* description="API Server"
* )
*
* @OA\SecurityScheme(
* securityScheme="bearerAuth",
* type="http",
* scheme="bearer",
* bearerFormat="JWT"
* )
*/
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
@@ -124,7 +124,7 @@ class VirtualMachineController extends Controller
{
$query = $this->virtualMachineRepository->filter(
$request->all(),
['datacenter'],
['datacenter', 'host'],
[],
['*'],
true
@@ -151,6 +151,9 @@ class VirtualMachineController extends Controller
->editColumn('datacenter.name', function ($d) {
return $d->datacenter ? $d->datacenter->name . ' - ' . $d->datacenter->datacenter_id : 'N/A';
})
->editColumn('host.name', function ($d) {
return $d->host ? $d->host->name . ' - ' . $d->host->host_id : 'N/A';
})
->rawColumns(['action', 'power_state'])
->make(true);
}
+4 -1
View File
@@ -77,7 +77,7 @@ class vCenter
return "Login_Again";
}
public function getAllVirtualMachine($datacenter = '')
public function getAllVirtualMachine($datacenter = '', $host = '')
{
$check = $this->checkApiExits();
if ($check) {
@@ -85,6 +85,9 @@ class vCenter
if (!empty($datacenter)) {
$params['filter.datacenters'] = $datacenter;
}
if (!empty($host)) {
$params['filter.hosts.1'] = $host;
}
$response = Http::withoutVerifying()
->withHeaders([
@@ -5,6 +5,7 @@ namespace App\Jobs\VirtualMachine\Sync;
use App\Http\Services\Telegram;
use App\Http\Services\vCenter as ServiceVCenter;
use App\Models\ModuleDatacenter;
use App\Models\ModuleHost;
use App\Models\VirtualMachines;
use App\Models\vCenter;
use Illuminate\Bus\Queueable;
@@ -67,15 +68,29 @@ class SyncVirtualMachine implements ShouldQueue
$datacenters = ModuleDatacenter::where('vcenter_id', $item->id)->get();
if ($datacenters->count() > 0) {
foreach ($datacenters as $dc) {
$vms = $this->vCenter->getAllVirtualMachine($dc->datacenter_id);
$vmCount = $this->processVMs($vms, $item, $dc->id); // Extract method for reuse
$vmTotalCount += $vmCount;
// Get all hosts for this datacenter
$hosts = ModuleHost::where('module_datacenter_id', $dc->id)->get();
if ($hosts->count() > 0) {
foreach ($hosts as $host) {
Log::info("Fetching VMs for host: {$host->host_id} in datacenter: {$dc->name}");
$vms = $this->vCenter->getAllVirtualMachine($dc->datacenter_id, $host->host_id);
$vmCount = $this->processVMs($vms, $item, $dc->id, $host->id);
$vmTotalCount += $vmCount;
}
} else {
// Fallback: Sync VMs by datacenter only
Log::info("No hosts found for datacenter {$dc->name}, fetching VMs by datacenter only");
$vms = $this->vCenter->getAllVirtualMachine($dc->datacenter_id);
$vmCount = $this->processVMs($vms, $item, $dc->id, null);
$vmTotalCount += $vmCount;
}
}
} else {
// Fallback: Sync all VMs without datacenter context
Log::info("No datacenters found, fetching all VMs for {$item->host}");
$vms = $this->vCenter->getAllVirtualMachine();
$vmCount = $this->processVMs($vms, $item, null);
$vmCount = $this->processVMs($vms, $item, null, null);
$vmTotalCount += $vmCount;
}
@@ -102,7 +117,7 @@ class SyncVirtualMachine implements ShouldQueue
}
}
private function processVMs($vms, $vCenterItem, $datacenterId)
private function processVMs($vms, $vCenterItem, $datacenterId, $hostId)
{
// If token expired, retry login (handled inside loop in previous logic, but simpler here if we just handle the array check)
if ($vms === 'Login_Again') {
@@ -113,7 +128,12 @@ class SyncVirtualMachine implements ShouldQueue
$vCenterItem->save();
$this->vCenter->token = $token;
// Retry fetch
$vms = $this->vCenter->getAllVirtualMachine($datacenterId ? ModuleDatacenter::find($datacenterId)->datacenter_id : '');
$datacenter = $datacenterId ? ModuleDatacenter::find($datacenterId) : null;
$host = $hostId ? ModuleHost::find($hostId) : null;
$vms = $this->vCenter->getAllVirtualMachine(
$datacenter ? $datacenter->datacenter_id : '',
$host ? $host->host_id : ''
);
}
}
@@ -135,7 +155,9 @@ class SyncVirtualMachine implements ShouldQueue
$vmObj->power_state = $vmData['power_state'];
$vmObj->cpu = $vmData['cpu_count'] ?? 0;
$vmObj->ram_mb = $vmData['memory_size_MiB'] ?? 0;
$vmObj->datacenter_id = $datacenterId; // Assign datacenter if known
$vmObj->datacenter_id = $datacenterId;
$vmObj->host_id = $hostId;
$vmObj->folder = $vmData['folder'] ?? null;
$vmObj->last_synced_at = now();
$vmObj->save();
+1 -1
View File
@@ -3,7 +3,7 @@
namespace App\Services\Cache;
use App\Services\Caches\CacheServiceInterface;
use App\Services\Cache\CacheServiceInterface;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
+1
View File
@@ -12,6 +12,7 @@
"ext-openssl": "*",
"ext-ssh2": "*",
"ckfinder/ckfinder-laravel-package": "^5.0",
"darkaonline/l5-swagger": "^8.6",
"developermithu/tallcraftui": "^2.0",
"guzzlehttp/guzzle": "^7.2",
"kalnoy/nestedset": "^6.0",
Generated
+425 -77
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": "e39826815f466a751ab38a5fc092dca2",
"content-hash": "5d2f1f843f26a0e49fde4a891d311eb7",
"packages": [
{
"name": "aws/aws-crt-php",
@@ -644,6 +644,86 @@
],
"time": "2025-08-20T19:15:30+00:00"
},
{
"name": "darkaonline/l5-swagger",
"version": "8.6.5",
"source": {
"type": "git",
"url": "https://github.com/DarkaOnLine/L5-Swagger.git",
"reference": "4cf2b3faae9e9cffd05e4eb6e066741bf56f0a85"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/DarkaOnLine/L5-Swagger/zipball/4cf2b3faae9e9cffd05e4eb6e066741bf56f0a85",
"reference": "4cf2b3faae9e9cffd05e4eb6e066741bf56f0a85",
"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"
},
"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"
},
"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/8.6.5"
},
"funding": [
{
"url": "https://github.com/DarkaOnLine",
"type": "github"
}
],
"time": "2025-02-06T14:54:32+00:00"
},
{
"name": "defuse/php-encryption",
"version": "v2.4.0",
@@ -858,6 +938,83 @@
},
"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"
},
"abandoned": true,
"time": "2024-09-05T10:17:24+00:00"
},
{
"name": "doctrine/inflector",
"version": "2.1.0",
@@ -5775,6 +5932,55 @@
],
"time": "2025-11-12T18:00:11+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",
@@ -6809,6 +7015,67 @@
],
"time": "2025-04-24T09:21:00+00:00"
},
{
"name": "swagger-api/swagger-ui",
"version": "v5.31.0",
"source": {
"type": "git",
"url": "https://github.com/swagger-api/swagger-ui.git",
"reference": "61dddd44389bf25922639afd682f50b8a0fb5cf4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/swagger-api/swagger-ui/zipball/61dddd44389bf25922639afd682f50b8a0fb5cf4",
"reference": "61dddd44389bf25922639afd682f50b8a0fb5cf4",
"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.31.0"
},
"time": "2025-12-11T15:57:42+00:00"
},
{
"name": "symfony/console",
"version": "v6.4.32",
@@ -9227,6 +9494,82 @@
],
"time": "2026-01-01T13:34:06+00:00"
},
{
"name": "symfony/yaml",
"version": "v7.4.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "24dd4de28d2e3988b311751ac49e684d783e2345"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/24dd4de28d2e3988b311751ac49e684d783e2345",
"reference": "24dd4de28d2e3988b311751ac49e684d783e2345",
"shasum": ""
},
"require": {
"php": ">=8.2",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/polyfill-ctype": "^1.8"
},
"conflict": {
"symfony/console": "<6.4"
},
"require-dev": {
"symfony/console": "^6.4|^7.0|^8.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.4.1"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2025-12-04T18:11:45+00:00"
},
{
"name": "tijsverkoyen/css-to-inline-styles",
"version": "v2.4.0",
@@ -9953,6 +10296,87 @@
}
],
"time": "2024-02-28T05:00:23+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": [
@@ -12604,82 +13028,6 @@
],
"time": "2025-02-20T13:13:55+00:00"
},
{
"name": "symfony/yaml",
"version": "v7.4.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "24dd4de28d2e3988b311751ac49e684d783e2345"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/24dd4de28d2e3988b311751ac49e684d783e2345",
"reference": "24dd4de28d2e3988b311751ac49e684d783e2345",
"shasum": ""
},
"require": {
"php": ">=8.2",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/polyfill-ctype": "^1.8"
},
"conflict": {
"symfony/console": "<6.4"
},
"require-dev": {
"symfony/console": "^6.4|^7.0|^8.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.4.1"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2025-12-04T18:11:45+00:00"
},
{
"name": "theseer/tokenizer",
"version": "1.3.1",
+320
View File
@@ -0,0 +1,320 @@
<?php
return [
'default' => 'default',
'documentations' => [
'default' => [
'api' => [
'title' => 'Portal API Documentation',
'description' => 'API documentation for Portal Application',
'version' => '1.0.0',
],
'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" => []
],
],
],
*/
'bearerAuth' => [ // Unique name of security
'type' => 'http',
'scheme' => 'bearer',
'bearerFormat' => 'JWT',
'description' => 'Enter your Bearer token in the format: Bearer {token}'
],
],
'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'),
],
],
];
@@ -0,0 +1,29 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('virtual_machines', function (Blueprint $table) {
$table->foreignId('host_id')->nullable()->after('datacenter_id')->constrained('module_host');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('virtual_machines', function (Blueprint $table) {
$table->dropForeign(['host_id']);
$table->dropColumn('host_id');
});
}
};
-13061
View File
File diff suppressed because it is too large Load Diff
@@ -29,6 +29,7 @@
['key' => 'cpu', 'label' => 'CPU'],
['key' => 'ram_mb', 'label' => 'RAM (MB)'],
['key' => 'datacenter.name', 'label' => 'Datacenter'],
['key' => 'host.name', 'label' => 'Host'],
['key' => 'action', 'label' => 'Action', 'orderable' => false, 'searchable' => false],
]" />
</div>
View File
+167
View File
@@ -0,0 +1,167 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{config('l5-swagger.documentations.'.$documentation.'.api.title')}}</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() {
// Build a system
const ui = SwaggerUIBundle({
dom_id: '#swagger-ui',
url: "{!! $urlToDocs !!}",
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>
+985
View File
@@ -0,0 +1,985 @@
{
"openapi": "3.0.0",
"info": {
"title": "Portal API",
"description": "API Documentation for Portal Application",
"contact": {
"email": "admin@portal.com"
},
"license": {
"name": "MIT",
"url": "https://opensource.org/licenses/MIT"
},
"version": "1.0.0"
},
"servers": [
{
"url": "/api/v1",
"description": "API Server"
}
],
"paths": {
"/client/me": {
"get": {
"tags": [
"Client Authentication"
],
"summary": "Get current client information",
"description": "Returns the currently authenticated client's details",
"operationId": "getClientMe",
"responses": {
"200": {
"description": "Successful operation",
"content": {
"application/json": {
"schema": {
"properties": {
"customer": {
"properties": {
"fullname": {
"type": "string",
"example": "Nguyen Van A"
},
"email": {
"type": "string",
"example": "client@example.com"
},
"phone": {
"type": "string",
"example": "0987654321"
},
"address": {
"type": "string",
"example": "123 Street"
},
"city": {
"type": "string",
"example": "Hanoi"
},
"ward": {
"type": "string",
"example": "Ward 1"
},
"province": {
"type": "string",
"example": "Hanoi"
},
"gender": {
"type": "string",
"example": "Nam"
},
"date": {
"type": "string",
"format": "date-time",
"example": "2024-01-01 12:00:00"
},
"company": {
"type": "string",
"example": "Company Ltd"
},
"avatar": {
"type": "string",
"example": "/uploads/avatar.jpg"
},
"cmnd_after": {
"type": "string",
"example": "cmnd_back.jpg"
},
"cmnd_before": {
"type": "string",
"example": "cmnd_front.jpg"
},
"cmnd": {
"type": "string",
"example": "123456789"
},
"fb_link": {
"type": "string",
"nullable": true
},
"active_2fa": {
"type": "integer",
"example": 0
},
"off_mail_marketing": {
"type": "integer",
"example": 0
},
"is_recharge_zalo": {
"type": "integer",
"example": 0
},
"is_verify_telegram": {
"type": "integer",
"example": 0
}
},
"type": "object"
},
"credit": {
"properties": {
"credit": {
"type": "number",
"example": 1000000
},
"total": {
"type": "number",
"example": 5000000
},
"point": {
"type": "number",
"example": 100
},
"point_used": {
"type": "integer",
"example": 0
},
"point_total": {
"type": "number",
"example": 100
}
},
"type": "object"
},
"service": {
"properties": {
"vps": {
"type": "integer",
"example": 2
},
"proxy": {
"type": "integer",
"example": 0
},
"server": {
"type": "integer",
"example": 0
},
"hosting": {
"type": "integer",
"example": 1
},
"domain": {
"type": "integer",
"example": 3
}
},
"type": "object"
}
},
"type": "object"
}
}
}
},
"401": {
"description": "Unauthenticated"
}
},
"security": [
{
"bearerAuth": []
}
]
}
},
"/client/login": {
"post": {
"tags": [
"Client Authentication"
],
"summary": "Client login",
"description": "Authenticate a client and return access token",
"operationId": "clientLogin",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"required": [
"email",
"password"
],
"properties": {
"email": {
"type": "string",
"format": "email",
"example": "client@example.com"
},
"password": {
"type": "string",
"format": "password",
"example": "password123"
}
},
"type": "object"
}
}
}
},
"responses": {
"200": {
"description": "Login successful",
"content": {
"application/json": {
"schema": {
"properties": {
"error": {
"type": "boolean",
"example": false
},
"data": {
"properties": {
"client": {
"type": "object"
},
"token": {
"type": "string",
"example": "1|laravel_sanctum_token..."
}
},
"type": "object"
}
},
"type": "object"
}
}
}
},
"422": {
"description": "Validation error"
}
}
}
},
"/client/register": {
"post": {
"tags": [
"Client Authentication"
],
"summary": "Client registration",
"description": "Register a new client account",
"operationId": "clientRegister",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"required": [
"fullname",
"phone",
"email",
"password",
"password_confirmation"
],
"properties": {
"fullname": {
"type": "string",
"example": "Nguyen Van A"
},
"phone": {
"type": "string",
"example": "0987654321"
},
"email": {
"type": "string",
"format": "email",
"example": "client@example.com"
},
"password": {
"type": "string",
"format": "password",
"example": "password123"
},
"password_confirmation": {
"type": "string",
"format": "password",
"example": "password123"
}
},
"type": "object"
}
}
}
},
"responses": {
"200": {
"description": "Registration successful",
"content": {
"application/json": {
"schema": {
"properties": {
"error": {
"type": "boolean",
"example": false
},
"data": {
"properties": {
"client": {
"type": "object"
},
"token": {
"type": "string",
"example": "1|laravel_sanctum_token..."
}
},
"type": "object"
}
},
"type": "object"
}
}
}
},
"422": {
"description": "Validation error"
}
}
}
},
"/client/logout": {
"post": {
"tags": [
"Client Authentication"
],
"summary": "Client logout",
"description": "Logout the currently authenticated client",
"operationId": "clientLogout",
"responses": {
"200": {
"description": "Logout successful",
"content": {
"application/json": {
"schema": {
"properties": {
"error": {
"type": "boolean",
"example": false
},
"message": {
"type": "string",
"example": "Đã đăng xuất thành công."
}
},
"type": "object"
}
}
}
},
"401": {
"description": "Unauthenticated"
}
},
"security": [
{
"bearerAuth": []
}
]
}
},
"/orders": {
"get": {
"tags": [
"Orders"
],
"summary": "Get list of orders",
"description": "Returns a list of orders for the authenticated customer",
"operationId": "getOrdersList",
"parameters": [
{
"name": "total_price",
"in": "query",
"description": "Filter by total price",
"required": false,
"schema": {
"type": "number"
}
},
{
"name": "pending",
"in": "query",
"description": "Filter by pending status",
"required": false,
"schema": {
"type": "boolean"
}
}
],
"responses": {
"200": {
"description": "Successful operation",
"content": {
"application/json": {
"schema": {
"properties": {
"success": {
"type": "boolean",
"example": true
},
"data": {
"type": "array",
"items": {
"type": "object"
}
}
},
"type": "object"
}
}
}
},
"401": {
"description": "Unauthenticated"
}
},
"security": [
{
"bearerAuth": []
}
]
},
"post": {
"tags": [
"Orders"
],
"summary": "Create a new order",
"description": "Create a new order for a product",
"operationId": "storeOrder",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"required": [
"product_id",
"billing_cycle"
],
"properties": {
"product_id": {
"description": "Product UUID",
"type": "string",
"example": "550e8400-e29b-41d4-a716-446655440000"
},
"billing_cycle": {
"type": "string",
"enum": [
"monthly",
"quarterly",
"semi_annually",
"annually",
"biennially"
],
"example": "monthly"
},
"server_name": {
"type": "string",
"example": "My VPS Server"
},
"os_id": {
"description": "Operating System ID",
"type": "integer",
"example": 1
},
"addons": {
"type": "array",
"items": {
"properties": {
"addon_id": {
"type": "integer",
"example": 1
},
"qty": {
"type": "integer",
"example": 1
}
},
"type": "object"
}
}
},
"type": "object"
}
}
}
},
"responses": {
"201": {
"description": "Order created successfully",
"content": {
"application/json": {
"schema": {
"properties": {
"success": {
"type": "boolean",
"example": true
},
"data": {
"type": "object"
},
"message": {
"type": "string",
"example": "Tạo đơn hàng thành công"
}
},
"type": "object"
}
}
}
},
"401": {
"description": "Unauthenticated"
},
"422": {
"description": "Validation error"
}
},
"security": [
{
"bearerAuth": []
}
]
}
},
"/orders/{id}/pay": {
"post": {
"tags": [
"Orders"
],
"summary": "Pay for an order",
"description": "Process payment for a specific order",
"operationId": "payOrder",
"parameters": [
{
"name": "id",
"in": "path",
"description": "Order ID",
"required": true,
"schema": {
"type": "integer"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"required": [
"payment_method"
],
"properties": {
"payment_method": {
"type": "string",
"enum": [
"credit",
"vnpay",
"momo",
"bank_transfer"
],
"example": "credit"
}
},
"type": "object"
}
}
}
},
"responses": {
"200": {
"description": "Payment successful",
"content": {
"application/json": {
"schema": {
"properties": {
"success": {
"type": "boolean",
"example": true
},
"message": {
"type": "string",
"example": "Thanh toán thành công"
}
},
"type": "object"
}
}
}
},
"401": {
"description": "Unauthenticated"
},
"403": {
"description": "Forbidden - Not your order"
},
"404": {
"description": "Order not found"
}
},
"security": [
{
"bearerAuth": []
}
]
}
},
"/services/get-list": {
"get": {
"tags": [
"Services"
],
"summary": "Get list of services",
"description": "Returns a paginated list of active services/products",
"operationId": "getServiceList",
"parameters": [
{
"name": "limit",
"in": "query",
"description": "Number of items per page",
"required": false,
"schema": {
"type": "integer",
"default": 10
}
}
],
"responses": {
"200": {
"description": "Successful operation",
"content": {
"application/json": {
"schema": {
"properties": {
"error": {
"type": "boolean",
"example": false
},
"message": {
"type": "string",
"example": "Lấy dữ liệu thành công"
},
"page_type": {
"type": "string",
"example": "service"
},
"data": {
"type": "array",
"items": {
"properties": {
"uuid": {
"type": "string",
"example": "550e8400-e29b-41d4-a716-446655440000"
},
"name": {
"type": "string",
"example": "Cloud VPS Basic"
},
"slug": {
"type": "string",
"example": "cloud-vps-basic"
},
"short_description": {
"type": "string",
"example": "Basic VPS plan"
},
"description": {
"type": "string"
},
"thumbnail": {
"type": "string",
"example": "https://example.com/images/vps.jpg"
},
"product_type": {
"type": "string",
"example": "vps"
},
"specs": {
"properties": {
"cpu": {
"type": "integer",
"example": 2
},
"ram": {
"type": "integer",
"example": 4
},
"disk": {
"type": "integer",
"example": 50
},
"bandwidth": {
"type": "integer",
"example": 1000
},
"gpu": {
"type": "string",
"nullable": true
}
},
"type": "object"
},
"stock": {
"type": "integer",
"example": 100
},
"price": {
"type": "number",
"example": 299000
},
"price_sale": {
"type": "number",
"example": 199000
},
"pricing": {
"type": "object"
},
"pricing_sale": {
"type": "object"
},
"group": {
"type": "string",
"example": "VPS"
},
"os": {
"type": "array",
"items": {
"properties": {
"group": {
"type": "string",
"example": "Ubuntu"
}
},
"type": "object"
}
}
},
"type": "object"
}
}
},
"type": "object"
}
}
}
}
}
}
},
"/services/{slug}": {
"get": {
"tags": [
"Services"
],
"summary": "Get service details",
"description": "Returns detailed information about a specific service/product",
"operationId": "getServiceDetail",
"parameters": [
{
"name": "slug",
"in": "path",
"description": "Service slug",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Successful operation",
"content": {
"application/json": {
"schema": {
"properties": {
"error": {
"type": "boolean",
"example": false
},
"message": {
"type": "string",
"example": "Lấy dữ liệu thành công"
},
"page_type": {
"type": "string",
"example": "service"
},
"data": {
"properties": {
"uuid": {
"type": "string",
"example": "550e8400-e29b-41d4-a716-446655440000"
},
"name": {
"type": "string",
"example": "Cloud VPS Basic"
},
"slug": {
"type": "string",
"example": "cloud-vps-basic"
},
"short_description": {
"type": "string",
"example": "Basic VPS plan"
},
"description": {
"type": "string"
},
"thumbnail": {
"type": "string",
"example": "https://example.com/images/vps.jpg"
},
"product_type": {
"type": "string",
"example": "vps"
},
"specs": {
"properties": {
"cpu": {
"type": "integer",
"example": 2
},
"ram": {
"type": "integer",
"example": 4
},
"disk": {
"type": "integer",
"example": 50
},
"bandwidth": {
"type": "integer",
"example": 1000
},
"gpu": {
"type": "string",
"nullable": true
}
},
"type": "object"
},
"stock": {
"type": "integer",
"example": 100
},
"price": {
"type": "number",
"example": 299000
},
"price_sale": {
"type": "number",
"example": 199000
},
"group": {
"type": "string",
"example": "VPS"
},
"os": {
"type": "array",
"items": {
"properties": {
"name": {
"type": "string",
"example": "Ubuntu"
},
"thumbnail": {
"type": "string"
},
"versions": {
"type": "array",
"items": {
"properties": {
"version": {
"type": "string",
"example": "22.04"
},
"name": {
"type": "string",
"example": "Ubuntu 22.04 LTS"
}
},
"type": "object"
}
}
},
"type": "object"
}
}
},
"type": "object"
}
},
"type": "object"
}
}
}
},
"404": {
"description": "Service not found"
}
}
}
},
"/setting": {
"get": {
"tags": [
"Settings"
],
"summary": "Get application settings",
"description": "Returns the application settings",
"operationId": "getSetting",
"responses": {
"200": {
"description": "Successful operation",
"content": {
"application/json": {
"schema": {
"properties": {
"data": {
"type": "object"
},
"message": {
"type": "string",
"example": "Get setting successfully"
},
"error": {
"type": "boolean",
"example": false
}
},
"type": "object"
}
}
}
}
}
}
}
},
"components": {
"securitySchemes": {
"bearerAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "JWT",
"description": "Enter your Bearer token in the format: Bearer {token}"
}
}
},
"tags": [
{
"name": "Client Authentication",
"description": "API Endpoints for Client Authentication"
},
{
"name": "Orders",
"description": "API Endpoints for Order Management"
},
{
"name": "Services",
"description": "API Endpoints for Services/Products"
},
{
"name": "Settings",
"description": "API Endpoints for Application Settings"
}
]
}
+616 -255
View File
File diff suppressed because it is too large Load Diff