mirror of
https://github.com/chenasraf/nextcloud-forum.git
synced 2026-05-17 17:28:02 +00:00
feat: weekly task now calculates category/thread post counts
This commit is contained in:
@@ -56,11 +56,12 @@ The forum integrates seamlessly with your Nextcloud instance, using your existin
|
||||
<nextcloud min-version="29" max-version="33"/>
|
||||
</dependencies>
|
||||
<background-jobs>
|
||||
<job>OCA\Forum\Cron\RebuildUserStatsTask</job>
|
||||
<job>OCA\Forum\Cron\RebuildStatsTask</job>
|
||||
</background-jobs>
|
||||
<commands>
|
||||
<command>OCA\Forum\Command\TestNotifier</command>
|
||||
<command>OCA\Forum\Command\RebuildUserStats</command>
|
||||
<command>OCA\Forum\Command\SetRole</command>
|
||||
</commands>
|
||||
<navigations>
|
||||
<navigation role="all">
|
||||
|
||||
@@ -7,14 +7,14 @@ declare(strict_types=1);
|
||||
|
||||
namespace OCA\Forum\Command;
|
||||
|
||||
use OCA\Forum\Service\UserStatsService;
|
||||
use OCA\Forum\Service\StatsService;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class RebuildUserStats extends Command {
|
||||
public function __construct(
|
||||
private UserStatsService $userStatsService,
|
||||
private StatsService $statsService,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
@@ -28,7 +28,7 @@ class RebuildUserStats extends Command {
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int {
|
||||
$output->writeln('<info>Rebuilding user statistics for all users...</info>');
|
||||
|
||||
$result = $this->userStatsService->createStatsForAllUsers();
|
||||
$result = $this->statsService->rebuildAllUserStats();
|
||||
|
||||
$output->writeln(sprintf('Processed %d users', $result['users']));
|
||||
$output->writeln(sprintf('Created %d new user stats', $result['created']));
|
||||
|
||||
54
lib/Cron/RebuildStatsTask.php
Normal file
54
lib/Cron/RebuildStatsTask.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
// SPDX-FileCopyrightText: Chen Asraf <contact@casraf.dev>
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
namespace OCA\Forum\Cron;
|
||||
|
||||
use OCA\Forum\Service\StatsService;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\BackgroundJob\TimedJob;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class RebuildStatsTask extends TimedJob {
|
||||
public function __construct(
|
||||
ITimeFactory $time,
|
||||
private StatsService $statsService,
|
||||
private LoggerInterface $logger,
|
||||
) {
|
||||
parent::__construct($time);
|
||||
|
||||
// Run once a week (604800 seconds = 7 days)
|
||||
$this->setInterval(604800);
|
||||
}
|
||||
|
||||
protected function run($arguments): void {
|
||||
$this->logger->info('Starting weekly stats rebuild');
|
||||
|
||||
// Rebuild user stats
|
||||
$userResult = $this->statsService->rebuildAllUserStats();
|
||||
$this->logger->info('User stats rebuild completed', [
|
||||
'users' => $userResult['users'],
|
||||
'created' => $userResult['created'],
|
||||
'updated' => $userResult['updated'],
|
||||
]);
|
||||
|
||||
// Rebuild category stats
|
||||
$categoryResult = $this->statsService->rebuildAllCategoryStats();
|
||||
$this->logger->info('Category stats rebuild completed', [
|
||||
'categories' => $categoryResult['categories'],
|
||||
'updated' => $categoryResult['updated'],
|
||||
]);
|
||||
|
||||
// Rebuild thread stats
|
||||
$threadResult = $this->statsService->rebuildAllThreadStats();
|
||||
$this->logger->info('Thread stats rebuild completed', [
|
||||
'threads' => $threadResult['threads'],
|
||||
'updated' => $threadResult['updated'],
|
||||
]);
|
||||
|
||||
$this->logger->info('Weekly stats rebuild completed');
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
// SPDX-FileCopyrightText: Chen Asraf <contact@casraf.dev>
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
namespace OCA\Forum\Cron;
|
||||
|
||||
use OCA\Forum\Service\UserStatsService;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\BackgroundJob\TimedJob;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class RebuildUserStatsTask extends TimedJob {
|
||||
public function __construct(
|
||||
ITimeFactory $time,
|
||||
private UserStatsService $userStatsService,
|
||||
private LoggerInterface $logger,
|
||||
) {
|
||||
parent::__construct($time);
|
||||
|
||||
// Run once a week (604800 seconds = 7 days)
|
||||
$this->setInterval(604800);
|
||||
}
|
||||
|
||||
protected function run($arguments): void {
|
||||
$this->logger->info('Starting weekly user stats rebuild for all users');
|
||||
|
||||
$result = $this->userStatsService->createStatsForAllUsers();
|
||||
|
||||
$this->logger->info('User stats rebuild completed', [
|
||||
'users' => $result['users'],
|
||||
'created' => $result['created'],
|
||||
'updated' => $result['updated'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ declare(strict_types=1);
|
||||
namespace OCA\Forum\Listener;
|
||||
|
||||
use OCA\Forum\Db\UserStatsMapper;
|
||||
use OCA\Forum\Service\UserStatsService;
|
||||
use OCA\Forum\Service\StatsService;
|
||||
use OCP\EventDispatcher\Event;
|
||||
use OCP\EventDispatcher\IEventListener;
|
||||
use OCP\User\Events\UserCreatedEvent;
|
||||
@@ -21,7 +21,7 @@ use Psr\Log\LoggerInterface;
|
||||
class UserEventListener implements IEventListener {
|
||||
public function __construct(
|
||||
private UserStatsMapper $userStatsMapper,
|
||||
private UserStatsService $userStatsService,
|
||||
private StatsService $statsService,
|
||||
private LoggerInterface $logger,
|
||||
) {
|
||||
}
|
||||
@@ -40,7 +40,7 @@ class UserEventListener implements IEventListener {
|
||||
|
||||
try {
|
||||
// Create user stats with zero counts for new user
|
||||
$this->userStatsService->rebuildUserStats($userId);
|
||||
$this->statsService->rebuildUserStats($userId);
|
||||
$this->logger->info("Created user stats for new Nextcloud user: {$userId}");
|
||||
} catch (\Exception $ex) {
|
||||
$this->logger->error("Failed to create user stats for new user: {$userId}", [
|
||||
|
||||
@@ -8,14 +8,14 @@ declare(strict_types=1);
|
||||
namespace OCA\Forum\Migration;
|
||||
|
||||
use Closure;
|
||||
use OCA\Forum\Service\UserStatsService;
|
||||
use OCA\Forum\Service\StatsService;
|
||||
use OCP\DB\ISchemaWrapper;
|
||||
use OCP\Migration\IOutput;
|
||||
use OCP\Migration\SimpleMigrationStep;
|
||||
|
||||
class Version2Date20251114222614 extends SimpleMigrationStep {
|
||||
public function __construct(
|
||||
private UserStatsService $userStatsService,
|
||||
private StatsService $statsService,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ class Version2Date20251114222614 extends SimpleMigrationStep {
|
||||
public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
|
||||
$output->info('Creating user statistics for all users...');
|
||||
|
||||
$result = $this->userStatsService->createStatsForAllUsers();
|
||||
$result = $this->statsService->rebuildAllUserStats();
|
||||
|
||||
$output->info(sprintf('Processed %d users', $result['users']));
|
||||
$output->info(sprintf('Created %d new user stats', $result['created']));
|
||||
|
||||
@@ -11,7 +11,7 @@ use OCP\IDBConnection;
|
||||
use OCP\IUserManager;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class UserStatsService {
|
||||
class StatsService {
|
||||
public function __construct(
|
||||
private IDBConnection $db,
|
||||
private IUserManager $userManager,
|
||||
@@ -24,7 +24,7 @@ class UserStatsService {
|
||||
*
|
||||
* @return array{users: int, updated: int, created: int} Statistics about the creation
|
||||
*/
|
||||
public function createStatsForAllUsers(): array {
|
||||
public function rebuildAllUserStats(): array {
|
||||
// Get all user IDs from Nextcloud
|
||||
$users = [];
|
||||
$this->userManager->callForAllUsers(function ($user) use (&$users) {
|
||||
@@ -50,16 +50,6 @@ class UserStatsService {
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild user statistics from actual post and thread counts
|
||||
*
|
||||
* @return array{users: int, updated: int, created: int} Statistics about the rebuild
|
||||
*/
|
||||
public function rebuildAllUserStats(): array {
|
||||
// Delegate to createStatsForAllUsers which processes all Nextcloud users
|
||||
return $this->createStatsForAllUsers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild statistics for a single user
|
||||
*
|
||||
@@ -170,4 +160,128 @@ class UserStatsService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild thread and post counts for all categories
|
||||
*
|
||||
* @return array{categories: int, updated: int} Statistics about the rebuild
|
||||
*/
|
||||
public function rebuildAllCategoryStats(): array {
|
||||
// Get all category IDs
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('id')
|
||||
->from('forum_categories');
|
||||
$result = $qb->executeQuery();
|
||||
$categoryIds = [];
|
||||
while ($row = $result->fetch()) {
|
||||
$categoryIds[] = (int)$row['id'];
|
||||
}
|
||||
$result->closeCursor();
|
||||
|
||||
$updated = 0;
|
||||
foreach ($categoryIds as $categoryId) {
|
||||
$this->rebuildCategoryStats($categoryId);
|
||||
$updated++;
|
||||
}
|
||||
|
||||
return [
|
||||
'categories' => count($categoryIds),
|
||||
'updated' => $updated,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild statistics for a single category
|
||||
*
|
||||
* @param int $categoryId The category ID to rebuild stats for
|
||||
* @return void
|
||||
*/
|
||||
public function rebuildCategoryStats(int $categoryId): void {
|
||||
// Count non-deleted threads in this category
|
||||
$threadQb = $this->db->getQueryBuilder();
|
||||
$threadQb->select($threadQb->func()->count('*', 'count'))
|
||||
->from('forum_threads')
|
||||
->where($threadQb->expr()->eq('category_id', $threadQb->createNamedParameter($categoryId, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT)))
|
||||
->andWhere($threadQb->expr()->isNull('deleted_at'));
|
||||
$threadResult = $threadQb->executeQuery();
|
||||
$threadCount = (int)($threadResult->fetchOne() ?? 0);
|
||||
$threadResult->closeCursor();
|
||||
|
||||
// Count non-deleted posts in non-deleted threads in this category
|
||||
$postQb = $this->db->getQueryBuilder();
|
||||
$postQb->select($postQb->func()->count('*', 'count'))
|
||||
->from('forum_posts', 'p')
|
||||
->innerJoin('p', 'forum_threads', 't', $postQb->expr()->eq('p.thread_id', 't.id'))
|
||||
->where($postQb->expr()->eq('t.category_id', $postQb->createNamedParameter($categoryId, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT)))
|
||||
->andWhere($postQb->expr()->isNull('p.deleted_at'))
|
||||
->andWhere($postQb->expr()->isNull('t.deleted_at'));
|
||||
$postResult = $postQb->executeQuery();
|
||||
$postCount = (int)($postResult->fetchOne() ?? 0);
|
||||
$postResult->closeCursor();
|
||||
|
||||
// Update category stats
|
||||
$updateQb = $this->db->getQueryBuilder();
|
||||
$updateQb->update('forum_categories')
|
||||
->set('thread_count', $updateQb->createNamedParameter($threadCount, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT))
|
||||
->set('post_count', $updateQb->createNamedParameter($postCount, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT))
|
||||
->set('updated_at', $updateQb->createNamedParameter(time(), \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT))
|
||||
->where($updateQb->expr()->eq('id', $updateQb->createNamedParameter($categoryId, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT)));
|
||||
$updateQb->executeStatement();
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild post counts for all threads
|
||||
*
|
||||
* @return array{threads: int, updated: int} Statistics about the rebuild
|
||||
*/
|
||||
public function rebuildAllThreadStats(): array {
|
||||
// Get all non-deleted thread IDs
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('id')
|
||||
->from('forum_threads')
|
||||
->where($qb->expr()->isNull('deleted_at'));
|
||||
$result = $qb->executeQuery();
|
||||
$threadIds = [];
|
||||
while ($row = $result->fetch()) {
|
||||
$threadIds[] = (int)$row['id'];
|
||||
}
|
||||
$result->closeCursor();
|
||||
|
||||
$updated = 0;
|
||||
foreach ($threadIds as $threadId) {
|
||||
$this->rebuildThreadStats($threadId);
|
||||
$updated++;
|
||||
}
|
||||
|
||||
return [
|
||||
'threads' => count($threadIds),
|
||||
'updated' => $updated,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild statistics for a single thread
|
||||
*
|
||||
* @param int $threadId The thread ID to rebuild stats for
|
||||
* @return void
|
||||
*/
|
||||
public function rebuildThreadStats(int $threadId): void {
|
||||
// Count non-deleted posts in this thread
|
||||
$postQb = $this->db->getQueryBuilder();
|
||||
$postQb->select($postQb->func()->count('*', 'count'))
|
||||
->from('forum_posts')
|
||||
->where($postQb->expr()->eq('thread_id', $postQb->createNamedParameter($threadId, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT)))
|
||||
->andWhere($postQb->expr()->isNull('deleted_at'));
|
||||
$postResult = $postQb->executeQuery();
|
||||
$postCount = (int)($postResult->fetchOne() ?? 0);
|
||||
$postResult->closeCursor();
|
||||
|
||||
// Update thread stats
|
||||
$updateQb = $this->db->getQueryBuilder();
|
||||
$updateQb->update('forum_threads')
|
||||
->set('post_count', $updateQb->createNamedParameter($postCount, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT))
|
||||
->set('updated_at', $updateQb->createNamedParameter(time(), \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT))
|
||||
->where($updateQb->expr()->eq('id', $updateQb->createNamedParameter($threadId, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT)));
|
||||
$updateQb->executeStatement();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user