<commit_before><?php

namespace App\EventListener;
use App\Event\NotificationEvent;
use JMS\DiExtraBundle\Annotation as DI;
use Kreait\Firebase;
use Kreait\Firebase\Database;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * @DI\Service()
 *
 * Firebase data structure
 *
 * "doc": {
 *     doc_id: {
 *         "notification": [
 *             KEY: {
 *                 "message": "message here",
 *                 "username": "username here",
 *                 "itemIds": [],
 *                 "associationIds": [],
 *             }
 *         ]
 *     }
 * }
 */
class NotificationToFirebaseListener
{
    /**
     * @var LoggerInterface
     */
    private $logger;

    /**
     * @var Firebase
     */
    private $firebase;

    /**
     * @DI\InjectParams({
     *     "firebase" = @DI\Inject(Firebase::class),
     *     "logger" = @DI\Inject("logger")
     * })
     */
    public function __construct(?Firebase $firebase, LoggerInterface $logger)
    {
        $this->firebase = $firebase;
        $this->logger = $logger;
    }

    /**
     * @DI\Observe(App\Event\NotificationEvent::class)
     */
    public function handleNotification(NotificationEvent $event, string $eventName, EventDispatcherInterface $dispatcher): void
    {
        if (null === $this->firebase) {
            $this->logger->info('Firebase not enabled');
            return;
        }

        $doc = $event->getDoc();
        if (null === $doc) {
            // Only add doc change records to firebase
            return;
        }
        $docId = $doc->getId();

        $notification = [
            'msgId' => $event->getMessageId(),
            'msg' => $event->getMessage(),
            'by' => $event->getUsername(),
            'changes' => $event->getChanged(),
            'at' => Database::SERVER_TIMESTAMP,
        ];

        $this->addDocChangeToFirebase($notification, $docId);
    }

    protected function addDocChangeToFirebase(array $notification, int $docId): void
    {
        $this->logger->info('Adding to firebase', [
            'msg' => $notification['msg'],
            'user' => $notification['by'],
            'changes' => $notification['changes'],
        ]);

        $path = "/doc/{$docId}/notification";
        $db = $this->firebase->getDatabase();
        $db->getReference($path)->push($notification);
    }
}<commit_msg>feat: send firebase notifications after response<commit_after><?php

namespace App\EventListener;
use App\Event\NotificationEvent;
use JMS\DiExtraBundle\Annotation as DI;
use Kreait\Firebase;
use Kreait\Firebase\Database;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * @DI\Service()
 *
 * Firebase data structure
 *
 * "doc": {
 *     doc_id: {
 *         "notification": [
 *             KEY: {
 *                 "message": "message here",
 *                 "username": "username here",
 *                 "itemIds": [],
 *                 "associationIds": [],
 *             }
 *         ]
 *     }
 * }
 */
class NotificationToFirebaseListener
{
    /**
     * @var LoggerInterface
     */
    private $logger;

    /**
     * @var Firebase
     */
    private $firebase;

    /**
     * @DI\InjectParams({
     *     "firebase" = @DI\Inject(Firebase::class),
     *     "logger" = @DI\Inject("logger")
     * })
     */
    public function __construct(?Firebase $firebase, LoggerInterface $logger)
    {
        $this->firebase = $firebase;
        $this->logger = $logger;
    }

    /**
     * @DI\Observe(App\Event\NotificationEvent::class)
     */
    public function handleNotification(NotificationEvent $event, string $eventName, EventDispatcherInterface $dispatcher): void
    {
        if (null === $this->firebase) {
            $this->logger->info('Firebase not enabled');
            return;
        }

        $doc = $event->getDoc();
        if (null === $doc) {
            // Only add doc change records to firebase
            return;
        }
        $docId = $doc->getId();

        $notification = [
            'msgId' => $event->getMessageId(),
            'msg' => $event->getMessage(),
            'by' => $event->getUsername(),
            'changes' => $event->getChanged(),
            'at' => Database::SERVER_TIMESTAMP,
        ];

        // Send the notification after the response is sent
        $dispatcher->addListener(
            'kernel.terminate',
            function (Event $event) use ($notification, $docId) {
                $this->addDocChangeToFirebase($notification, $docId);
            },
            -10
        );

        try {
            // Clean up old notifications around 1% of the time
            if (1 === random_int(1, 100)) {
                $dispatcher->addListener(
                    'kernel.terminate',
                    function (Event $event) {
                        $this->cleanupOldNotifcations();
                    },
                    10
                );
            }
        } catch (\Exception $e) {
            // Ignore if cannot get enough entropy for random_int
        }
    }

    protected function addDocChangeToFirebase(array $notification, int $docId): void
    {
        $this->logger->info('Adding to firebase', [
            'msg' => $notification['msg'],
            'user' => $notification['by'],
            'changes' => $notification['changes'],
        ]);

        $path = "/doc/{$docId}/notification";
        $db = $this->firebase->getDatabase();
        $db->getReference($path)->push($notification);
    }

    protected function cleanupOldNotifcations(): void
    {
        $expireBefore = (new \DateTime('now - 5 minutes'))->format('Uv');
        $removeKeys = [];
        $path = '/doc';

        $db = $this->firebase->getDatabase();
        $docs = $db->getReference($path)->getValue();
        if (null !== $docs) {
            foreach ($docs as $doc => $content) {
                if (array_key_exists('notification', $content)) {
                    foreach ($content['notification'] as $key => $notification) {
                        if ($expireBefore > $notification['at']) {
                            $removeKeys["/doc/{$doc}/notification/{$key}"] = null;
                        }
                    }
                }
            }

            if (0 < \count($removeKeys)) {
                $db->getReference()->update($removeKeys);
            }
        }
    }
}
{'fraction_non_alphanumeric': 0.15079242960455455, 'fraction_numerical': 0.0018464379135251577, 'mean_word_length': 1.7969018932874354, 'pattern_counts': {'":': 12, '<': 6, '<?xml version=': 0, '>': 68, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2272299', 'n_tokens_mistral': 1817, 'n_tokens_neox': 1772, 'n_words': 346}