From 0b7b67e2e71c2ae6679d522c72990b8a1ef8c132 Mon Sep 17 00:00:00 2001 From: Celestora Date: Fri, 15 Oct 2021 23:05:27 +0300 Subject: [PATCH] Notifications: WIP: Add ajax notifications :D --- ServiceAPI/Notifications.php | 83 ++++++++++++++++++ .../Entities/Notifications/Notification.php | 42 ++++++--- Web/Models/Repositories/Notifications.php | 52 +++++++++-- Web/Presenters/InternalAPIPresenter.php | 6 +- Web/Presenters/templates/@layout.xml | 9 +- .../components/notifications/0/_14_18_.xml | 5 -- Web/static/audio/Bruh.mp3 | Bin 0 -> 7662 bytes Web/static/js/al_notifs.js | 37 ++++++++ Web/static/js/package.json | 2 + Web/static/js/yarn.lock | 35 ++++++++ 10 files changed, 244 insertions(+), 27 deletions(-) create mode 100644 ServiceAPI/Notifications.php create mode 100644 Web/static/audio/Bruh.mp3 create mode 100644 Web/static/js/al_notifs.js diff --git a/ServiceAPI/Notifications.php b/ServiceAPI/Notifications.php new file mode 100644 index 00000000..12f0ed8e --- /dev/null +++ b/ServiceAPI/Notifications.php @@ -0,0 +1,83 @@ +user = $user; + $this->notifs = new N; + } + + function ack(callable $resolve, callable $reject): void + { + $this->user->updateNotificationOffset(); + $this->user->save(); + $resolve("OK"); + } + + function fetch(callable $resolve, callable $reject): void + { + $kafkaConf = OPENVK_ROOT_CONF["openvk"]["credentials"]["notificationsBroker"]; + if(!$kafkaConf["enable"]) { + $reject(1999, "Disabled"); + return; + } + + $kafkaConf = $kafkaConf["kafka"]; + $conf = new RDKConf(); + $conf->set("metadata.broker.list", $kafkaConf["addr"] . ":" . $kafkaConf["port"]); + $conf->set("group.id", "UserFetch-" . $this->user->getId()); # Чтобы уведы приходили только на разные устройства одного чебупелика + $conf->set("auto.offset.reset", "latest"); + + set_time_limit(30); + $consumer = new KafkaConsumer($conf); + $consumer->subscribe([ $kafkaConf["topic"] ]); + + while(true) { + $message = $consumer->consume(30*1000); + switch ($message->err) { + case RD_KAFKA_RESP_ERR_NO_ERROR: + $descriptor = $message->payload; + [,$user,] = explode(",", $descriptor); + if(((int) $user) === $this->user->getId()) { + $data = (object) []; + $notification = $this->notifs->fromDescriptor($descriptor, $data); + if(!$notification) { + $reject(1982, "Server Error"); + return; + } + + $tplDir = __DIR__ . "/../Web/Presenters/templates/components/notifications/"; + $tplId = "$tplDir$data->actionCode/_$data->originModelType" . "_" . $data->targetModelType . "_.xml"; + $latte = new TemplatingEngine; + $latte->setTempDirectory(CHANDLER_ROOT . "/tmp/cache/templates"); + $latte->addFilter("translate", fn($trId) => tr($trId)); + $resolve([ + "title" => tr("notif_" . $data->actionCode . "_" . $data->originModelType . "_" . $data->targetModelType), + "body" => trim(preg_replace('%(\s){2,}%', "$1", $latte->renderToString($tplId, ["notification" => $notification]))), + "ava" => $notification->getModel(1)->getAvatarUrl(), + "priority" => 1, + ]); + return; + } + + break; + case RD_KAFKA_RESP_ERR__TIMED_OUT: + case RD_KAFKA_RESP_ERR__PARTITION_EOF: + $reject(1983, "Nothing to report"); + break 2; + default: + $reject(1981, "Kafka Error: " . $message->errstr()); + break 2; + } + } + } +} diff --git a/Web/Models/Entities/Notifications/Notification.php b/Web/Models/Entities/Notifications/Notification.php index 09a015f2..6c2c113d 100644 --- a/Web/Models/Entities/Notifications/Notification.php +++ b/Web/Models/Entities/Notifications/Notification.php @@ -3,6 +3,7 @@ namespace openvk\Web\Models\Entities\Notifications; use openvk\Web\Models\RowModel; use openvk\Web\Models\Entities\{User}; use openvk\Web\Util\DateTime; +use RdKafka\{Conf, Producer}; class Notification { @@ -80,14 +81,14 @@ class Notification return false; $data = [ - $this->recipient->getId(), - $this->encodeType($this->originModel), - $this->originModel->getId(), - $this->encodeType($this->targetModel), - $this->targetModel->getId(), - $this->actionCode, - $this->data, - $this->time, + "recipient" => $this->recipient->getId(), + "originModelType" => $this->encodeType($this->originModel), + "originModelId" => $this->originModel->getId(), + "targetModelType" => $this->encodeType($this->targetModel), + "targetModelId" => $this->targetModel->getId(), + "actionCode" => $this->actionCode, + "additionalPayload" => $this->data, + "timestamp" => $this->time, ]; $edb = $e->getConnection(); @@ -96,12 +97,33 @@ class Notification $query = <<<'QUERY' SELECT * FROM `notifications` WHERE `recipientType`=0 AND `recipientId`=? AND `originModelType`=? AND `originModelId`=? AND `targetModelType`=? AND `targetModelId`=? AND `modelAction`=? AND `additionalData`=? AND `timestamp` > (? - ?) QUERY; - $result = $edb->query($query, ...array_merge($data, [ $this->threshold ])); + $result = $edb->query($query, ...array_merge(array_values($data), [ $this->threshold ])); if($result->getRowCount() > 0) return false; } + + $edb->query("INSERT INTO notifications VALUES (0, ?, ?, ?, ?, ?, ?, ?, ?)", ...array_values($data)); - $edb->query("INSERT INTO notifications VALUES (0, ?, ?, ?, ?, ?, ?, ?, ?)", ...$data); + $kafkaConf = OPENVK_ROOT_CONF["openvk"]["credentials"]["notificationsBroker"]; + if($kafkaConf["enable"]) { + $kafkaConf = $kafkaConf["kafka"]; + $brokerConf = new Conf(); + $brokerConf->set("log_level", (string) LOG_DEBUG); + $brokerConf->set("debug", "all"); + + $producer = new Producer($brokerConf); + $producer->addBrokers($kafkaConf["addr"] . ":" . $kafkaConf["port"]); + + $descriptor = implode(",", [ + str_replace("\\", ".", get_class($this)), + $this->recipient->getId(), + base64_encode(serialize((object) $data)), + ]); + + $notifTopic = $producer->newTopic($kafkaConf["topic"]); + $notifTopic->produce(RD_KAFKA_PARTITION_UA, RD_KAFKA_MSG_F_BLOCK, $descriptor); + $producer->flush(100); + } return true; } diff --git a/Web/Models/Repositories/Notifications.php b/Web/Models/Repositories/Notifications.php index 4ae4806e..3b93b7a6 100644 --- a/Web/Models/Repositories/Notifications.php +++ b/Web/Models/Repositories/Notifications.php @@ -43,6 +43,19 @@ class Notifications return $query; } + private function assemble(int $act, int $originModelType, int $originModelId, int $targetModelType, int $targetModelId, int $recipientId, int $timestamp, $data, ?string $class = NULL): Notification + { + $class ??= 'openvk\Web\Models\Entities\Notifications\Notification'; + + $originModel = $this->getModel($originModelType, $originModelId); + $targetModel = $this->getModel($targetModelType, $targetModelId); + $recipient = (new Users)->get($recipientId); + + $notification = new $class($recipient, $originModel, $targetModel, $timestamp, $data); + $notification->setActionCode($act); + return $notification; + } + function getNotificationCountByUser(User $user, int $offset, bool $archived = false): int { $db = $this->getEDB(false); @@ -64,13 +77,38 @@ class Notifications $results = $this->getEDB()->query($this->getQuery($user, false, $offset, $archived, $page, $perPage)); foreach($results->fetchAll() as $notif) { - $originModel = $this->getModel($notif->originModelType, $notif->originModelId); - $targetModel = $this->getModel($notif->targetModelType, $notif->targetModelId); - $recipient = (new Users)->get($notif->recipientId); - - $notification = new Notification($recipient, $originModel, $targetModel, $notif->timestamp, $notif->additionalData); - $notification->setActionCode($notif->modelAction); - yield $notification; + yield $this->assemble( + $notif->modelAction, + $notif->originModelType, + $notif->originModelId, + + $notif->targetModelType, + $notif->targetModelId, + + $notif->recipientId, + $notif->timestamp, + $notif->additionalData + ); } } + + function fromDescriptor(string $descriptor, ?object &$parsedData = nullptr) + { + [$class, $recv, $data] = explode(",", $descriptor); + $class = str_replace(".", "\\", $class); + + $parsedData = unserialize(base64_decode($data)); + return $this->assemble( + $parsedData->actionCode, + $parsedData->originModelType, + $parsedData->originModelId, + + $parsedData->targetModelType, + $parsedData->targetModelId, + + $parsedData->recipient, + $parsedData->timestamp, + $parsedData->additionalPayload, + ); + } } diff --git a/Web/Presenters/InternalAPIPresenter.php b/Web/Presenters/InternalAPIPresenter.php index 4469ec24..a8933639 100644 --- a/Web/Presenters/InternalAPIPresenter.php +++ b/Web/Presenters/InternalAPIPresenter.php @@ -58,8 +58,8 @@ final class InternalAPIPresenter extends OpenVKPresenter try { $params = array_merge($input->params ?? [], [function($data) { $this->succ($data); - }, function($data) { - $this->fail($data); + }, function(int $errno, string $errstr) { + $this->fail($errno, $errstr); }]); $handler->{$method}(...$params); } catch(\TypeError $te) { @@ -68,4 +68,4 @@ final class InternalAPIPresenter extends OpenVKPresenter $this->fail(-32603, "Uncaught " . get_class($ex)); } } -} \ No newline at end of file +} diff --git a/Web/Presenters/templates/@layout.xml b/Web/Presenters/templates/@layout.xml index df7f7b6e..c9faf2ab 100644 --- a/Web/Presenters/templates/@layout.xml +++ b/Web/Presenters/templates/@layout.xml @@ -252,12 +252,13 @@ {_footer_privacy}

OpenVK {php echo OPENVK_VERSION} | PHP: {phpversion()} | DB: {$dbVersion}

-

+

{php echo OPENVK_ROOT_CONF["openvk"]["appearance"]["motd"]}

- + {script "js/node_modules/msgpack-lite/dist/msgpack.min.js"} + {script "js/node_modules/soundjs/lib/soundjs.min.js"} {script "js/node_modules/ky/umd.js"} {script "js/messagebox.js"} {script "js/notifications.js"} @@ -265,6 +266,10 @@ {script "js/al_wall.js"} {script "js/al_api.js"} + {ifset $thisUser} + {script "js/al_notifs.js"} + {/ifset} +