feat: API Flow Tester 기능 기반 구조 추가
- 모델: AdminApiFlow, AdminApiFlowRun - 컨트롤러: FlowTesterController - 뷰: index, create, edit, history, run-detail - 사이드바 메뉴에 "개발 도구" 그룹 추가 - 라우트 설정
This commit is contained in:
239
app/Http/Controllers/DevTools/FlowTesterController.php
Normal file
239
app/Http/Controllers/DevTools/FlowTesterController.php
Normal file
@@ -0,0 +1,239 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\DevTools;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Admin\AdminApiFlow;
|
||||
use App\Models\Admin\AdminApiFlowRun;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
/**
|
||||
* API Flow Tester Controller
|
||||
*
|
||||
* API 플로우 테스트 도구 컨트롤러
|
||||
*/
|
||||
class FlowTesterController extends Controller
|
||||
{
|
||||
/**
|
||||
* 플로우 목록
|
||||
*/
|
||||
public function index(): View
|
||||
{
|
||||
$flows = AdminApiFlow::with(['runs' => fn ($q) => $q->latest()->limit(1)])
|
||||
->orderByDesc('updated_at')
|
||||
->paginate(20);
|
||||
|
||||
return view('dev-tools.flow-tester.index', compact('flows'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 플로우 생성 폼
|
||||
*/
|
||||
public function create(): View
|
||||
{
|
||||
return view('dev-tools.flow-tester.create');
|
||||
}
|
||||
|
||||
/**
|
||||
* 플로우 저장
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:100',
|
||||
'description' => 'nullable|string',
|
||||
'category' => 'nullable|string|max:50',
|
||||
'flow_definition' => 'required|json',
|
||||
]);
|
||||
|
||||
$validated['flow_definition'] = json_decode($validated['flow_definition'], true);
|
||||
$validated['created_by'] = auth()->id();
|
||||
|
||||
$flow = AdminApiFlow::create($validated);
|
||||
|
||||
return redirect()
|
||||
->route('dev-tools.flow-tester.edit', $flow->id)
|
||||
->with('success', '플로우가 생성되었습니다.');
|
||||
}
|
||||
|
||||
/**
|
||||
* 플로우 편집 폼
|
||||
*/
|
||||
public function edit(int $id): View
|
||||
{
|
||||
$flow = AdminApiFlow::findOrFail($id);
|
||||
|
||||
return view('dev-tools.flow-tester.edit', compact('flow'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 플로우 수정
|
||||
*/
|
||||
public function update(Request $request, int $id)
|
||||
{
|
||||
$flow = AdminApiFlow::findOrFail($id);
|
||||
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:100',
|
||||
'description' => 'nullable|string',
|
||||
'category' => 'nullable|string|max:50',
|
||||
'flow_definition' => 'required|json',
|
||||
'is_active' => 'boolean',
|
||||
]);
|
||||
|
||||
$validated['flow_definition'] = json_decode($validated['flow_definition'], true);
|
||||
$validated['updated_by'] = auth()->id();
|
||||
|
||||
$flow->update($validated);
|
||||
|
||||
return redirect()
|
||||
->route('dev-tools.flow-tester.edit', $flow->id)
|
||||
->with('success', '플로우가 수정되었습니다.');
|
||||
}
|
||||
|
||||
/**
|
||||
* 플로우 삭제
|
||||
*/
|
||||
public function destroy(int $id)
|
||||
{
|
||||
$flow = AdminApiFlow::findOrFail($id);
|
||||
$flow->delete();
|
||||
|
||||
return redirect()
|
||||
->route('dev-tools.flow-tester.index')
|
||||
->with('success', '플로우가 삭제되었습니다.');
|
||||
}
|
||||
|
||||
/**
|
||||
* 플로우 복제
|
||||
*/
|
||||
public function clone(int $id)
|
||||
{
|
||||
$original = AdminApiFlow::findOrFail($id);
|
||||
|
||||
$clone = $original->replicate();
|
||||
$clone->name = $original->name.' (복사본)';
|
||||
$clone->created_by = auth()->id();
|
||||
$clone->updated_by = null;
|
||||
$clone->save();
|
||||
|
||||
return redirect()
|
||||
->route('dev-tools.flow-tester.edit', $clone->id)
|
||||
->with('success', '플로우가 복제되었습니다.');
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON 유효성 검사 (HTMX)
|
||||
*/
|
||||
public function validateJson(Request $request)
|
||||
{
|
||||
$json = $request->input('flow_definition', '');
|
||||
|
||||
try {
|
||||
$data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
|
||||
|
||||
// 기본 구조 검증
|
||||
$errors = [];
|
||||
|
||||
if (! isset($data['steps']) || ! is_array($data['steps'])) {
|
||||
$errors[] = 'steps 배열이 필요합니다.';
|
||||
} else {
|
||||
foreach ($data['steps'] as $i => $step) {
|
||||
if (empty($step['id'])) {
|
||||
$errors[] = "steps[{$i}]: id가 필요합니다.";
|
||||
}
|
||||
if (empty($step['method'])) {
|
||||
$errors[] = "steps[{$i}]: method가 필요합니다.";
|
||||
}
|
||||
if (empty($step['endpoint'])) {
|
||||
$errors[] = "steps[{$i}]: endpoint가 필요합니다.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (! empty($errors)) {
|
||||
return response()->json([
|
||||
'valid' => false,
|
||||
'errors' => $errors,
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'valid' => true,
|
||||
'stepCount' => count($data['steps'] ?? []),
|
||||
]);
|
||||
} catch (\JsonException $e) {
|
||||
return response()->json([
|
||||
'valid' => false,
|
||||
'errors' => ['JSON 파싱 오류: '.$e->getMessage()],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 플로우 실행
|
||||
*/
|
||||
public function run(int $id)
|
||||
{
|
||||
$flow = AdminApiFlow::findOrFail($id);
|
||||
|
||||
// 실행 기록 생성
|
||||
$run = AdminApiFlowRun::create([
|
||||
'flow_id' => $flow->id,
|
||||
'status' => AdminApiFlowRun::STATUS_RUNNING,
|
||||
'started_at' => now(),
|
||||
'total_steps' => $flow->step_count,
|
||||
'executed_by' => auth()->id(),
|
||||
]);
|
||||
|
||||
// TODO: 실제 플로우 실행 로직은 Service 클래스로 분리
|
||||
// 현재는 스캐폴딩만 제공
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'run_id' => $run->id,
|
||||
'message' => '플로우 실행이 시작되었습니다.',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 실행 상태 조회 (Polling)
|
||||
*/
|
||||
public function runStatus(int $runId)
|
||||
{
|
||||
$run = AdminApiFlowRun::findOrFail($runId);
|
||||
|
||||
return response()->json([
|
||||
'status' => $run->status,
|
||||
'progress' => $run->progress,
|
||||
'completed_steps' => $run->completed_steps,
|
||||
'total_steps' => $run->total_steps,
|
||||
'is_completed' => $run->isCompleted(),
|
||||
'execution_log' => $run->execution_log,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 실행 이력 목록
|
||||
*/
|
||||
public function history(int $id): View
|
||||
{
|
||||
$flow = AdminApiFlow::findOrFail($id);
|
||||
$runs = $flow->runs()
|
||||
->orderByDesc('created_at')
|
||||
->paginate(20);
|
||||
|
||||
return view('dev-tools.flow-tester.history', compact('flow', 'runs'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 실행 상세 보기
|
||||
*/
|
||||
public function runDetail(int $runId): View
|
||||
{
|
||||
$run = AdminApiFlowRun::with('flow')->findOrFail($runId);
|
||||
|
||||
return view('dev-tools.flow-tester.run-detail', compact('run'));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user