Merge pull request 'feat: add Perfect Publisher web services API plugin' (#68) from dev into main
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 4s
Joomla: Repo Health / Release configuration (push) Blocked by required conditions
Joomla: Repo Health / Scripts governance (push) Blocked by required conditions
Joomla: Repo Health / Repository health (push) Blocked by required conditions
Joomla: Repo Health / Access control (push) Successful in 1s
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 4s
Joomla: Repo Health / Release configuration (push) Blocked by required conditions
Joomla: Repo Health / Scripts governance (push) Blocked by required conditions
Joomla: Repo Health / Repository health (push) Blocked by required conditions
Joomla: Repo Health / Access control (push) Successful in 1s
This commit was merged in pull request #68.
This commit is contained in:
@@ -34,6 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Added
|
||||
- `branch-cleanup.yml`: auto-delete merged feature branches after PR merge
|
||||
- `plg_webservices_perfectpublisher`: REST API for Perfect Publisher (com_autotweet) — channels, posts, requests, rules, feeds, and stats
|
||||
|
||||
### Planned
|
||||
- License/subscription check
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
DEFGROUP: Joomla.Plugin
|
||||
INGROUP: MokoWaaS
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS
|
||||
VERSION: 02.13.01
|
||||
VERSION: 02.13.02
|
||||
PATH: /README.md
|
||||
BRIEF: MokoWaaS platform plugin for Joomla
|
||||
-->
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<license>GPL-3.0-or-later</license>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<version>02.13.01</version>
|
||||
<version>02.13.02</version>
|
||||
<description>Minimal API-only component for MokoWaaS. Provides REST endpoints for site health, cache, updates, and backups.</description>
|
||||
<namespace path="api/src">Moko\Component\MokoWaaS\Api</namespace>
|
||||
<administration>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<license>GNU General Public License version 3 or later; see LICENSE.md</license>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<version>02.13.01</version>
|
||||
<version>02.13.02</version>
|
||||
<description>This plugin rebrands the Joomla system interface with MokoWaaS identity. It applies language overrides and ensures consistent branding across the platform.</description>
|
||||
<namespace path=".">Moko\Plugin\System\MokoWaaS</namespace>
|
||||
<scriptfile>script.php</scriptfile>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<license>GPL-3.0-or-later</license>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<version>02.13.01</version>
|
||||
<version>02.13.02</version>
|
||||
<description>Joomla Web Services API routes for MokoWaaS site management — health checks, cache, updates, backups, and site info.</description>
|
||||
<namespace path="src">Moko\Plugin\WebServices\MokoWaaS</namespace>
|
||||
<files>
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<extension type="plugin" group="webservices" method="upgrade">
|
||||
<name>Web Services - Perfect Publisher</name>
|
||||
<author>Moko Consulting</author>
|
||||
<creationDate>2026-05-28</creationDate>
|
||||
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
|
||||
<license>GPL-3.0-or-later</license>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<version>02.13.02</version>
|
||||
<description>Joomla Web Services API routes for Perfect Publisher (com_autotweet) — channels, posts, requests, rules, and feeds.</description>
|
||||
<namespace path="src">Moko\Plugin\WebServices\PerfectPublisher</namespace>
|
||||
<files>
|
||||
<folder plugin="perfectpublisher">services</folder>
|
||||
<folder>src</folder>
|
||||
</files>
|
||||
</extension>
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: Joomla.Plugin
|
||||
* INGROUP: MokoWaaS
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS
|
||||
* PATH: /src/packages/plg_webservices_perfectpublisher/services/provider.php
|
||||
* VERSION: 02.13.01
|
||||
* BRIEF: DI service provider for Perfect Publisher Web Services plugin
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Extension\PluginInterface;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Plugin\PluginHelper;
|
||||
use Joomla\DI\Container;
|
||||
use Joomla\DI\ServiceProviderInterface;
|
||||
use Joomla\Event\DispatcherInterface;
|
||||
use Moko\Plugin\WebServices\PerfectPublisher\Extension\PerfectPublisherApi;
|
||||
|
||||
return new class implements ServiceProviderInterface
|
||||
{
|
||||
public function register(Container $container): void
|
||||
{
|
||||
$container->set(
|
||||
PluginInterface::class,
|
||||
function (Container $container) {
|
||||
$dispatcher = $container->get(DispatcherInterface::class);
|
||||
$plugin = new PerfectPublisherApi(
|
||||
$dispatcher,
|
||||
(array) PluginHelper::getPlugin('webservices', 'perfectpublisher')
|
||||
);
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
return $plugin;
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,539 @@
|
||||
<?php
|
||||
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: Joomla.Plugin
|
||||
* INGROUP: MokoWaaS
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS
|
||||
* PATH: /src/packages/plg_webservices_perfectpublisher/src/Extension/PerfectPublisherApi.php
|
||||
* VERSION: 02.13.01
|
||||
* BRIEF: Web Services API plugin for Perfect Publisher (com_autotweet)
|
||||
*/
|
||||
|
||||
namespace Moko\Plugin\WebServices\PerfectPublisher\Extension;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\CMS\Event\Application\BeforeApiRouteEvent;
|
||||
use Joomla\CMS\Router\ApiRouter;
|
||||
use Joomla\Event\SubscriberInterface;
|
||||
|
||||
/**
|
||||
* Perfect Publisher Web Services API Plugin
|
||||
*
|
||||
* Registers REST API routes for Perfect Publisher (com_autotweet) data.
|
||||
* Provides read access to channels, posts, requests, rules, and feeds.
|
||||
* Provides write access to create publish requests.
|
||||
*
|
||||
* Routes:
|
||||
* GET /v1/perfectpublisher/channels List social channels
|
||||
* GET /v1/perfectpublisher/channels/:id Get channel detail
|
||||
* GET /v1/perfectpublisher/posts List published posts
|
||||
* GET /v1/perfectpublisher/posts/:id Get post detail
|
||||
* GET /v1/perfectpublisher/requests List pending requests
|
||||
* POST /v1/perfectpublisher/requests Create a publish request
|
||||
* GET /v1/perfectpublisher/rules List publishing rules
|
||||
* GET /v1/perfectpublisher/feeds List RSS feeds
|
||||
* GET /v1/perfectpublisher/channeltypes List channel type definitions
|
||||
* GET /v1/perfectpublisher/stats Dashboard statistics
|
||||
*
|
||||
* @since 02.13.01
|
||||
*/
|
||||
final class PerfectPublisherApi extends CMSPlugin implements SubscriberInterface
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
'onBeforeApiRoute' => 'onBeforeApiRoute',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register API routes.
|
||||
*
|
||||
* @param BeforeApiRouteEvent $event The API route event
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function onBeforeApiRoute(BeforeApiRouteEvent $event): void
|
||||
{
|
||||
$router = $event->getRouter();
|
||||
|
||||
// All routes are handled by this plugin directly via custom callbacks
|
||||
// because com_autotweet uses FOF, not standard Joomla MVC
|
||||
|
||||
$router->addRoute(
|
||||
new \Joomla\Router\Route(
|
||||
['GET'],
|
||||
'v1/perfectpublisher/channels',
|
||||
[$this, 'getChannels']
|
||||
)
|
||||
);
|
||||
|
||||
$router->addRoute(
|
||||
new \Joomla\Router\Route(
|
||||
['GET'],
|
||||
'v1/perfectpublisher/channels/:id',
|
||||
[$this, 'getChannel']
|
||||
)
|
||||
);
|
||||
|
||||
$router->addRoute(
|
||||
new \Joomla\Router\Route(
|
||||
['GET'],
|
||||
'v1/perfectpublisher/posts',
|
||||
[$this, 'getPosts']
|
||||
)
|
||||
);
|
||||
|
||||
$router->addRoute(
|
||||
new \Joomla\Router\Route(
|
||||
['GET'],
|
||||
'v1/perfectpublisher/posts/:id',
|
||||
[$this, 'getPost']
|
||||
)
|
||||
);
|
||||
|
||||
$router->addRoute(
|
||||
new \Joomla\Router\Route(
|
||||
['GET'],
|
||||
'v1/perfectpublisher/requests',
|
||||
[$this, 'getRequests']
|
||||
)
|
||||
);
|
||||
|
||||
$router->addRoute(
|
||||
new \Joomla\Router\Route(
|
||||
['POST'],
|
||||
'v1/perfectpublisher/requests',
|
||||
[$this, 'createRequest']
|
||||
)
|
||||
);
|
||||
|
||||
$router->addRoute(
|
||||
new \Joomla\Router\Route(
|
||||
['GET'],
|
||||
'v1/perfectpublisher/rules',
|
||||
[$this, 'getRules']
|
||||
)
|
||||
);
|
||||
|
||||
$router->addRoute(
|
||||
new \Joomla\Router\Route(
|
||||
['GET'],
|
||||
'v1/perfectpublisher/feeds',
|
||||
[$this, 'getFeeds']
|
||||
)
|
||||
);
|
||||
|
||||
$router->addRoute(
|
||||
new \Joomla\Router\Route(
|
||||
['GET'],
|
||||
'v1/perfectpublisher/channeltypes',
|
||||
[$this, 'getChannelTypes']
|
||||
)
|
||||
);
|
||||
|
||||
$router->addRoute(
|
||||
new \Joomla\Router\Route(
|
||||
['GET'],
|
||||
'v1/perfectpublisher/stats',
|
||||
[$this, 'getStats']
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /v1/perfectpublisher/channels
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function getChannels(): void
|
||||
{
|
||||
$db = Factory::getDbo();
|
||||
$app = Factory::getApplication();
|
||||
$limit = (int) $app->input->get('limit', 20);
|
||||
$offset = (int) $app->input->get('offset', 0);
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->select('c.*, ct.name AS channeltype_name, ct.max_chars')
|
||||
->from($db->quoteName('#__autotweet_channels', 'c'))
|
||||
->leftJoin(
|
||||
$db->quoteName('#__autotweet_channeltypes', 'ct')
|
||||
. ' ON ' . $db->quoteName('c.channeltype_id')
|
||||
. ' = ' . $db->quoteName('ct.id')
|
||||
)
|
||||
->order($db->quoteName('c.ordering') . ' ASC');
|
||||
|
||||
$published = $app->input->get('published', null);
|
||||
if ($published !== null) {
|
||||
$query->where($db->quoteName('c.published') . ' = ' . (int) $published);
|
||||
}
|
||||
|
||||
$db->setQuery($query, $offset, $limit);
|
||||
|
||||
$this->sendJsonResponse($db->loadObjectList());
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /v1/perfectpublisher/channels/:id
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function getChannel(): void
|
||||
{
|
||||
$id = (int) Factory::getApplication()->input->get('id', 0);
|
||||
$db = Factory::getDbo();
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->select('c.*, ct.name AS channeltype_name, ct.max_chars, ct.description AS channeltype_desc')
|
||||
->from($db->quoteName('#__autotweet_channels', 'c'))
|
||||
->leftJoin(
|
||||
$db->quoteName('#__autotweet_channeltypes', 'ct')
|
||||
. ' ON ' . $db->quoteName('c.channeltype_id')
|
||||
. ' = ' . $db->quoteName('ct.id')
|
||||
)
|
||||
->where($db->quoteName('c.id') . ' = ' . $id);
|
||||
|
||||
$db->setQuery($query);
|
||||
$result = $db->loadObject();
|
||||
|
||||
if (!$result) {
|
||||
$this->sendJsonError('Channel not found', 404);
|
||||
return;
|
||||
}
|
||||
|
||||
// Strip sensitive OAuth params
|
||||
if (isset($result->params)) {
|
||||
$params = json_decode($result->params, true);
|
||||
if (is_array($params)) {
|
||||
foreach (['access_token', 'access_secret', 'client_secret', 'api_secret', 'password'] as $key) {
|
||||
if (isset($params[$key])) {
|
||||
$params[$key] = '***';
|
||||
}
|
||||
}
|
||||
$result->params = json_encode($params);
|
||||
}
|
||||
}
|
||||
|
||||
$this->sendJsonResponse($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /v1/perfectpublisher/posts
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function getPosts(): void
|
||||
{
|
||||
$db = Factory::getDbo();
|
||||
$app = Factory::getApplication();
|
||||
$limit = (int) $app->input->get('limit', 20);
|
||||
$offset = (int) $app->input->get('offset', 0);
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->select('p.*, c.name AS channel_name')
|
||||
->from($db->quoteName('#__autotweet_posts', 'p'))
|
||||
->leftJoin(
|
||||
$db->quoteName('#__autotweet_channels', 'c')
|
||||
. ' ON ' . $db->quoteName('p.channel_id')
|
||||
. ' = ' . $db->quoteName('c.id')
|
||||
)
|
||||
->order($db->quoteName('p.postdate') . ' DESC');
|
||||
|
||||
$pubstate = $app->input->get('pubstate', '');
|
||||
if ($pubstate !== '') {
|
||||
$query->where($db->quoteName('p.pubstate') . ' = ' . $db->quote($pubstate));
|
||||
}
|
||||
|
||||
$channel = (int) $app->input->get('channel_id', 0);
|
||||
if ($channel > 0) {
|
||||
$query->where($db->quoteName('p.channel_id') . ' = ' . $channel);
|
||||
}
|
||||
|
||||
$db->setQuery($query, $offset, $limit);
|
||||
|
||||
$this->sendJsonResponse($db->loadObjectList());
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /v1/perfectpublisher/posts/:id
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function getPost(): void
|
||||
{
|
||||
$id = (int) Factory::getApplication()->input->get('id', 0);
|
||||
$db = Factory::getDbo();
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->select('p.*, c.name AS channel_name, ct.name AS channeltype_name')
|
||||
->from($db->quoteName('#__autotweet_posts', 'p'))
|
||||
->leftJoin(
|
||||
$db->quoteName('#__autotweet_channels', 'c')
|
||||
. ' ON ' . $db->quoteName('p.channel_id')
|
||||
. ' = ' . $db->quoteName('c.id')
|
||||
)
|
||||
->leftJoin(
|
||||
$db->quoteName('#__autotweet_channeltypes', 'ct')
|
||||
. ' ON ' . $db->quoteName('c.channeltype_id')
|
||||
. ' = ' . $db->quoteName('ct.id')
|
||||
)
|
||||
->where($db->quoteName('p.id') . ' = ' . $id);
|
||||
|
||||
$db->setQuery($query);
|
||||
$result = $db->loadObject();
|
||||
|
||||
if (!$result) {
|
||||
$this->sendJsonError('Post not found', 404);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->sendJsonResponse($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /v1/perfectpublisher/requests
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function getRequests(): void
|
||||
{
|
||||
$db = Factory::getDbo();
|
||||
$app = Factory::getApplication();
|
||||
$limit = (int) $app->input->get('limit', 20);
|
||||
$offset = (int) $app->input->get('offset', 0);
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->select('*')
|
||||
->from($db->quoteName('#__autotweet_requests'))
|
||||
->order($db->quoteName('publish_up') . ' ASC');
|
||||
|
||||
$published = $app->input->get('published', null);
|
||||
if ($published !== null) {
|
||||
$query->where($db->quoteName('published') . ' = ' . (int) $published);
|
||||
}
|
||||
|
||||
$db->setQuery($query, $offset, $limit);
|
||||
|
||||
$this->sendJsonResponse($db->loadObjectList());
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /v1/perfectpublisher/requests
|
||||
*
|
||||
* Create a new publish request. Required fields: description.
|
||||
* Optional: url, image_url, publish_up, plugin, priority.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function createRequest(): void
|
||||
{
|
||||
$app = Factory::getApplication();
|
||||
$db = Factory::getDbo();
|
||||
$data = json_decode($app->input->json->getRaw(), true);
|
||||
|
||||
if (empty($data['description'])) {
|
||||
$this->sendJsonError('Field "description" is required', 400);
|
||||
return;
|
||||
}
|
||||
|
||||
$now = Factory::getDate()->toSql();
|
||||
$user = Factory::getUser();
|
||||
|
||||
$row = (object) [
|
||||
'ref_id' => $data['ref_id'] ?? null,
|
||||
'plugin' => $data['plugin'] ?? 'manual-api',
|
||||
'priority' => (int) ($data['priority'] ?? 5),
|
||||
'publish_up' => $data['publish_up'] ?? $now,
|
||||
'description' => $data['description'],
|
||||
'typeinfo' => (int) ($data['typeinfo'] ?? 0),
|
||||
'url' => $data['url'] ?? null,
|
||||
'image_url' => $data['image_url'] ?? null,
|
||||
'created' => $now,
|
||||
'created_by' => $user->id,
|
||||
'params' => json_encode($data['params'] ?? []),
|
||||
'published' => (int) ($data['published'] ?? 1),
|
||||
];
|
||||
|
||||
$db->insertObject('#__autotweet_requests', $row, 'id');
|
||||
|
||||
$this->sendJsonResponse(
|
||||
['id' => $row->id, 'status' => 'created'],
|
||||
201
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /v1/perfectpublisher/rules
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function getRules(): void
|
||||
{
|
||||
$db = Factory::getDbo();
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->select('r.*, rt.name AS ruletype_name, rt.description AS ruletype_desc, c.name AS channel_name')
|
||||
->from($db->quoteName('#__autotweet_rules', 'r'))
|
||||
->leftJoin(
|
||||
$db->quoteName('#__autotweet_ruletypes', 'rt')
|
||||
. ' ON ' . $db->quoteName('r.ruletype_id')
|
||||
. ' = ' . $db->quoteName('rt.id')
|
||||
)
|
||||
->leftJoin(
|
||||
$db->quoteName('#__autotweet_channels', 'c')
|
||||
. ' ON ' . $db->quoteName('r.channel_id')
|
||||
. ' = ' . $db->quoteName('c.id')
|
||||
)
|
||||
->order($db->quoteName('r.ordering') . ' ASC');
|
||||
|
||||
$db->setQuery($query);
|
||||
|
||||
$this->sendJsonResponse($db->loadObjectList());
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /v1/perfectpublisher/feeds
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function getFeeds(): void
|
||||
{
|
||||
$db = Factory::getDbo();
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->select('*')
|
||||
->from($db->quoteName('#__autotweet_feeds'))
|
||||
->order($db->quoteName('ordering') . ' ASC');
|
||||
|
||||
$db->setQuery($query);
|
||||
|
||||
$this->sendJsonResponse($db->loadObjectList());
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /v1/perfectpublisher/channeltypes
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function getChannelTypes(): void
|
||||
{
|
||||
$db = Factory::getDbo();
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->select('*')
|
||||
->from($db->quoteName('#__autotweet_channeltypes'))
|
||||
->order($db->quoteName('id') . ' ASC');
|
||||
|
||||
$db->setQuery($query);
|
||||
|
||||
$this->sendJsonResponse($db->loadObjectList());
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /v1/perfectpublisher/stats
|
||||
*
|
||||
* Dashboard statistics: post counts by status, channel counts, recent activity.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function getStats(): void
|
||||
{
|
||||
$db = Factory::getDbo();
|
||||
|
||||
// Posts by status
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select('pubstate, COUNT(*) AS total')
|
||||
->from($db->quoteName('#__autotweet_posts'))
|
||||
->group($db->quoteName('pubstate'))
|
||||
);
|
||||
$postsByStatus = $db->loadObjectList('pubstate');
|
||||
|
||||
// Active channels
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select('COUNT(*) AS total')
|
||||
->from($db->quoteName('#__autotweet_channels'))
|
||||
->where($db->quoteName('published') . ' = 1')
|
||||
);
|
||||
$activeChannels = (int) $db->loadResult();
|
||||
|
||||
// Pending requests
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select('COUNT(*) AS total')
|
||||
->from($db->quoteName('#__autotweet_requests'))
|
||||
->where($db->quoteName('published') . ' = 1')
|
||||
);
|
||||
$pendingRequests = (int) $db->loadResult();
|
||||
|
||||
// Posts last 24h
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select('COUNT(*) AS total')
|
||||
->from($db->quoteName('#__autotweet_posts'))
|
||||
->where($db->quoteName('postdate') . ' >= DATE_SUB(NOW(), INTERVAL 1 DAY)')
|
||||
);
|
||||
$posts24h = (int) $db->loadResult();
|
||||
|
||||
// Posts last 7d
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select('COUNT(*) AS total')
|
||||
->from($db->quoteName('#__autotweet_posts'))
|
||||
->where($db->quoteName('postdate') . ' >= DATE_SUB(NOW(), INTERVAL 7 DAY)')
|
||||
);
|
||||
$posts7d = (int) $db->loadResult();
|
||||
|
||||
$this->sendJsonResponse([
|
||||
'posts_by_status' => $postsByStatus,
|
||||
'active_channels' => $activeChannels,
|
||||
'pending_requests' => $pendingRequests,
|
||||
'posts_24h' => $posts24h,
|
||||
'posts_7d' => $posts7d,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a JSON API response.
|
||||
*
|
||||
* @param mixed $data Response data
|
||||
* @param int $status HTTP status code
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function sendJsonResponse($data, int $status = 200): void
|
||||
{
|
||||
$app = Factory::getApplication();
|
||||
$app->setHeader('Content-Type', 'application/json; charset=utf-8');
|
||||
$app->setHeader('Status', (string) $status);
|
||||
echo json_encode(['data' => $data], JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
|
||||
$app->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a JSON error response.
|
||||
*
|
||||
* @param string $message Error message
|
||||
* @param int $status HTTP status code
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function sendJsonError(string $message, int $status = 400): void
|
||||
{
|
||||
$app = Factory::getApplication();
|
||||
$app->setHeader('Content-Type', 'application/json; charset=utf-8');
|
||||
$app->setHeader('Status', (string) $status);
|
||||
echo json_encode(['error' => $message], JSON_UNESCAPED_SLASHES);
|
||||
$app->close();
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
<extension type="package" method="upgrade">
|
||||
<name>MokoWaaS</name>
|
||||
<packagename>mokowaas</packagename>
|
||||
<version>02.13.01</version>
|
||||
<version>02.13.02</version>
|
||||
<creationDate>2026-05-23</creationDate>
|
||||
<author>Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
@@ -16,6 +16,7 @@
|
||||
<file type="plugin" id="plg_system_mokowaas" group="system">plg_system_mokowaas.zip</file>
|
||||
<file type="component" id="com_mokowaas">com_mokowaas.zip</file>
|
||||
<file type="plugin" id="plg_webservices_mokowaas" group="webservices">plg_webservices_mokowaas.zip</file>
|
||||
<file type="plugin" id="plg_webservices_perfectpublisher" group="webservices">plg_webservices_perfectpublisher.zip</file>
|
||||
</files>
|
||||
|
||||
<updateservers>
|
||||
|
||||
Reference in New Issue
Block a user