diff --git a/.noindex b/.noindex new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/.noindex @@ -0,0 +1 @@ + diff --git a/src/Commands/Command.php b/src/Commands/Command.php index 8c038f9..abb7c7f 100644 --- a/src/Commands/Command.php +++ b/src/Commands/Command.php @@ -56,7 +56,7 @@ public function setContext(): void $context = app(Context::class); $meta = new Metadata(); $meta->task_id = $this->taskID; - $meta->identifier = $this->signature; + $meta->identifier = explode(" ", $this->signature)[0]; $context->set(Metadata::class, $meta); } } diff --git a/src/Libraries/Client.php b/src/Libraries/Client.php index a92e676..cc733ac 100644 --- a/src/Libraries/Client.php +++ b/src/Libraries/Client.php @@ -77,7 +77,7 @@ public function __construct(array $config = []) * @var Metadata $meta */ $meta = $context->get(Metadata::class); - if (!is_null($meta)) { + if ($meta !== null) { if (isset($meta->user_agent) && $meta->user_agent !== null) { $this->requestHeaders['User-Agent'] = $meta->user_agent; } @@ -101,6 +101,8 @@ public function __construct(array $config = []) } if (isset($meta->req_id) && $meta->req_id !== null) { $this->requestHeaders['X-Request-ID'] = $meta->req_id; + } elseif (isset($meta->task_id) && $meta->task_id !== null) { + $this->requestHeaders['X-Request-ID'] = $meta->task_id; } if (isset($meta->req_user) && $meta->req_user !== null) { $this->requestHeaders['X-Request-User'] = $meta->req_user; @@ -141,6 +143,33 @@ public function __construct(array $config = []) if (isset($meta->req_role) && $meta->req_role !== null) { $this->requestHeaders['X-Request-Role'] = $meta->req_role; } + if (isset($meta->req_uker_supervised) && $meta->req_uker_supervised !== null) { + $this->requestHeaders['X-Request-Uker-Supervised'] = json_encode($meta->req_uker_supervised); + } + if (isset($meta->req_stell) && $meta->req_stell !== null) { + $this->requestHeaders['X-Request-Kode-Org-Jabatan'] = $meta->req_stell; + } + if (isset($meta->req_stell_tx) && $meta->req_stell_tx !== null) { + $this->requestHeaders['X-Request-Nama-Org-Jabatan'] = $meta->req_stell_tx; + } + if (isset($meta->req_kostl) && $meta->req_kostl !== null) { + $this->requestHeaders['X-Request-Kode-Cost-Center'] = $meta->req_kostl; + } + if (isset($meta->req_kostl_tx) && $meta->req_kostl_tx !== null) { + $this->requestHeaders['X-Request-Nama-Cost-Center'] = $meta->req_kostl_tx; + } + if (isset($meta->req_orgeh) && $meta->req_orgeh !== null) { + $this->requestHeaders['X-Request-Kode-Org-Unit'] = $meta->req_orgeh; + } + if (isset($meta->req_orgeh_tx) && $meta->req_orgeh_tx !== null) { + $this->requestHeaders['X-Request-Nama-Org-Unit'] = $meta->req_orgeh_tx; + } + if (isset($meta->req_level_uker) && $meta->req_level_uker !== null) { + $this->requestHeaders['X-Request-Level-Uker'] = $meta->req_level_uker; + } + if (isset($meta->req_uid) && $meta->req_uid !== null) { + $this->requestHeaders['X-Request-Uid-Las'] = $meta->req_uid; + } } } diff --git a/src/Libraries/ClientExternal.php b/src/Libraries/ClientExternal.php index ec0bb93..16c34fa 100644 --- a/src/Libraries/ClientExternal.php +++ b/src/Libraries/ClientExternal.php @@ -20,7 +20,6 @@ use Illuminate\Support\Facades\Redis; use Psr\Http\Message\ResponseInterface; use Spotlibs\PhpLib\Exceptions\InvalidRuleException; -use Spotlibs\PhpLib\Libraries\MapRoute; use Spotlibs\PhpLib\Logs\Log; use Spotlibs\PhpLib\Services\Context; use Spotlibs\PhpLib\Services\Metadata; @@ -160,15 +159,26 @@ public function call(Request $request, array $options = []): ResponseInterface } $elapsed = microtime(true) - $startime; + // Parse request body $request->getBody()->rewind(); - $reqbody = $request->getBody()->getContents(); - $respbody = $response->getBody()->getContents(); - if (strlen($reqbody) > 5000) { - $reqbody = "more than 5000 characters"; + $reqBody = $request->getBody()->getContents(); + $detectedType = $this->detectContentType($reqBody); + if ($detectedType === 'multipart/form-data') { + $parsedReqBody = $this->parseBody($reqBody); + } elseif (strlen($reqBody) > 5000) { + $parsedReqBody = "more than 5000 characters"; + } else { + $parsedReqBody = $this->parseBody($reqBody); } - if (strlen($respbody) > 5000) { - $respbody = "more than 5000 characters"; + + // Parse response body + $respBody = $response->getBody()->getContents(); + if (strlen($respBody) > 5000) { + $parsedRespBody = "more than 5000 characters"; + } else { + $parsedRespBody = $this->parseBody($respBody); } + $logData = [ 'app_name' => env('APP_NAME'), 'path' => is_null($metadata) ? null : $metadata->identifier, @@ -177,29 +187,140 @@ public function call(Request $request, array $options = []): ResponseInterface 'request' => [ 'method' => $request->getMethod(), 'headers' => $request->getHeaders(), + 'body' => $parsedReqBody ], 'response' => [ 'httpCode' => $response->getStatusCode(), 'headers' => $response->getHeaders(), + 'body' => $parsedRespBody ], 'responseTime' => round($elapsed * 1000), 'memoryUsage' => memory_get_usage() ]; - if ($request->getHeader('Content-Type') == ['application/json']) { - $logData['request']['body'] = json_decode($reqbody, true); - } else { - $logData['request']['body'] = $reqbody; - } - if ($response->getHeader('Content-Type') == ['application/json']) { - $logData['response']['body'] = json_decode($respbody, true); - } else { - $logData['response']['body'] = $respbody; - } + $response->getBody()->rewind(); Log::activity()->info($logData); return $response; } + /** + * Detect Content Type + * + * @param string $body Raw body content + * + * @return string + */ + private function detectContentType(string $body): string + { + if (empty($body)) { + return 'empty'; + } + + // Check for multipart - look for Content-Disposition + if (preg_match('/Content-Disposition:\s*form-data/i', $body)) { + return 'multipart/form-data'; + } + + // Check for URL-encoded - look for key=value pattern + if (preg_match('/^[^=]+=/', $body) && !str_contains($body, "\n")) { + return 'application/x-www-form-urlencoded'; + } + + // Check for JSON - try to decode + if ($body[0] === '{' || $body[0] === '[') { + json_decode($body); + if (json_last_error() === JSON_ERROR_NONE) { + return 'application/json'; + } + } + + return 'text/plain'; + } + + /** + * Parse body content based on content type + * + * @param string $body Raw body content + * + * @return mixed + */ + private function parseBody(string $body): mixed + { + if (empty($body)) { + return null; + } + + $detectedType = $this->detectContentType($body); + + if ($detectedType === 'application/json') { + return json_decode($body, true) ?? $body; + } + + if ($detectedType === 'multipart/form-data') { + return $this->parseMultipartFormDataFromBody($body); + } + + if ($detectedType === 'application/x-www-form-urlencoded') { + parse_str($body, $parsed); + return $parsed; + } + + return $body; + } + + /** + * Parse multipart form data into readable array + * + * @param string $body Raw body content + * + * @return array + */ + private function parseMultipartFormDataFromBody(string $body): array + { + $parsed = []; + + // Extract boundary from first line + preg_match('/^--([^\r\n]+)/', $body, $boundaryMatch); + if (empty($boundaryMatch[1])) { + return ['raw' => 'Unable to parse multipart']; + } + + $boundary = $boundaryMatch[1]; + $parts = explode("--$boundary", $body); + + foreach ($parts as $part) { + if (trim($part) === '' || trim($part) === '--') { + continue; + } + + // Split headers and content + $sections = preg_split('/\r?\n\r?\n/', $part, 2); + if (count($sections) < 2) { + continue; + } + + $headers = $sections[0]; + $content = trim($sections[1]); + + // Extract field name + if (preg_match('/name="([^"]+)"/', $headers, $nameMatch)) { + $name = $nameMatch[1]; + + // Check if it's a file + if (preg_match('/filename="([^"]+)"/', $headers, $fileMatch)) { + $parsed[$name] = [ + 'filename' => $fileMatch[1], + 'contents' => "[BINARY FILE DATA - " . strlen($content) . " bytes]" + ]; + } else { + $parsed[$name] = $content; + } + } + } + + return $parsed; + } + /** * Check if url shall mock * diff --git a/src/Middlewares/ActivityMonitor.php b/src/Middlewares/ActivityMonitor.php index dd56bc8..3f338a8 100644 --- a/src/Middlewares/ActivityMonitor.php +++ b/src/Middlewares/ActivityMonitor.php @@ -85,6 +85,15 @@ public function handle($request, Closure $next) $meta->version_app = $request->header('X-Version-App'); $meta->identifier = $request->getPathInfo(); $meta->req_role = $request->header('X-Request-Role'); + $meta->req_uker_supervised = json_decode($request->header('X-Request-Uker-Supervised')); + $meta->req_stell = $request->header('X-Request-Kode-Org-Jabatan'); + $meta->req_stell_tx = $request->header('X-Request-Nama-Org-Jabatan'); + $meta->req_kostl = $request->header('X-Request-Kode-Org-Cost-Center'); + $meta->req_kostl_tx = $request->header('X-Request-Nama-Org-Cost-Center'); + $meta->req_orgeh = $request->header('X-Request-Kode-Org-Unit'); + $meta->req_orgeh_tx = $request->header('X-Request-Nama-Org-Unit'); + $meta->req_level_uker = $request->header('X-Request-Level-Uker'); + $meta->req_uid = $request->header('X-Request-Uid-Las'); $this->contextService->set(Metadata::class, $meta); $this->contextService->set('method', $request->method()); diff --git a/src/Services/Metadata.php b/src/Services/Metadata.php index 7bfcde7..981ac61 100644 --- a/src/Services/Metadata.php +++ b/src/Services/Metadata.php @@ -52,4 +52,13 @@ class Metadata public ?string $path_gateway; public ?string $identifier; public ?string $req_role; + public ?array $req_uker_supervised; + public ?string $req_stell; + public ?string $req_stell_tx; + public ?string $req_kostl; + public ?string $req_kostl_tx; + public ?string $req_orgeh; + public ?string $req_orgeh_tx; + public ?string $req_level_uker; + public ?string $req_uid; } diff --git a/tests/Dtos/DtosTest.php b/tests/Dtos/DtosTest.php index 4339319..cd15c5f 100644 --- a/tests/Dtos/DtosTest.php +++ b/tests/Dtos/DtosTest.php @@ -270,6 +270,8 @@ public function testDtoWithAliases(): void $this->assertIsArray($y['partner']['dog']); $this->assertEquals('Joshua', $y['partner']['dog']['name']); $this->assertEquals('Jacob', $x->siblings[0]->name); + $this->assertArrayNotHasKey('arrayOfObjectMap', $y); + $this->assertArrayNotHasKey('aliases', $y); } /** @test */ /** @runInSeparateProcess */ diff --git a/tests/Libraries/ClientTest.php b/tests/Libraries/ClientTest.php index ae1f6d2..eb66f24 100644 --- a/tests/Libraries/ClientTest.php +++ b/tests/Libraries/ClientTest.php @@ -52,6 +52,16 @@ public function testCallY(): void $meta->req_nama_uker = 'test_name'; $meta->path_gateway = 'test_path'; $meta->identifier = 'test_identifier'; + $meta->req_uker_supervised = ['abc', 'def']; + $meta->req_stell = '123'; + $meta->req_stell_tx = 'abc'; + $meta->req_kostl = '123'; + $meta->req_kostl_tx = 'abc'; + $meta->req_orgeh = '123'; + $meta->req_orgeh_tx = 'abc'; + $meta->req_level_uker = 'X'; + $meta->req_uid = 'abc123'; + $meta->req_role = 'abc'; /** * @var \Mockery\MockInterface $context */ @@ -80,6 +90,14 @@ public function testCallX(): void 'GET', 'https://dummyjson.com/test', ); + $meta = new Metadata(); + $meta->task_id = 'abcd'; + /** + * @var \Mockery\MockInterface $context + */ + $context = Mockery::mock(Context::class); + $context->shouldReceive('get')->with(Metadata::class)->andReturn($meta); + $this->app->instance(Context::class, $context); $client = new Client(['handler' => $handlerStack]); $response = $client->call($request); $contents = $response->getBody()->getContents(); @@ -101,7 +119,11 @@ public function testCallZ(): void "message" => "welcome" ]) ); - $client = new Client(); + $mock = new MockHandler([ + new Response(200, ['Content-Type' => 'application/json'], json_encode(['status' => 'ok', 'message' => 'well done'])), + ]); + $handlerStack = new HandlerStack($mock); + $client = new Client(['handler' => $handlerStack]); $response = $client ->injectRequestHeader(['X-Powered-By' => ['Money']]) ->injectResponseHeader(['X-Server' => ['tinyurl'], 'X-Overhead' => ['true', 'allowed']])