src/ApplicationBundle/Controller/Api/InternalNotificationApiController.php line 148

Open in your IDE?
  1. <?php
  2. namespace ApplicationBundle\Controller\Api;
  3. use ApplicationBundle\Modules\Authentication\Constants\UserConstants;
  4. use CompanyGroupBundle\Entity\NotificationLog;
  5. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  6. use Symfony\Component\HttpFoundation\JsonResponse;
  7. use Symfony\Component\HttpFoundation\Request;
  8. /**
  9.  * InternalNotificationApiController
  10.  *
  11.  * Internal API endpoints called by the Node socket server.
  12.  * All routes are protected by X-Api-Key header checked against
  13.  * the socket_shared_secret parameter.
  14.  */
  15. class InternalNotificationApiController extends AbstractController
  16. {
  17.     private function checkApiKey(Request $request): bool
  18.     {
  19.         $secret $this->getParameter('socket_shared_secret');
  20.         if (!$secret) return true// not configured yet — allow during migration
  21.         $key $request->headers->get('X-Api-Key''');
  22.         return hash_equals($secret$key);
  23.     }
  24.     /**
  25.      * GET /api/internal/tokens?userIds=1,2,3
  26.      *
  27.      * Returns FCM device tokens for the requested user IDs,
  28.      * fetched from entity_token_storage.fire_base_token.
  29.      */
  30.     public function getTokensAction(Request $request): JsonResponse
  31.     {
  32.         if (!$this->checkApiKey($request)) {
  33.             return new JsonResponse(['error' => 'Unauthorized'], 401);
  34.         }
  35.         $rawIds $request->query->get('userIds''');
  36.         if (!$rawIds) {
  37.             return new JsonResponse(['tokens' => []]);
  38.         }
  39.         $userIds array_filter(
  40.             array_map('intval'explode(','$rawIds)),
  41.             fn($id) => $id 0
  42.         );
  43.         if (empty($userIds)) {
  44.             return new JsonResponse(['tokens' => []]);
  45.         }
  46.         $cgEm $this->getDoctrine()->getManager('company_group');
  47.         try {
  48.             $rows $cgEm->getConnection()->fetchAllAssociative(
  49.                 'SELECT user_id, fire_base_token
  50.                  FROM entity_token_storage
  51.                  WHERE user_id IN (' implode(','$userIds) . ')
  52.                    AND fire_base_token IS NOT NULL
  53.                    AND fire_base_token != \'\'',
  54.                 []
  55.             );
  56.         } catch (\Exception $e) {
  57.             return new JsonResponse(['tokens' => [], 'error' => $e->getMessage()]);
  58.         }
  59.         // Group by userId: userId => [token1, token2, ...]
  60.         $result = [];
  61.         foreach ($rows as $row) {
  62.             $uid = (int) $row['user_id'];
  63.             if (!isset($result[$uid])) $result[$uid] = [];
  64.             $result[$uid][] = $row['fire_base_token'];
  65.         }
  66.         return new JsonResponse(['tokens' => $result]);
  67.     }
  68.     /**
  69.      * POST /api/internal/fcm_log
  70.      *
  71.      * Accepts FCM delivery results from Node and persists them to notification_log.
  72.      * Also purges invalidated tokens from entity_token_storage.
  73.      */
  74.     public function fcmLogAction(Request $request): JsonResponse
  75.     {
  76.         if (!$this->checkApiKey($request)) {
  77.             return new JsonResponse(['error' => 'Unauthorized'], 401);
  78.         }
  79.         $data      json_decode($request->getContent(), true) ?: [];
  80.         $eventCode $data['eventCode'] ?? '';
  81.         $userIds   $data['userIds'] ?? [];
  82.         $responses $data['responses'] ?? [];
  83.         $cgEm $this->getDoctrine()->getManager('company_group');
  84.         // Write per-user log entries
  85.         foreach ($userIds as $i => $uid) {
  86.             $response $responses[$i] ?? null;
  87.             $success  $response && $response['success'];
  88.             $errCode  $response['errorCode'] ?? null;
  89.             $log = new NotificationLog();
  90.             $log->setEventCode($eventCode);
  91.             $log->setChannel('fcm');
  92.             $log->setRecipientUserId((int) $uid);
  93.             $log->setStatus($success NotificationLog::STATUS_SENT NotificationLog::STATUS_FAILED);
  94.             $log->setError($errCode);
  95.             $cgEm->persist($log);
  96.             // Remove stale token from DB if FCM said it's unregistered
  97.             if (
  98.                 !$success &&
  99.                 in_array($errCode, [
  100.                     'messaging/registration-token-not-registered',
  101.                     'messaging/invalid-registration-token',
  102.                 ])
  103.             ) {
  104.                 try {
  105.                     $cgEm->getConnection()->executeStatement(
  106.                         'UPDATE entity_token_storage SET fire_base_token = NULL WHERE user_id = :uid',
  107.                         ['uid' => (int) $uid]
  108.                     );
  109.                 } catch (\Exception $e) {}
  110.             }
  111.         }
  112.         try {
  113.             $cgEm->flush();
  114.         } catch (\Exception $e) {}
  115.         return new JsonResponse(['ok' => true]);
  116.     }
  117.     /**
  118.      * GET /api/notifications/unread_count
  119.      *
  120.      * Returns unread notification count for the currently logged-in user.
  121.      * Called by frontend JS after a socket push to refresh the badge.
  122.      */
  123.     public function unreadCountAction(Request $request): JsonResponse
  124.     {
  125.         $session $request->getSession();
  126.         $userId  = (int) $session->get(UserConstants::USER_ID0);
  127.         $companyId = (int) $session->get(UserConstants::USER_COMPANY_ID0);
  128.         if ($userId <= 0) {
  129.             return new JsonResponse(['count' => 0]);
  130.         }
  131.         $cgEm $this->getDoctrine()->getManager('company_group');
  132.         try {
  133.             $count = (int) $cgEm->getConnection()->fetchOne(
  134.                 'SELECT COUNT(*) FROM entity_notification
  135.                  WHERE user_id = :uid AND company_id = :cid AND seen_flag = 0
  136.                    AND (expire_ts = 0 OR expire_ts > :now)',
  137.                 ['uid' => $userId'cid' => $companyId'now' => time()]
  138.             );
  139.         } catch (\Exception $e) {
  140.             $count 0;
  141.         }
  142.         return new JsonResponse(['count' => $count]);
  143.     }
  144.     /**
  145.      * GET /api/notifications/recent?limit=15
  146.      *
  147.      * Returns the most recent notifications for the logged-in user as JSON.
  148.      * Used to pre-populate the header dropdown on every page load.
  149.      */
  150.     public function recentListAction(Request $request): JsonResponse
  151.     {
  152.         $session $request->getSession();
  153.         $userId  = (int) $session->get(UserConstants::USER_ID0);
  154.         if ($userId <= 0) {
  155.             return new JsonResponse(['items' => [], 'unreadCount' => 0]);
  156.         }
  157.         $limit min(20max(1, (int) $request->query->get('limit'15)));
  158.         $cgEm  $this->getDoctrine()->getManager('company_group');
  159.         try {
  160.             $rows $cgEm->getConnection()->fetchAllAssociative(
  161.                 'SELECT id, title, body, target_route, target_url, notification_ts, seen_flag
  162.                  FROM entity_notification
  163.                  WHERE user_id = :uid
  164.                  ORDER BY notification_ts DESC, id DESC
  165.                  LIMIT ' $limit,
  166.                 ['uid' => $userId]
  167.             );
  168.             $unreadCount = (int) $cgEm->getConnection()->fetchOne(
  169.                 'SELECT COUNT(*) FROM entity_notification WHERE user_id = :uid AND seen_flag = 0',
  170.                 ['uid' => $userId]
  171.             );
  172.         } catch (\Exception $e) {
  173.             return new JsonResponse(['items' => [], 'unreadCount' => 0]);
  174.         }
  175.         $items = [];
  176.         foreach ($rows as $row) {
  177.             $items[] = [
  178.                 'id'             => (int) $row['id'],
  179.                 'title'          => $row['title'] ?: 'Notification',
  180.                 'body'           => $row['body'] ?: '',
  181.                 'targetRoute'    => $row['target_route'] ?: '',
  182.                 'targetUrl'      => $row['target_url'] ?: '',
  183.                 'notificationTs' => (int) $row['notification_ts'],
  184.                 'seenFlag'       => (int) $row['seen_flag'],
  185.             ];
  186.         }
  187.         return new JsonResponse(['items' => $items'unreadCount' => $unreadCount]);
  188.     }
  189.     /**
  190.      * POST /api/notifications/mark_read
  191.      *
  192.      * Marks one or all notifications as read for the current user.
  193.      */
  194.     public function markReadAction(Request $request): JsonResponse
  195.     {
  196.         $session $request->getSession();
  197.         $userId  = (int) $session->get(UserConstants::USER_ID0);
  198.         if ($userId <= 0) {
  199.             return new JsonResponse(['ok' => false'error' => 'Not logged in']);
  200.         }
  201.         $id  = (int) ($request->request->get('id') ?: $request->query->get('id'0));
  202.         $all = (bool) ($request->request->get('all') ?: $request->query->get('all'0));
  203.         $cgEm $this->getDoctrine()->getManager('company_group');
  204.         try {
  205.             if ($all) {
  206.                 $cgEm->getConnection()->executeStatement(
  207.                     'UPDATE entity_notification SET seen_flag = 1, read_flag = 1
  208.                      WHERE user_id = :uid',
  209.                     ['uid' => $userId]
  210.                 );
  211.             } elseif ($id 0) {
  212.                 $cgEm->getConnection()->executeStatement(
  213.                     'UPDATE entity_notification SET seen_flag = 1, read_flag = 1
  214.                      WHERE id = :id AND user_id = :uid',
  215.                     ['id' => $id'uid' => $userId]
  216.                 );
  217.             }
  218.         } catch (\Exception $e) {
  219.             return new JsonResponse(['ok' => false'error' => $e->getMessage()]);
  220.         }
  221.         return new JsonResponse(['ok' => true]);
  222.     }
  223. }