<?php
namespace ApplicationBundle\Modules\Api\Controller;
use ApplicationBundle\Modules\Authentication\Constants\UserConstants;
use ApplicationBundle\Controller\GenericController;
use ApplicationBundle\Modules\Api\Service\ActivityLogService;
use InvalidArgumentException;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
class ActivityLogController extends GenericController
{
public function logAction(Request $request): JsonResponse
{
$payload = $this->extractPayload($request);
// Route the write to the LOGGED-IN TENANT's DB. SessionListener switches the
// default connection with reset=false, which only rewrites the params and does
// NOT move an already-open connection — so an /api write can land in the box
// DEFAULT database (every tenant's activity funneled there, its own Activity
// Dashboard read empty). Force a reconnect (reset=true) to the session tenant.
$this->switchToSessionTenantDb($request);
$service = new ActivityLogService($this->getDoctrine()->getManager());
try {
$activityLog = $service->log($payload);
} catch (InvalidArgumentException $exception) {
return new JsonResponse([
'success' => false,
'code' => 'HB_VALIDATION_ERROR',
'message' => 'Validation failed',
'errors' => [$exception->getMessage()],
], 400);
} catch (\Throwable $exception) {
return new JsonResponse([
'success' => false,
'code' => 'HB_INTERNAL_ERROR',
'message' => 'Activity log could not be saved',
], 500);
}
return new JsonResponse([
'success' => true,
'code' => 'HB_OK',
'message' => 'Activity logged successfully',
'data' => [
'id' => $activityLog->getId(),
'user_id' => $activityLog->getUserId(),
'session_id' => $activityLog->getSessionId(),
'route' => $activityLog->getRoute(),
'action_type' => $activityLog->getActionType(),
'duration_seconds' => $activityLog->getDurationSeconds(),
'created_at' => $activityLog->getCreatedAt() ? $activityLog->getCreatedAt()->format('Y-m-d H:i:s') : null,
],
], 201);
}
/**
* Force-reconnect the default EM to the session tenant's DB (reset=true) so the
* activity row is written to the right tenant, not the box default. Best-effort:
* any failure leaves the existing connection and never breaks logging.
*/
private function switchToSessionTenantDb(Request $request): void
{
try {
$appId = (int) $request->getSession()->get(UserConstants::USER_APP_ID);
if ($appId <= 0) {
return;
}
$emGoc = $this->getDoctrine()->getManager('company_group');
$goc = $emGoc->getRepository('CompanyGroupBundle\\Entity\\CompanyGroup')
->findOneBy(['appId' => $appId]);
if (!$goc || !$goc->getDbName()) {
return;
}
$this->container->get('application_connector')->resetConnection(
'default',
$goc->getDbName(),
$goc->getDbUser(),
$goc->getDbPass(),
$goc->getDbHost(),
true
);
} catch (\Throwable $e) {
// never break activity logging on the tenant-switch path
}
}
private function extractPayload(Request $request): array
{
$contentType = strtolower((string) $request->headers->get('Content-Type', ''));
$rawContent = trim((string) $request->getContent());
if ($rawContent !== '' && strpos($contentType, 'application/json') !== false) {
$decoded = json_decode($rawContent, true);
return is_array($decoded) ? $decoded : [];
}
return $request->request->all();
}
}