Init
This commit is contained in:
8
app/Http/Controllers/Controller.php
Normal file
8
app/Http/Controllers/Controller.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
//
|
||||
}
|
||||
836
app/Http/Controllers/TemporalDemoController.php
Normal file
836
app/Http/Controllers/TemporalDemoController.php
Normal file
@@ -0,0 +1,836 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\ImportJob;
|
||||
use App\Models\Order;
|
||||
use App\Temporal\DataEnrichment\DataEnrichmentWorkflowInterface;
|
||||
use App\Temporal\EloquentQuery\EloquentQueryWorkflowInterface;
|
||||
use App\Temporal\ExternalApiSync\ExternalApiSyncWorkflowInterface;
|
||||
use App\Temporal\OrderFulfillment\OrderFulfillmentWorkflowInterface;
|
||||
use App\Temporal\ProductImport\ProductImportWorkflowInterface;
|
||||
use App\Temporal\SystemMonitor\SystemMonitorWorkflowInterface;
|
||||
use App\Temporal\UserMigration\UserMigrationWorkflowInterface;
|
||||
use App\Temporal\WebhookDelivery\WebhookDeliveryWorkflowInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response as InertiaResponse;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
use Temporal\Client\GRPC\ServiceClient;
|
||||
use Temporal\Client\WorkflowClient;
|
||||
use Temporal\Client\WorkflowClientInterface;
|
||||
use Temporal\Client\WorkflowOptions;
|
||||
use Temporal\Workflow\WorkflowExecutionStatus;
|
||||
|
||||
class TemporalDemoController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private WorkflowClientInterface $workflowClient,
|
||||
) {}
|
||||
|
||||
public function dashboard(): InertiaResponse
|
||||
{
|
||||
$importJobs = ImportJob::latest()->take(10)->get();
|
||||
$orders = Order::latest()->take(10)->get();
|
||||
|
||||
return Inertia::render('Dashboard', [
|
||||
'importJobs' => $importJobs,
|
||||
'orders' => $orders,
|
||||
]);
|
||||
}
|
||||
|
||||
// --- Reset & Terminate ---
|
||||
|
||||
public function reset(): StreamedResponse
|
||||
{
|
||||
return $this->streamedOperation(function () {
|
||||
$this->emit('Starting full reset...', 'step');
|
||||
|
||||
// 1. Look up container IDs (before dropping DBs, which may crash them)
|
||||
$containerId = null;
|
||||
$workerContainerId = null;
|
||||
try {
|
||||
$containerId = $this->findDockerContainer('temporal');
|
||||
$workerContainerId = $this->findDockerContainer('temporal-worker');
|
||||
} catch (\Throwable $e) {
|
||||
$this->emit("Docker lookup failed: {$e->getMessage()}", 'error');
|
||||
}
|
||||
|
||||
// 2. Drop Temporal databases (all workflow history will be wiped)
|
||||
$this->emit('Dropping Temporal databases...', 'step');
|
||||
try {
|
||||
$pdo = new \PDO('pgsql:host=temporal-pgsql;port=5432;dbname=postgres', 'temporal', 'temporal');
|
||||
$pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
|
||||
|
||||
foreach (['temporal', 'temporal_visibility'] as $db) {
|
||||
$pdo->exec("SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '{$db}' AND pid <> pg_backend_pid()");
|
||||
$pdo->exec("DROP DATABASE IF EXISTS {$db}");
|
||||
$pdo->exec("CREATE DATABASE {$db} OWNER temporal");
|
||||
$this->emit("Recreated database: {$db}");
|
||||
}
|
||||
$this->emit('Temporal databases reset', 'success');
|
||||
} catch (\Throwable $e) {
|
||||
$this->emit("Database reset failed: {$e->getMessage()}", 'error');
|
||||
}
|
||||
|
||||
// 3. Restart Temporal container (using pre-fetched ID)
|
||||
$this->emit('Restarting Temporal container...', 'step');
|
||||
try {
|
||||
$this->restartDockerContainer($containerId);
|
||||
$this->emit('Restart signal sent');
|
||||
} catch (\Throwable $e) {
|
||||
$this->emit("Docker restart failed: {$e->getMessage()}", 'error');
|
||||
}
|
||||
|
||||
// 4. Wait for Temporal to come back
|
||||
$this->emit('Waiting for Temporal to come back online...', 'step');
|
||||
$start = time();
|
||||
$online = false;
|
||||
$lastUpdate = 0;
|
||||
while (time() - $start < 30) {
|
||||
try {
|
||||
$client = WorkflowClient::create(
|
||||
ServiceClient::create(config('temporal.address'))
|
||||
);
|
||||
$client->listWorkflowExecutions('ExecutionStatus="Running"', pageSize: 1);
|
||||
$online = true;
|
||||
break;
|
||||
} catch (\Throwable) {
|
||||
$elapsed = time() - $start;
|
||||
if ($elapsed - $lastUpdate >= 3) {
|
||||
$this->emit("Still waiting... ({$elapsed}s)");
|
||||
$lastUpdate = $elapsed;
|
||||
}
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
if ($online) {
|
||||
$this->emit('Temporal is back online', 'success');
|
||||
} else {
|
||||
$this->emit('Temporal did not come back within 30s — may need manual restart', 'error');
|
||||
}
|
||||
|
||||
// 5. Restart worker
|
||||
$this->emit('Restarting worker...', 'step');
|
||||
try {
|
||||
$this->restartDockerContainer($workerContainerId);
|
||||
$this->emit('Worker restarted', 'success');
|
||||
} catch (\Throwable $e) {
|
||||
$this->emit("Worker restart failed: {$e->getMessage()}", 'error');
|
||||
}
|
||||
|
||||
// 6. Reset Laravel database
|
||||
$this->emit('Running migrate:fresh --seed...', 'step');
|
||||
try {
|
||||
Artisan::call('migrate:fresh', ['--seed' => true, '--seeder' => 'TemporalDemoSeeder', '--force' => true]);
|
||||
$this->emit('Database reset and seeded', 'success');
|
||||
} catch (\Throwable $e) {
|
||||
$this->emit("Migration failed: {$e->getMessage()}", 'error');
|
||||
}
|
||||
|
||||
$this->emit('Reset complete', 'done');
|
||||
});
|
||||
}
|
||||
|
||||
public function terminateAll(): StreamedResponse
|
||||
{
|
||||
return $this->streamedOperation(function () {
|
||||
$this->emit('Listing running workflows...', 'step');
|
||||
$terminated = 0;
|
||||
|
||||
try {
|
||||
$paginator = $this->workflowClient->listWorkflowExecutions(
|
||||
'ExecutionStatus="Running"',
|
||||
pageSize: 100,
|
||||
);
|
||||
|
||||
foreach ($paginator as $info) {
|
||||
try {
|
||||
$wfId = $info->execution->getID();
|
||||
$stub = $this->workflowClient->newUntypedRunningWorkflowStub(
|
||||
$wfId,
|
||||
$info->execution->getRunID(),
|
||||
);
|
||||
$stub->terminate('Manual termination from dashboard');
|
||||
$terminated++;
|
||||
$this->emit("Terminated: {$wfId}", 'warn');
|
||||
} catch (\Throwable) {
|
||||
// Already completed
|
||||
}
|
||||
}
|
||||
|
||||
$this->emit("Terminated {$terminated} workflow(s)", 'success');
|
||||
} catch (\Throwable $e) {
|
||||
$this->emit("Failed to connect to Temporal: {$e->getMessage()}", 'error');
|
||||
}
|
||||
|
||||
$this->emit($terminated > 0 ? 'All workflows terminated' : 'No running workflows found', 'done');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check actual execution status via Temporal's DescribeWorkflowExecution.
|
||||
* Returns a terminal status string, or null if the workflow is still running.
|
||||
*/
|
||||
private function resolveWorkflowStatus(string $workflowId): ?string
|
||||
{
|
||||
try {
|
||||
$stub = $this->workflowClient->newUntypedRunningWorkflowStub($workflowId);
|
||||
$description = $stub->describe();
|
||||
|
||||
return match ($description->info->status) {
|
||||
WorkflowExecutionStatus::Completed => 'completed',
|
||||
WorkflowExecutionStatus::Failed, WorkflowExecutionStatus::TimedOut => 'failed',
|
||||
WorkflowExecutionStatus::Canceled, WorkflowExecutionStatus::Terminated => 'cancelled',
|
||||
default => null,
|
||||
};
|
||||
} catch (\Throwable) {
|
||||
// Workflow not found in Temporal — treat as failed
|
||||
return 'failed';
|
||||
}
|
||||
}
|
||||
|
||||
private function streamedOperation(\Closure $callback): StreamedResponse
|
||||
{
|
||||
return response()->stream(function () use ($callback) {
|
||||
while (ob_get_level()) {
|
||||
ob_end_flush();
|
||||
}
|
||||
$callback();
|
||||
}, 200, [
|
||||
'Content-Type' => 'text/plain',
|
||||
'Cache-Control' => 'no-cache',
|
||||
'X-Accel-Buffering' => 'no',
|
||||
]);
|
||||
}
|
||||
|
||||
private function emit(string $message, string $type = 'info'): void
|
||||
{
|
||||
echo json_encode([
|
||||
'type' => $type,
|
||||
'message' => $message,
|
||||
'time' => date('H:i:s'),
|
||||
]) . "\n";
|
||||
flush();
|
||||
}
|
||||
|
||||
private function findDockerContainer(string $serviceName): ?string
|
||||
{
|
||||
$socketPath = '/var/run/docker.sock';
|
||||
if (!file_exists($socketPath)) {
|
||||
$this->emit('Docker socket not found — cannot restart automatically', 'error');
|
||||
return null;
|
||||
}
|
||||
|
||||
$filters = urlencode(json_encode([
|
||||
'label' => [
|
||||
"com.docker.compose.service={$serviceName}",
|
||||
'com.docker.compose.project=temporalio-test',
|
||||
],
|
||||
]));
|
||||
|
||||
$response = $this->dockerApiGet("/containers/json?all=true&filters={$filters}");
|
||||
$containers = json_decode($response, true);
|
||||
|
||||
if (empty($containers)) {
|
||||
$this->emit("Container for service '{$serviceName}' not found", 'error');
|
||||
return null;
|
||||
}
|
||||
|
||||
$containerId = $containers[0]['Id'];
|
||||
$containerName = ltrim($containers[0]['Names'][0] ?? $containerId, '/');
|
||||
$this->emit("Found container: {$containerName}");
|
||||
|
||||
return $containerId;
|
||||
}
|
||||
|
||||
private function restartDockerContainer(?string $containerId): void
|
||||
{
|
||||
if (!$containerId) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->dockerApiPost("/containers/{$containerId}/restart?t=5");
|
||||
}
|
||||
|
||||
private function dockerApiGet(string $path): string
|
||||
{
|
||||
return $this->dockerApiRequest('GET', $path);
|
||||
}
|
||||
|
||||
private function dockerApiPost(string $path): string
|
||||
{
|
||||
return $this->dockerApiRequest('POST', $path);
|
||||
}
|
||||
|
||||
private function dockerApiRequest(string $method, string $path): string
|
||||
{
|
||||
$socket = stream_socket_client('unix:///var/run/docker.sock', $errno, $errstr, 5);
|
||||
if (!$socket) {
|
||||
$this->emit("Docker socket connection failed: {$errstr}", 'error');
|
||||
return '';
|
||||
}
|
||||
|
||||
stream_set_timeout($socket, 30);
|
||||
|
||||
$request = "{$method} {$path} HTTP/1.0\r\nHost: localhost\r\n\r\n";
|
||||
fwrite($socket, $request);
|
||||
|
||||
$response = '';
|
||||
while (!feof($socket)) {
|
||||
$chunk = fread($socket, 8192);
|
||||
if ($chunk === false) break;
|
||||
$meta = stream_get_meta_data($socket);
|
||||
if ($meta['timed_out']) {
|
||||
$this->emit('Docker API request timed out', 'error');
|
||||
break;
|
||||
}
|
||||
$response .= $chunk;
|
||||
}
|
||||
fclose($socket);
|
||||
|
||||
// Strip HTTP headers — body comes after \r\n\r\n
|
||||
$parts = explode("\r\n\r\n", $response, 2);
|
||||
return $parts[1] ?? '';
|
||||
}
|
||||
|
||||
// --- Product Import ---
|
||||
|
||||
public function startImport(Request $request): JsonResponse
|
||||
{
|
||||
$filePath = storage_path('app/imports/products.csv');
|
||||
|
||||
if (!file_exists($filePath)) {
|
||||
return response()->json(['error' => 'CSV file not found. Run TemporalDemoSeeder first.'], 404);
|
||||
}
|
||||
|
||||
$simulationConfig = $request->input('simulation', []);
|
||||
$workflowId = 'product-import-' . uniqid();
|
||||
|
||||
$stub = $this->workflowClient->newWorkflowStub(
|
||||
ProductImportWorkflowInterface::class,
|
||||
WorkflowOptions::new()
|
||||
->withWorkflowId($workflowId)
|
||||
->withTaskQueue(config('temporal.task_queue'))
|
||||
);
|
||||
|
||||
$run = $this->workflowClient->start($stub, $filePath, $simulationConfig);
|
||||
|
||||
$importJob = ImportJob::create([
|
||||
'workflow_id' => $workflowId,
|
||||
'run_id' => $run->getExecution()->getRunID(),
|
||||
'type' => 'product_import',
|
||||
'file_path' => $filePath,
|
||||
'status' => 'started',
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Product import started',
|
||||
'workflow_id' => $workflowId,
|
||||
'run_id' => $run->getExecution()->getRunID(),
|
||||
'import_job_id' => $importJob->id,
|
||||
]);
|
||||
}
|
||||
|
||||
public function pauseImport(string $id): JsonResponse
|
||||
{
|
||||
$importJob = ImportJob::findOrFail($id);
|
||||
|
||||
$stub = $this->workflowClient->newRunningWorkflowStub(
|
||||
ProductImportWorkflowInterface::class,
|
||||
$importJob->workflow_id
|
||||
);
|
||||
|
||||
$stub->pause();
|
||||
|
||||
$importJob->update(['status' => 'paused']);
|
||||
|
||||
return response()->json(['message' => 'Import paused', 'workflow_id' => $importJob->workflow_id]);
|
||||
}
|
||||
|
||||
public function resumeImport(string $id): JsonResponse
|
||||
{
|
||||
$importJob = ImportJob::findOrFail($id);
|
||||
|
||||
$stub = $this->workflowClient->newRunningWorkflowStub(
|
||||
ProductImportWorkflowInterface::class,
|
||||
$importJob->workflow_id
|
||||
);
|
||||
|
||||
$stub->resume();
|
||||
|
||||
$importJob->update(['status' => 'processing']);
|
||||
|
||||
return response()->json(['message' => 'Import resumed', 'workflow_id' => $importJob->workflow_id]);
|
||||
}
|
||||
|
||||
public function importStatus(string $id): JsonResponse
|
||||
{
|
||||
$importJob = ImportJob::findOrFail($id);
|
||||
|
||||
try {
|
||||
$stub = $this->workflowClient->newRunningWorkflowStub(
|
||||
ProductImportWorkflowInterface::class,
|
||||
$importJob->workflow_id
|
||||
);
|
||||
|
||||
$status = $stub->getStatus();
|
||||
|
||||
// Query handler returns cached internal state — cross-check
|
||||
// actual execution status when the query says non-terminal
|
||||
if (!in_array($status['status'], ['completed', 'cancelled', 'failed'])) {
|
||||
$resolved = $this->resolveWorkflowStatus($importJob->workflow_id);
|
||||
if ($resolved !== null) {
|
||||
$status['status'] = $resolved;
|
||||
}
|
||||
}
|
||||
|
||||
$importJob->update([
|
||||
'status' => $status['status'],
|
||||
'total_records' => $status['totalRecords'] ?? 0,
|
||||
'processed_records' => $status['processedRecords'] ?? 0,
|
||||
'failed_records' => $status['failedRecords'] ?? 0,
|
||||
]);
|
||||
|
||||
return response()->json($status);
|
||||
} catch (\Throwable $e) {
|
||||
$resolvedStatus = $this->resolveWorkflowStatus($importJob->workflow_id);
|
||||
if ($resolvedStatus !== null) {
|
||||
$importJob->update(['status' => $resolvedStatus]);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'status' => $resolvedStatus ?? $importJob->status,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Order Fulfillment ---
|
||||
|
||||
public function processOrder(Request $request, int $orderId): JsonResponse
|
||||
{
|
||||
$order = Order::findOrFail($orderId);
|
||||
|
||||
$simulationConfig = $request->input('simulation', []);
|
||||
$workflowId = 'order-fulfillment-' . $orderId;
|
||||
|
||||
$stub = $this->workflowClient->newWorkflowStub(
|
||||
OrderFulfillmentWorkflowInterface::class,
|
||||
WorkflowOptions::new()
|
||||
->withWorkflowId($workflowId)
|
||||
->withTaskQueue(config('temporal.task_queue'))
|
||||
);
|
||||
|
||||
$run = $this->workflowClient->start($stub, $orderId, $simulationConfig);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Order processing started',
|
||||
'workflow_id' => $workflowId,
|
||||
'run_id' => $run->getExecution()->getRunID(),
|
||||
'order_id' => $orderId,
|
||||
]);
|
||||
}
|
||||
|
||||
public function shipOrder(int $orderId, Request $request): JsonResponse
|
||||
{
|
||||
$trackingNumber = $request->input('tracking_number', 'TRACK-' . strtoupper(uniqid()));
|
||||
$workflowId = 'order-fulfillment-' . $orderId;
|
||||
|
||||
$stub = $this->workflowClient->newRunningWorkflowStub(
|
||||
OrderFulfillmentWorkflowInterface::class,
|
||||
$workflowId
|
||||
);
|
||||
|
||||
$stub->confirmShipping($trackingNumber);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Shipping confirmation sent',
|
||||
'workflow_id' => $workflowId,
|
||||
'tracking_number' => $trackingNumber,
|
||||
]);
|
||||
}
|
||||
|
||||
public function orderStatus(int $orderId): JsonResponse
|
||||
{
|
||||
$workflowId = 'order-fulfillment-' . $orderId;
|
||||
|
||||
try {
|
||||
$stub = $this->workflowClient->newRunningWorkflowStub(
|
||||
OrderFulfillmentWorkflowInterface::class,
|
||||
$workflowId
|
||||
);
|
||||
|
||||
$status = $stub->getOrderStatus();
|
||||
|
||||
return response()->json($status);
|
||||
} catch (\Throwable $e) {
|
||||
return response()->json([
|
||||
'error' => $e->getMessage(),
|
||||
'order_id' => $orderId,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// --- User Migration ---
|
||||
|
||||
public function startMigration(Request $request): JsonResponse
|
||||
{
|
||||
$totalUsers = $request->input('total_users', 100);
|
||||
$batchSize = $request->input('batch_size', 20);
|
||||
$simulationConfig = $request->input('simulation', []);
|
||||
|
||||
$workflowId = 'user-migration-' . uniqid();
|
||||
|
||||
$stub = $this->workflowClient->newWorkflowStub(
|
||||
UserMigrationWorkflowInterface::class,
|
||||
WorkflowOptions::new()
|
||||
->withWorkflowId($workflowId)
|
||||
->withTaskQueue(config('temporal.task_queue'))
|
||||
);
|
||||
|
||||
$run = $this->workflowClient->start($stub, (int) $totalUsers, (int) $batchSize, $simulationConfig);
|
||||
|
||||
$importJob = ImportJob::create([
|
||||
'workflow_id' => $workflowId,
|
||||
'run_id' => $run->getExecution()->getRunID(),
|
||||
'type' => 'user_migration',
|
||||
'status' => 'started',
|
||||
'total_records' => $totalUsers,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'User migration started',
|
||||
'workflow_id' => $workflowId,
|
||||
'run_id' => $run->getExecution()->getRunID(),
|
||||
'import_job_id' => $importJob->id,
|
||||
]);
|
||||
}
|
||||
|
||||
public function pauseMigration(string $id): JsonResponse
|
||||
{
|
||||
$importJob = ImportJob::findOrFail($id);
|
||||
|
||||
$stub = $this->workflowClient->newRunningWorkflowStub(
|
||||
UserMigrationWorkflowInterface::class,
|
||||
$importJob->workflow_id
|
||||
);
|
||||
|
||||
$stub->pause();
|
||||
|
||||
$importJob->update(['status' => 'paused']);
|
||||
|
||||
return response()->json(['message' => 'Migration paused', 'workflow_id' => $importJob->workflow_id]);
|
||||
}
|
||||
|
||||
public function resumeMigration(string $id): JsonResponse
|
||||
{
|
||||
$importJob = ImportJob::findOrFail($id);
|
||||
|
||||
$stub = $this->workflowClient->newRunningWorkflowStub(
|
||||
UserMigrationWorkflowInterface::class,
|
||||
$importJob->workflow_id
|
||||
);
|
||||
|
||||
$stub->resume();
|
||||
|
||||
$importJob->update(['status' => 'processing']);
|
||||
|
||||
return response()->json(['message' => 'Migration resumed', 'workflow_id' => $importJob->workflow_id]);
|
||||
}
|
||||
|
||||
public function migrationStatus(string $id): JsonResponse
|
||||
{
|
||||
$importJob = ImportJob::findOrFail($id);
|
||||
|
||||
try {
|
||||
$stub = $this->workflowClient->newRunningWorkflowStub(
|
||||
UserMigrationWorkflowInterface::class,
|
||||
$importJob->workflow_id
|
||||
);
|
||||
|
||||
$status = $stub->getProgress();
|
||||
|
||||
// Query handler returns cached internal state — cross-check
|
||||
// actual execution status when the query says non-terminal
|
||||
if (!in_array($status['status'], ['completed', 'cancelled', 'failed'])) {
|
||||
$resolved = $this->resolveWorkflowStatus($importJob->workflow_id);
|
||||
if ($resolved !== null) {
|
||||
$status['status'] = $resolved;
|
||||
}
|
||||
}
|
||||
|
||||
$importJob->update([
|
||||
'status' => $status['status'],
|
||||
'processed_records' => $status['processedUsers'] ?? 0,
|
||||
'failed_records' => $status['failedUsers'] ?? 0,
|
||||
]);
|
||||
|
||||
return response()->json($status);
|
||||
} catch (\Throwable $e) {
|
||||
$resolvedStatus = $this->resolveWorkflowStatus($importJob->workflow_id);
|
||||
if ($resolvedStatus !== null) {
|
||||
$importJob->update(['status' => $resolvedStatus]);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'status' => $resolvedStatus ?? $importJob->status,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// --- External API Sync ---
|
||||
|
||||
public function startApiSync(Request $request): JsonResponse
|
||||
{
|
||||
$apiEndpoint = $request->input('api_endpoint', 'https://api.example.com/products');
|
||||
$simulationConfig = $request->input('simulation', []);
|
||||
|
||||
$workflowId = 'api-sync-' . uniqid();
|
||||
|
||||
$stub = $this->workflowClient->newWorkflowStub(
|
||||
ExternalApiSyncWorkflowInterface::class,
|
||||
WorkflowOptions::new()
|
||||
->withWorkflowId($workflowId)
|
||||
->withTaskQueue(config('temporal.task_queue'))
|
||||
);
|
||||
|
||||
$run = $this->workflowClient->start($stub, $apiEndpoint, $simulationConfig);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'API sync started',
|
||||
'workflow_id' => $workflowId,
|
||||
'run_id' => $run->getExecution()->getRunID(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function apiSyncStatus(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$stub = $this->workflowClient->newRunningWorkflowStub(
|
||||
ExternalApiSyncWorkflowInterface::class,
|
||||
$id
|
||||
);
|
||||
|
||||
return response()->json($stub->getProgress());
|
||||
} catch (\Throwable $e) {
|
||||
return response()->json(['error' => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
public function pauseApiSync(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$stub = $this->workflowClient->newRunningWorkflowStub(
|
||||
ExternalApiSyncWorkflowInterface::class,
|
||||
$id
|
||||
);
|
||||
$stub->pause();
|
||||
return response()->json(['message' => 'API sync paused']);
|
||||
} catch (\Throwable $e) {
|
||||
return response()->json(['error' => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
public function resumeApiSync(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$stub = $this->workflowClient->newRunningWorkflowStub(
|
||||
ExternalApiSyncWorkflowInterface::class,
|
||||
$id
|
||||
);
|
||||
$stub->resume();
|
||||
return response()->json(['message' => 'API sync resumed']);
|
||||
} catch (\Throwable $e) {
|
||||
return response()->json(['error' => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Webhook Delivery ---
|
||||
|
||||
public function deliverWebhooks(Request $request): JsonResponse
|
||||
{
|
||||
$payload = $request->input('payload', ['event' => 'order.created', 'data' => ['order_id' => 1]]);
|
||||
$endpoints = $request->input('endpoints', [
|
||||
'https://api.example.com/webhook',
|
||||
'https://hooks.partner.io/events',
|
||||
'https://notify.service.dev/incoming',
|
||||
'https://webhook.site/test-endpoint',
|
||||
]);
|
||||
$simulationConfig = $request->input('simulation', []);
|
||||
|
||||
$workflowId = 'webhook-delivery-' . uniqid();
|
||||
|
||||
$stub = $this->workflowClient->newWorkflowStub(
|
||||
WebhookDeliveryWorkflowInterface::class,
|
||||
WorkflowOptions::new()
|
||||
->withWorkflowId($workflowId)
|
||||
->withTaskQueue(config('temporal.task_queue'))
|
||||
);
|
||||
|
||||
$run = $this->workflowClient->start($stub, $payload, $endpoints, $simulationConfig);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Webhook delivery started',
|
||||
'workflow_id' => $workflowId,
|
||||
'run_id' => $run->getExecution()->getRunID(),
|
||||
'endpoints_count' => count($endpoints),
|
||||
]);
|
||||
}
|
||||
|
||||
public function webhookStatus(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$stub = $this->workflowClient->newRunningWorkflowStub(
|
||||
WebhookDeliveryWorkflowInterface::class,
|
||||
$id
|
||||
);
|
||||
|
||||
return response()->json($stub->getDeliveryStatus());
|
||||
} catch (\Throwable $e) {
|
||||
return response()->json(['error' => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Data Enrichment ---
|
||||
|
||||
public function startEnrichment(Request $request): JsonResponse
|
||||
{
|
||||
$recordIds = $request->input('record_ids', []);
|
||||
$simulationConfig = $request->input('simulation', []);
|
||||
|
||||
// Default to first 5 orders if no IDs provided
|
||||
if (empty($recordIds)) {
|
||||
$recordIds = Order::orderBy('id')->take(5)->pluck('id')->toArray();
|
||||
}
|
||||
|
||||
$workflowId = 'data-enrichment-' . uniqid();
|
||||
|
||||
$stub = $this->workflowClient->newWorkflowStub(
|
||||
DataEnrichmentWorkflowInterface::class,
|
||||
WorkflowOptions::new()
|
||||
->withWorkflowId($workflowId)
|
||||
->withTaskQueue(config('temporal.task_queue'))
|
||||
);
|
||||
|
||||
$run = $this->workflowClient->start($stub, $recordIds, $simulationConfig);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Data enrichment started',
|
||||
'workflow_id' => $workflowId,
|
||||
'run_id' => $run->getExecution()->getRunID(),
|
||||
'record_count' => count($recordIds),
|
||||
]);
|
||||
}
|
||||
|
||||
public function enrichmentStatus(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$stub = $this->workflowClient->newRunningWorkflowStub(
|
||||
DataEnrichmentWorkflowInterface::class,
|
||||
$id
|
||||
);
|
||||
|
||||
return response()->json($stub->getProgress());
|
||||
} catch (\Throwable $e) {
|
||||
return response()->json(['error' => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Eloquent Query Pipeline ---
|
||||
|
||||
public function startEloquentQuery(Request $request): JsonResponse
|
||||
{
|
||||
$simulationConfig = $request->input('simulation', []);
|
||||
$workflowId = 'eloquent-query-' . uniqid();
|
||||
|
||||
$stub = $this->workflowClient->newWorkflowStub(
|
||||
EloquentQueryWorkflowInterface::class,
|
||||
WorkflowOptions::new()
|
||||
->withWorkflowId($workflowId)
|
||||
->withTaskQueue(config('temporal.task_queue'))
|
||||
);
|
||||
|
||||
$run = $this->workflowClient->start($stub, $simulationConfig);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Eloquent query pipeline started',
|
||||
'workflow_id' => $workflowId,
|
||||
'run_id' => $run->getExecution()->getRunID(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function eloquentQueryStatus(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$stub = $this->workflowClient->newRunningWorkflowStub(
|
||||
EloquentQueryWorkflowInterface::class,
|
||||
$id
|
||||
);
|
||||
|
||||
return response()->json($stub->getProgress());
|
||||
} catch (\Throwable $e) {
|
||||
return response()->json(['error' => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
// --- System Health Monitor ---
|
||||
|
||||
public function startSystemMonitor(Request $request): JsonResponse
|
||||
{
|
||||
$intervalSeconds = $request->input('interval_seconds', 60);
|
||||
$maxIterations = $request->input('max_iterations', 30);
|
||||
$simulationConfig = $request->input('simulation', []);
|
||||
|
||||
$workflowId = 'system-monitor-' . uniqid();
|
||||
|
||||
$stub = $this->workflowClient->newWorkflowStub(
|
||||
SystemMonitorWorkflowInterface::class,
|
||||
WorkflowOptions::new()
|
||||
->withWorkflowId($workflowId)
|
||||
->withTaskQueue(config('temporal.task_queue'))
|
||||
);
|
||||
|
||||
$run = $this->workflowClient->start($stub, (int) $intervalSeconds, (int) $maxIterations, [], $simulationConfig);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'System monitor started',
|
||||
'workflow_id' => $workflowId,
|
||||
'run_id' => $run->getExecution()->getRunID(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function systemMonitorStatus(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$stub = $this->workflowClient->newRunningWorkflowStub(
|
||||
SystemMonitorWorkflowInterface::class,
|
||||
$id
|
||||
);
|
||||
|
||||
return response()->json($stub->getStatus());
|
||||
} catch (\Throwable $e) {
|
||||
return response()->json(['error' => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
public function stopSystemMonitor(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$stub = $this->workflowClient->newRunningWorkflowStub(
|
||||
SystemMonitorWorkflowInterface::class,
|
||||
$id
|
||||
);
|
||||
|
||||
$stub->stop();
|
||||
|
||||
return response()->json(['message' => 'Stop signal sent']);
|
||||
} catch (\Throwable $e) {
|
||||
return response()->json(['error' => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user