Add f7push webhook for support replies and ticket deep links.

OCS endpoint for support.f7cloud.ru, notification notifier, and ?ticket= URL opening in the client UI.
This commit is contained in:
root
2026-05-24 12:30:02 +03:00
parent 92df667d78
commit dc299709f7
10 changed files with 332 additions and 21 deletions
+4
View File
@@ -50,6 +50,10 @@ class PageController extends Controller {
// Отключить вызовы с клиента: occ config:app:set f7support client_read_receipts --value=0
$clientReadReceipts = $this->config->getAppValue('f7support', 'client_read_receipts', '0');
// Push webhook (support.f7cloud.ru → f7push): POST /ocs/v2.php/apps/f7support/api/v1/push
// Secret: occ config:app:get f7push api_secret (or f7support push_webhook_secret)
// occ config:app:set f7support push_enabled --value=yes
// Периодический GET /api/client/tickets (мс): has_unread и отпечаток карточки; WebSocket только у открытого тикета.
// По умолчанию 3000 (3 с). Пустая строка в конфиге трактуется как 3000. Явное «0» отключает опрос. Иначе 3000…120000. occ: f7support tickets_poll_ms
$rawPoll = trim((string) $this->config->getAppValue('f7support', 'tickets_poll_ms', '3000'));
+87
View File
@@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
namespace OCA\F7Support\Controller;
use OCA\F7Support\Service\ConfigService;
use OCA\F7Support\Service\PushBridgeService;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController;
use OCP\IRequest;
use OCP\IUserManager;
/**
* Webhook for support.f7cloud.ru: notify F7cloud user about a new support reply.
*
* Header: X-F7-Support-Push-Secret or X-F7-Push-Secret
* JSON: { userId?, email?, ticketNumber, body?, messagePreview?, ticketSubject? }
*/
class PushApiController extends OCSController {
public function __construct(
string $appName,
IRequest $request,
private ConfigService $config,
private PushBridgeService $pushBridge,
private IUserManager $userManager,
) {
parent::__construct($appName, $request);
}
public function notify(): DataResponse {
if (!$this->authorize()) {
return new DataResponse(['error' => 'forbidden'], Http::STATUS_FORBIDDEN);
}
$params = $this->request->getParams();
$userId = trim((string)($params['userId'] ?? $params['user'] ?? ''));
if ($userId === '') {
$email = trim((string)($params['email'] ?? $params['clientEmail'] ?? ''));
if ($email !== '') {
$user = $this->userManager->getByEmail($email);
if ($user !== null) {
$userId = $user->getUID();
}
}
}
$ticketNumber = trim((string)($params['ticketNumber'] ?? $params['ticket'] ?? ''));
$preview = trim((string)($params['body'] ?? $params['messagePreview'] ?? $params['preview'] ?? ''));
$ticketSubject = trim((string)($params['ticketSubject'] ?? $params['subject'] ?? ''));
if ($userId === '' || $ticketNumber === '') {
return new DataResponse(
['error' => 'userId (or email) and ticketNumber required'],
Http::STATUS_BAD_REQUEST
);
}
if ($this->userManager->get($userId) === null) {
return new DataResponse(['error' => 'user not found'], Http::STATUS_NOT_FOUND);
}
$queued = $this->pushBridge->notifyNewSupportMessage(
$userId,
$ticketNumber,
$preview,
$ticketSubject !== '' ? $ticketSubject : null,
);
return new DataResponse([
'success' => $queued,
'queued' => $queued,
]);
}
private function authorize(): bool {
$header = $this->request->getHeader('X-F7-Support-Push-Secret');
if ($header === '') {
$header = $this->request->getHeader('X-F7-Push-Secret');
}
if ($header === '') {
$header = (string)$this->request->getParam('secret', '');
}
return $this->config->verifyPushWebhookSecret($header);
}
}