- Config: api-explorer.php (환경, 보안, 캐시 설정) - Migration: api_bookmarks, api_templates, api_histories, api_environments - Model: ApiBookmark, ApiTemplate, ApiHistory, ApiEnvironment - Service: OpenApiParserService, ApiRequestService, ApiExplorerService - Controller: ApiExplorerController (CRUD, 실행, 히스토리) - View: 3-Panel 레이아웃 (sidebar, request, response, history) - Route: 23개 엔드포인트 등록 Swagger UI 대체 개발 도구, HTMX 기반 SPA 경험
353 lines
10 KiB
PHP
353 lines
10 KiB
PHP
<?php
|
|
|
|
namespace App\Services\ApiExplorer;
|
|
|
|
use App\Models\DevTools\ApiBookmark;
|
|
use App\Models\DevTools\ApiEnvironment;
|
|
use App\Models\DevTools\ApiHistory;
|
|
use App\Models\DevTools\ApiTemplate;
|
|
use Illuminate\Support\Collection;
|
|
|
|
/**
|
|
* API Explorer 비즈니스 로직 통합 서비스
|
|
*/
|
|
class ApiExplorerService
|
|
{
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Bookmark Operations
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
/**
|
|
* 즐겨찾기 목록 조회
|
|
*/
|
|
public function getBookmarks(int $userId): Collection
|
|
{
|
|
return ApiBookmark::where('user_id', $userId)
|
|
->ordered()
|
|
->get();
|
|
}
|
|
|
|
/**
|
|
* 즐겨찾기 추가
|
|
*/
|
|
public function addBookmark(int $userId, array $data): ApiBookmark
|
|
{
|
|
$maxOrder = ApiBookmark::where('user_id', $userId)->max('display_order') ?? 0;
|
|
|
|
return ApiBookmark::create([
|
|
'user_id' => $userId,
|
|
'endpoint' => $data['endpoint'],
|
|
'method' => strtoupper($data['method']),
|
|
'display_name' => $data['display_name'] ?? null,
|
|
'display_order' => $maxOrder + 1,
|
|
'color' => $data['color'] ?? null,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* 즐겨찾기 제거
|
|
*/
|
|
public function removeBookmark(int $bookmarkId): void
|
|
{
|
|
ApiBookmark::where('id', $bookmarkId)->delete();
|
|
}
|
|
|
|
/**
|
|
* 즐겨찾기 순서 변경
|
|
*/
|
|
public function reorderBookmarks(int $userId, array $order): void
|
|
{
|
|
foreach ($order as $index => $id) {
|
|
ApiBookmark::where('id', $id)
|
|
->where('user_id', $userId)
|
|
->update(['display_order' => $index]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 즐겨찾기 여부 확인
|
|
*/
|
|
public function isBookmarked(int $userId, string $endpoint, string $method): bool
|
|
{
|
|
return ApiBookmark::where('user_id', $userId)
|
|
->where('endpoint', $endpoint)
|
|
->where('method', strtoupper($method))
|
|
->exists();
|
|
}
|
|
|
|
/**
|
|
* 즐겨찾기 토글
|
|
*/
|
|
public function toggleBookmark(int $userId, array $data): array
|
|
{
|
|
$existing = ApiBookmark::where('user_id', $userId)
|
|
->where('endpoint', $data['endpoint'])
|
|
->where('method', strtoupper($data['method']))
|
|
->first();
|
|
|
|
if ($existing) {
|
|
$existing->delete();
|
|
|
|
return ['action' => 'removed', 'bookmark' => null];
|
|
}
|
|
|
|
$bookmark = $this->addBookmark($userId, $data);
|
|
|
|
return ['action' => 'added', 'bookmark' => $bookmark];
|
|
}
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Template Operations
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
/**
|
|
* 템플릿 목록 조회
|
|
*/
|
|
public function getTemplates(int $userId, ?string $endpoint = null, ?string $method = null): Collection
|
|
{
|
|
$query = ApiTemplate::where(function ($q) use ($userId) {
|
|
$q->where('user_id', $userId)
|
|
->orWhere('is_shared', true);
|
|
});
|
|
|
|
if ($endpoint) {
|
|
$query->where('endpoint', $endpoint);
|
|
}
|
|
|
|
if ($method) {
|
|
$query->where('method', strtoupper($method));
|
|
}
|
|
|
|
return $query->orderBy('name')->get();
|
|
}
|
|
|
|
/**
|
|
* 템플릿 저장
|
|
*/
|
|
public function saveTemplate(int $userId, array $data): ApiTemplate
|
|
{
|
|
return ApiTemplate::create([
|
|
'user_id' => $userId,
|
|
'endpoint' => $data['endpoint'],
|
|
'method' => strtoupper($data['method']),
|
|
'name' => $data['name'],
|
|
'description' => $data['description'] ?? null,
|
|
'headers' => $data['headers'] ?? null,
|
|
'path_params' => $data['path_params'] ?? null,
|
|
'query_params' => $data['query_params'] ?? null,
|
|
'body' => $data['body'] ?? null,
|
|
'is_shared' => $data['is_shared'] ?? false,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* 템플릿 수정
|
|
*/
|
|
public function updateTemplate(int $templateId, array $data): ApiTemplate
|
|
{
|
|
$template = ApiTemplate::findOrFail($templateId);
|
|
|
|
$template->update([
|
|
'name' => $data['name'] ?? $template->name,
|
|
'description' => $data['description'] ?? $template->description,
|
|
'headers' => $data['headers'] ?? $template->headers,
|
|
'path_params' => $data['path_params'] ?? $template->path_params,
|
|
'query_params' => $data['query_params'] ?? $template->query_params,
|
|
'body' => $data['body'] ?? $template->body,
|
|
'is_shared' => $data['is_shared'] ?? $template->is_shared,
|
|
]);
|
|
|
|
return $template->fresh();
|
|
}
|
|
|
|
/**
|
|
* 템플릿 삭제
|
|
*/
|
|
public function deleteTemplate(int $templateId): void
|
|
{
|
|
ApiTemplate::where('id', $templateId)->delete();
|
|
}
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| History Operations
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
/**
|
|
* 요청 기록 저장
|
|
*/
|
|
public function logRequest(int $userId, array $data): ApiHistory
|
|
{
|
|
// 최대 개수 초과 시 오래된 것 삭제
|
|
$maxEntries = config('api-explorer.history.max_entries', 100);
|
|
$count = ApiHistory::where('user_id', $userId)->count();
|
|
|
|
if ($count >= $maxEntries) {
|
|
$deleteCount = $count - $maxEntries + 1;
|
|
ApiHistory::where('user_id', $userId)
|
|
->oldest('created_at')
|
|
->limit($deleteCount)
|
|
->delete();
|
|
}
|
|
|
|
return ApiHistory::create([
|
|
'user_id' => $userId,
|
|
'endpoint' => $data['endpoint'],
|
|
'method' => strtoupper($data['method']),
|
|
'request_headers' => $data['request_headers'] ?? null,
|
|
'request_body' => $data['request_body'] ?? null,
|
|
'response_status' => $data['response_status'],
|
|
'response_headers' => $data['response_headers'] ?? null,
|
|
'response_body' => $data['response_body'] ?? null,
|
|
'duration_ms' => $data['duration_ms'],
|
|
'environment' => $data['environment'],
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* 히스토리 조회
|
|
*/
|
|
public function getHistory(int $userId, int $limit = 50): Collection
|
|
{
|
|
return ApiHistory::where('user_id', $userId)
|
|
->latest('created_at')
|
|
->limit($limit)
|
|
->get();
|
|
}
|
|
|
|
/**
|
|
* 히스토리 삭제
|
|
*/
|
|
public function clearHistory(int $userId): int
|
|
{
|
|
return ApiHistory::where('user_id', $userId)->delete();
|
|
}
|
|
|
|
/**
|
|
* 단일 히스토리 조회
|
|
*/
|
|
public function getHistoryItem(int $historyId): ?ApiHistory
|
|
{
|
|
return ApiHistory::find($historyId);
|
|
}
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Environment Operations
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
/**
|
|
* 환경 목록 조회
|
|
*/
|
|
public function getEnvironments(int $userId): Collection
|
|
{
|
|
return ApiEnvironment::where('user_id', $userId)
|
|
->orderBy('is_default', 'desc')
|
|
->orderBy('name')
|
|
->get();
|
|
}
|
|
|
|
/**
|
|
* 환경 저장
|
|
*/
|
|
public function saveEnvironment(int $userId, array $data): ApiEnvironment
|
|
{
|
|
// 기본 환경으로 설정 시 기존 기본 해제
|
|
if ($data['is_default'] ?? false) {
|
|
ApiEnvironment::where('user_id', $userId)
|
|
->update(['is_default' => false]);
|
|
}
|
|
|
|
return ApiEnvironment::create([
|
|
'user_id' => $userId,
|
|
'name' => $data['name'],
|
|
'base_url' => $data['base_url'],
|
|
'api_key' => $data['api_key'] ?? null,
|
|
'auth_token' => $data['auth_token'] ?? null,
|
|
'variables' => $data['variables'] ?? null,
|
|
'is_default' => $data['is_default'] ?? false,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* 환경 수정
|
|
*/
|
|
public function updateEnvironment(int $environmentId, array $data): ApiEnvironment
|
|
{
|
|
$environment = ApiEnvironment::findOrFail($environmentId);
|
|
|
|
// 기본 환경으로 설정 시 기존 기본 해제
|
|
if ($data['is_default'] ?? false) {
|
|
ApiEnvironment::where('user_id', $environment->user_id)
|
|
->where('id', '!=', $environmentId)
|
|
->update(['is_default' => false]);
|
|
}
|
|
|
|
$environment->update($data);
|
|
|
|
return $environment->fresh();
|
|
}
|
|
|
|
/**
|
|
* 환경 삭제
|
|
*/
|
|
public function deleteEnvironment(int $environmentId): void
|
|
{
|
|
ApiEnvironment::where('id', $environmentId)->delete();
|
|
}
|
|
|
|
/**
|
|
* 기본 환경 설정
|
|
*/
|
|
public function setDefaultEnvironment(int $userId, int $environmentId): void
|
|
{
|
|
// 기존 기본 해제
|
|
ApiEnvironment::where('user_id', $userId)
|
|
->update(['is_default' => false]);
|
|
|
|
// 새 기본 설정
|
|
ApiEnvironment::where('id', $environmentId)
|
|
->where('user_id', $userId)
|
|
->update(['is_default' => true]);
|
|
}
|
|
|
|
/**
|
|
* 기본 환경 조회
|
|
*/
|
|
public function getDefaultEnvironment(int $userId): ?ApiEnvironment
|
|
{
|
|
return ApiEnvironment::where('user_id', $userId)
|
|
->where('is_default', true)
|
|
->first();
|
|
}
|
|
|
|
/**
|
|
* 기본 환경 초기화 (설정된 환경이 없는 경우)
|
|
*/
|
|
public function initializeDefaultEnvironments(int $userId): void
|
|
{
|
|
$count = ApiEnvironment::where('user_id', $userId)->count();
|
|
|
|
if ($count > 0) {
|
|
return;
|
|
}
|
|
|
|
$defaults = config('api-explorer.default_environments', []);
|
|
|
|
foreach ($defaults as $index => $env) {
|
|
$this->saveEnvironment($userId, [
|
|
'name' => $env['name'],
|
|
'base_url' => $env['base_url'],
|
|
'api_key' => $env['api_key'] ?? null,
|
|
'is_default' => $index === 0,
|
|
]);
|
|
}
|
|
}
|
|
}
|