Add f7push v0.1: FCM device registry and push API for F7cloud.
Portable occ-based config, OCS endpoints for APK registration, notification relay, and server-side push dispatch.
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\F7Push\Controller;
|
||||
|
||||
use OCA\F7Push\Service\ConfigService;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
|
||||
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
|
||||
use OCP\AppFramework\Http\DataResponse;
|
||||
use OCP\AppFramework\OCSController;
|
||||
use OCP\IRequest;
|
||||
|
||||
class ConfigApiController extends OCSController {
|
||||
public function __construct(
|
||||
string $appName,
|
||||
IRequest $request,
|
||||
private ConfigService $config,
|
||||
) {
|
||||
parent::__construct($appName, $request);
|
||||
}
|
||||
|
||||
/** Public status for mobile clients (no secrets). */
|
||||
#[NoAdminRequired]
|
||||
#[NoCSRFRequired]
|
||||
public function status(): DataResponse {
|
||||
return new DataResponse([
|
||||
'enabled' => $this->config->isEnabled(),
|
||||
'firebaseConfigured' => $this->config->isFirebaseConfigured(),
|
||||
'defaultClickUrl' => $this->config->getDefaultClickUrl(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\F7Push\Controller;
|
||||
|
||||
use OCA\F7Push\Service\PushDispatcher;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
|
||||
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
|
||||
use OCP\AppFramework\Http\DataResponse;
|
||||
use OCP\AppFramework\OCSController;
|
||||
use OCP\IRequest;
|
||||
use OCP\IUserSession;
|
||||
|
||||
class DeviceApiController extends OCSController {
|
||||
public function __construct(
|
||||
string $appName,
|
||||
IRequest $request,
|
||||
private IUserSession $userSession,
|
||||
private PushDispatcher $pushDispatcher,
|
||||
) {
|
||||
parent::__construct($appName, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register or update FCM device token for the logged-in F7cloud account.
|
||||
*
|
||||
* JSON: { "deviceId", "fcmToken", "platform?", "clientApp?" }
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
#[NoCSRFRequired]
|
||||
public function register(): DataResponse {
|
||||
$user = $this->userSession->getUser();
|
||||
if ($user === null) {
|
||||
return new DataResponse(['error' => 'unauthorized'], Http::STATUS_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$params = $this->request->getParams();
|
||||
$deviceId = trim((string)($params['deviceId'] ?? ''));
|
||||
$fcmToken = trim((string)($params['fcmToken'] ?? ''));
|
||||
if ($deviceId === '' || $fcmToken === '') {
|
||||
return new DataResponse(['error' => 'deviceId and fcmToken required'], Http::STATUS_BAD_REQUEST);
|
||||
}
|
||||
|
||||
$platform = trim((string)($params['platform'] ?? 'android'));
|
||||
$clientApp = trim((string)($params['clientApp'] ?? 'f7cloud-apk'));
|
||||
|
||||
$device = $this->pushDispatcher->registerDevice(
|
||||
$user->getUID(),
|
||||
$deviceId,
|
||||
$fcmToken,
|
||||
$platform !== '' ? $platform : 'android',
|
||||
$clientApp !== '' ? $clientApp : 'f7cloud-apk',
|
||||
);
|
||||
|
||||
return new DataResponse([
|
||||
'success' => true,
|
||||
'deviceId' => $device->getDeviceId(),
|
||||
]);
|
||||
}
|
||||
|
||||
#[NoAdminRequired]
|
||||
#[NoCSRFRequired]
|
||||
public function unregister(string $deviceId): DataResponse {
|
||||
$user = $this->userSession->getUser();
|
||||
if ($user === null) {
|
||||
return new DataResponse(['error' => 'unauthorized'], Http::STATUS_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$this->pushDispatcher->unregisterDevice($user->getUID(), $deviceId);
|
||||
return new DataResponse(['success' => true]);
|
||||
}
|
||||
|
||||
#[NoAdminRequired]
|
||||
#[NoCSRFRequired]
|
||||
public function listDevices(): DataResponse {
|
||||
$user = $this->userSession->getUser();
|
||||
if ($user === null) {
|
||||
return new DataResponse(['error' => 'unauthorized'], Http::STATUS_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$devices = $this->pushDispatcher->listDevices($user->getUID());
|
||||
$list = array_map(static function ($device) {
|
||||
return [
|
||||
'deviceId' => $device->getDeviceId(),
|
||||
'platform' => $device->getPlatform(),
|
||||
'clientApp' => $device->getClientApp(),
|
||||
'lastSeen' => $device->getLastSeen(),
|
||||
];
|
||||
}, $devices);
|
||||
|
||||
return new DataResponse(['devices' => $list]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\F7Push\Controller;
|
||||
|
||||
use OCA\F7Push\Model\PushMessage;
|
||||
use OCA\F7Push\Service\ConfigService;
|
||||
use OCA\F7Push\Service\PushDispatcher;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
|
||||
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
|
||||
use OCP\AppFramework\Http\DataResponse;
|
||||
use OCP\AppFramework\OCSController;
|
||||
use OCP\IRequest;
|
||||
use OCP\IUserSession;
|
||||
|
||||
class PushApiController extends OCSController {
|
||||
public function __construct(
|
||||
string $appName,
|
||||
IRequest $request,
|
||||
private ConfigService $config,
|
||||
private PushDispatcher $pushDispatcher,
|
||||
private IUserSession $userSession,
|
||||
) {
|
||||
parent::__construct($appName, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Server-to-server push (f7support and other F7cloud apps).
|
||||
* Header: X-F7-Push-Secret
|
||||
*
|
||||
* JSON: { "userId", "title", "body", "url?", "source?", "priority?" }
|
||||
*/
|
||||
#[NoCSRFRequired]
|
||||
public function send(): DataResponse {
|
||||
if (!$this->authorizeSecret()) {
|
||||
return new DataResponse(['error' => 'forbidden'], Http::STATUS_FORBIDDEN);
|
||||
}
|
||||
|
||||
$params = $this->request->getParams();
|
||||
$userId = trim((string)($params['userId'] ?? ''));
|
||||
$title = trim((string)($params['title'] ?? ''));
|
||||
$body = trim((string)($params['body'] ?? ''));
|
||||
if ($userId === '' || $title === '') {
|
||||
return new DataResponse(['error' => 'userId and title required'], Http::STATUS_BAD_REQUEST);
|
||||
}
|
||||
|
||||
$sent = $this->pushDispatcher->dispatch(new PushMessage(
|
||||
userId: $userId,
|
||||
title: $title,
|
||||
body: $body,
|
||||
source: trim((string)($params['source'] ?? 'api')),
|
||||
priority: trim((string)($params['priority'] ?? 'normal')),
|
||||
url: isset($params['url']) ? trim((string)$params['url']) : null,
|
||||
));
|
||||
|
||||
return new DataResponse(['success' => true, 'sent' => $sent]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send test push to devices of the current user (admin or self).
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
#[NoCSRFRequired]
|
||||
public function test(): DataResponse {
|
||||
$user = $this->userSession->getUser();
|
||||
if ($user === null) {
|
||||
return new DataResponse(['error' => 'unauthorized'], Http::STATUS_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$sent = $this->pushDispatcher->dispatch(new PushMessage(
|
||||
userId: $user->getUID(),
|
||||
title: 'F7 Push',
|
||||
body: 'Test notification from F7cloud',
|
||||
source: 'f7push',
|
||||
priority: 'normal',
|
||||
url: $this->config->getDefaultClickUrl(),
|
||||
));
|
||||
|
||||
return new DataResponse(['success' => true, 'sent' => $sent]);
|
||||
}
|
||||
|
||||
private function authorizeSecret(): bool {
|
||||
$header = $this->request->getHeader('X-F7-Push-Secret');
|
||||
if ($header === '') {
|
||||
$header = (string)$this->request->getParam('secret', '');
|
||||
}
|
||||
return $this->config->verifyApiSecret($header);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user