migrate therinaldos.com data
Build & Deploy to DigitalOcean Space / build (push) Failing after 2m38s

This commit is contained in:
2024-05-05 15:50:45 -04:00
commit ef1ff240d4
23182 changed files with 3801898 additions and 0 deletions
@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
/**
* @copyright 2023 Christopher Ng <chrng8@gmail.com>
*
* @author Christopher Ng <chrng8@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\FilesReminders\AppInfo;
use OCA\Files\Event\LoadAdditionalScriptsEvent;
use OCA\FilesReminders\Listener\LoadAdditionalScriptsListener;
use OCA\FilesReminders\Listener\NodeDeletedListener;
use OCA\FilesReminders\Listener\UserDeletedListener;
use OCA\FilesReminders\Notification\Notifier;
use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\Bootstrap\IRegistrationContext;
use OCP\Files\Events\Node\NodeDeletedEvent;
use OCP\User\Events\UserDeletedEvent;
class Application extends App implements IBootstrap {
public const APP_ID = 'files_reminders';
public function __construct() {
parent::__construct(static::APP_ID);
}
public function boot(IBootContext $context): void {
}
public function register(IRegistrationContext $context): void {
$context->registerNotifierService(Notifier::class);
$context->registerEventListener(NodeDeletedEvent::class, NodeDeletedListener::class);
$context->registerEventListener(UserDeletedEvent::class, UserDeletedListener::class);
$context->registerEventListener(LoadAdditionalScriptsEvent::class, LoadAdditionalScriptsListener::class);
}
}
@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
/**
* @copyright 2023 Christopher Ng <chrng8@gmail.com>
*
* @author Christopher Ng <chrng8@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\FilesReminders\BackgroundJob;
use OCA\FilesReminders\Service\ReminderService;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\IJob;
use OCP\BackgroundJob\TimedJob;
class CleanUpReminders extends TimedJob {
public function __construct(
ITimeFactory $time,
private ReminderService $reminderService,
) {
parent::__construct($time);
$this->setInterval(24 * 60 * 60); // 1 day
$this->setTimeSensitivity(IJob::TIME_INSENSITIVE);
}
/**
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
protected function run($argument) {
$this->reminderService->cleanUp(500);
}
}
@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
/**
* @copyright 2023 Christopher Ng <chrng8@gmail.com>
*
* @author Christopher Ng <chrng8@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\FilesReminders\BackgroundJob;
use OCA\FilesReminders\Db\ReminderMapper;
use OCA\FilesReminders\Service\ReminderService;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\Job;
use Psr\Log\LoggerInterface;
class ScheduledNotifications extends Job {
public function __construct(
ITimeFactory $time,
protected ReminderMapper $reminderMapper,
protected ReminderService $reminderService,
protected LoggerInterface $logger,
) {
parent::__construct($time);
}
/**
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function run($argument) {
$reminders = $this->reminderMapper->findOverdue();
foreach ($reminders as $reminder) {
try {
$this->reminderService->send($reminder);
} catch (DoesNotExistException $e) {
$this->logger->debug('Could not send notification for reminder with id ' . $reminder->getId());
}
}
}
}
@@ -0,0 +1,119 @@
<?php
declare(strict_types=1);
/**
* @copyright 2023 Christopher Ng <chrng8@gmail.com>
*
* @author Christopher Ng <chrng8@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\FilesReminders\Command;
use DateTimeInterface;
use OC\Core\Command\Base;
use OCA\FilesReminders\Model\RichReminder;
use OCA\FilesReminders\Service\ReminderService;
use OCP\IUserManager;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
class ListCommand extends Base {
public function __construct(
private ReminderService $reminderService,
private IUserManager $userManager,
) {
parent::__construct();
}
protected function configure(): void {
$this
->setName('files:reminders')
->setDescription('List file reminders')
->addArgument(
'user',
InputArgument::OPTIONAL,
'list reminders for user',
)
->addOption(
'output',
null,
InputOption::VALUE_OPTIONAL,
'Output format (plain, json or json_pretty, default is plain)',
$this->defaultOutputFormat,
);
}
protected function execute(InputInterface $input, OutputInterface $output): int {
$io = new SymfonyStyle($input, $output);
$uid = $input->getArgument('user');
if ($uid !== null) {
/** @var string $uid */
$user = $this->userManager->get($uid);
if ($user === null) {
$io->error("Unknown user <$uid>");
return 1;
}
}
$reminders = $this->reminderService->getAll($user ?? null);
$outputOption = $input->getOption('output');
switch ($outputOption) {
case static::OUTPUT_FORMAT_JSON:
case static::OUTPUT_FORMAT_JSON_PRETTY:
$this->writeArrayInOutputFormat(
$input,
$io,
array_map(
fn (RichReminder $reminder) => $reminder->jsonSerialize(),
$reminders,
),
'',
);
return 0;
default:
if (empty($reminders)) {
$io->text('No reminders');
return 0;
}
$io->table(
['User Id', 'File Id', 'Path', 'Due Date', 'Updated At', 'Created At', 'Notified'],
array_map(
fn (RichReminder $reminder) => [
$reminder->getUserId(),
$reminder->getFileId(),
$reminder->getNode()->getPath(),
$reminder->getDueDate()->format(DateTimeInterface::ATOM), // ISO 8601
$reminder->getUpdatedAt()->format(DateTimeInterface::ATOM), // ISO 8601
$reminder->getCreatedAt()->format(DateTimeInterface::ATOM), // ISO 8601
$reminder->getNotified() ? 'true' : 'false',
],
$reminders,
),
);
return 0;
}
}
}
@@ -0,0 +1,149 @@
<?php
declare(strict_types=1);
/**
* @copyright 2023 Christopher Ng <chrng8@gmail.com>
*
* @author Christopher Ng <chrng8@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\FilesReminders\Controller;
use DateTime;
use DateTimeInterface;
use DateTimeZone;
use Exception;
use OCA\FilesReminders\Exception\NodeNotFoundException;
use OCA\FilesReminders\Service\ReminderService;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController;
use OCP\IRequest;
use OCP\IUserSession;
use Psr\Log\LoggerInterface;
class ApiController extends OCSController {
public function __construct(
string $appName,
IRequest $request,
protected ReminderService $reminderService,
protected IUserSession $userSession,
protected LoggerInterface $logger,
) {
parent::__construct($appName, $request);
}
/**
* Get a reminder
*
* @param int $fileId ID of the file
* @return DataResponse<Http::STATUS_OK, array{dueDate: ?string}, array{}>|DataResponse<Http::STATUS_UNAUTHORIZED, array<empty>, array{}>
*
* 200: Reminder returned
* 401: User not found
*/
#[NoAdminRequired]
public function get(int $fileId): DataResponse {
$user = $this->userSession->getUser();
if ($user === null) {
return new DataResponse([], Http::STATUS_UNAUTHORIZED);
}
try {
$reminder = $this->reminderService->getDueForUser($user, $fileId);
$reminderData = [
'dueDate' => $reminder->getDueDate()->format(DateTimeInterface::ATOM), // ISO 8601
];
return new DataResponse($reminderData, Http::STATUS_OK);
} catch (DoesNotExistException $e) {
$reminderData = [
'dueDate' => null,
];
return new DataResponse($reminderData, Http::STATUS_OK);
}
}
/**
* Set a reminder
*
* @param int $fileId ID of the file
* @param string $dueDate ISO 8601 formatted date time string
*
* @return DataResponse<Http::STATUS_OK|Http::STATUS_CREATED|Http::STATUS_BAD_REQUEST|Http::STATUS_UNAUTHORIZED|Http::STATUS_NOT_FOUND, array<empty>, array{}>
*
* 200: Reminder updated
* 201: Reminder created successfully
* 400: Creating reminder is not possible
* 401: User not found
* 404: File not found
*/
#[NoAdminRequired]
public function set(int $fileId, string $dueDate): DataResponse {
try {
$dueDate = (new DateTime($dueDate))->setTimezone(new DateTimeZone('UTC'));
} catch (Exception $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
return new DataResponse([], Http::STATUS_BAD_REQUEST);
}
$user = $this->userSession->getUser();
if ($user === null) {
return new DataResponse([], Http::STATUS_UNAUTHORIZED);
}
try {
$created = $this->reminderService->createOrUpdate($user, $fileId, $dueDate);
if ($created) {
return new DataResponse([], Http::STATUS_CREATED);
}
return new DataResponse([], Http::STATUS_OK);
} catch (NodeNotFoundException $e) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
}
}
/**
* Remove a reminder
*
* @param int $fileId ID of the file
*
* @return DataResponse<Http::STATUS_OK|Http::STATUS_UNAUTHORIZED|Http::STATUS_NOT_FOUND, array<empty>, array{}>
*
* 200: Reminder deleted successfully
* 401: User not found
* 404: Reminder not found
*/
#[NoAdminRequired]
public function remove(int $fileId): DataResponse {
$user = $this->userSession->getUser();
if ($user === null) {
return new DataResponse([], Http::STATUS_UNAUTHORIZED);
}
try {
$this->reminderService->remove($user, $fileId);
return new DataResponse([], Http::STATUS_OK);
} catch (DoesNotExistException $e) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
}
}
}
@@ -0,0 +1,82 @@
<?php
declare(strict_types=1);
/**
* @copyright 2024 Christopher Ng <chrng8@gmail.com>
*
* @author Christopher Ng <chrng8@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\FilesReminders\Dav;
use DateTimeInterface;
use OCA\DAV\Connector\Sabre\Node;
use OCA\FilesReminders\Service\ReminderService;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\IUser;
use OCP\IUserSession;
use Sabre\DAV\INode;
use Sabre\DAV\PropFind;
use Sabre\DAV\Server;
use Sabre\DAV\ServerPlugin;
class PropFindPlugin extends ServerPlugin {
public const REMINDER_DUE_DATE_PROPERTY = '{http://nextcloud.org/ns}reminder-due-date';
public function __construct(
private ReminderService $reminderService,
private IUserSession $userSession,
) {
}
public function initialize(Server $server): void {
$server->on('propFind', [$this, 'propFind']);
}
public function propFind(PropFind $propFind, INode $node) {
if (!in_array(static::REMINDER_DUE_DATE_PROPERTY, $propFind->getRequestedProperties())) {
return;
}
if (!($node instanceof Node)) {
return;
}
$propFind->handle(
static::REMINDER_DUE_DATE_PROPERTY,
function () use ($node) {
$user = $this->userSession->getUser();
if (!($user instanceof IUser)) {
return '';
}
$fileId = $node->getId();
try {
$reminder = $this->reminderService->getDueForUser($user, $fileId);
} catch (DoesNotExistException $e) {
return '';
}
return $reminder->getDueDate()->format(DateTimeInterface::ATOM); // ISO 8601
},
);
}
}
@@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
/**
* @copyright 2023 Christopher Ng <chrng8@gmail.com>
*
* @author Christopher Ng <chrng8@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\FilesReminders\Db;
use DateTime;
use OCP\AppFramework\Db\Entity;
/**
* @method void setUserId(string $userId)
* @method string getUserId()
*
* @method void setFileId(int $fileId)
* @method int getFileId()
*
* @method void setDueDate(DateTime $dueDate)
* @method DateTime getDueDate()
*
* @method void setUpdatedAt(DateTime $updatedAt)
* @method DateTime getUpdatedAt()
*
* @method void setCreatedAt(DateTime $createdAt)
* @method DateTime getCreatedAt()
*
* @method void setNotified(bool $notified)
* @method bool getNotified()
*/
class Reminder extends Entity {
protected $userId;
protected $fileId;
protected $dueDate;
protected $updatedAt;
protected $createdAt;
protected $notified = false;
public function __construct() {
$this->addType('userId', 'string');
$this->addType('fileId', 'integer');
$this->addType('dueDate', 'datetime');
$this->addType('updatedAt', 'datetime');
$this->addType('createdAt', 'datetime');
$this->addType('notified', 'boolean');
}
}
@@ -0,0 +1,161 @@
<?php
declare(strict_types=1);
/**
* @copyright 2023 Christopher Ng <chrng8@gmail.com>
*
* @author Christopher Ng <chrng8@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\FilesReminders\Db;
use DateTime;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\Files\Node;
use OCP\Files\NotFoundException;
use OCP\IDBConnection;
use OCP\IUser;
/**
* @template-extends QBMapper<Reminder>
*/
class ReminderMapper extends QBMapper {
public const TABLE_NAME = 'files_reminders';
public function __construct(IDBConnection $db) {
parent::__construct(
$db,
static::TABLE_NAME,
Reminder::class,
);
}
public function markNotified(Reminder $reminder): Reminder {
$reminderUpdate = new Reminder();
$reminderUpdate->setId($reminder->getId());
$reminderUpdate->setNotified(true);
return $this->update($reminderUpdate);
}
public function find(int $id): Reminder {
$qb = $this->db->getQueryBuilder();
$qb->select('id', 'user_id', 'file_id', 'due_date', 'updated_at', 'created_at', 'notified')
->from($this->getTableName())
->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
return $this->findEntity($qb);
}
/**
* @throws DoesNotExistException
*/
public function findDueForUser(IUser $user, int $fileId): Reminder {
$qb = $this->db->getQueryBuilder();
$qb->select('id', 'user_id', 'file_id', 'due_date', 'updated_at', 'created_at', 'notified')
->from($this->getTableName())
->where($qb->expr()->eq('user_id', $qb->createNamedParameter($user->getUID(), IQueryBuilder::PARAM_STR)))
->andWhere($qb->expr()->eq('file_id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('notified', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL)));
return $this->findEntity($qb);
}
/**
* @return Reminder[]
*/
public function findAll() {
$qb = $this->db->getQueryBuilder();
$qb->select('id', 'user_id', 'file_id', 'due_date', 'updated_at', 'created_at', 'notified')
->from($this->getTableName())
->orderBy('due_date', 'ASC');
return $this->findEntities($qb);
}
/**
* @return Reminder[]
*/
public function findAllForUser(IUser $user) {
$qb = $this->db->getQueryBuilder();
$qb->select('id', 'user_id', 'file_id', 'due_date', 'updated_at', 'created_at', 'notified')
->from($this->getTableName())
->where($qb->expr()->eq('user_id', $qb->createNamedParameter($user->getUID(), IQueryBuilder::PARAM_STR)))
->orderBy('due_date', 'ASC');
return $this->findEntities($qb);
}
/**
* @return Reminder[]
*/
public function findAllForNode(Node $node) {
try {
$nodeId = $node->getId();
} catch (NotFoundException $e) {
return [];
}
$qb = $this->db->getQueryBuilder();
$qb->select('id', 'user_id', 'file_id', 'due_date', 'updated_at', 'created_at', 'notified')
->from($this->getTableName())
->where($qb->expr()->eq('file_id', $qb->createNamedParameter($nodeId, IQueryBuilder::PARAM_INT)))
->orderBy('due_date', 'ASC');
return $this->findEntities($qb);
}
/**
* @return Reminder[]
*/
public function findOverdue() {
$qb = $this->db->getQueryBuilder();
$qb->select('id', 'user_id', 'file_id', 'due_date', 'updated_at', 'created_at', 'notified')
->from($this->getTableName())
->where($qb->expr()->lt('due_date', $qb->createFunction('NOW()')))
->andWhere($qb->expr()->eq('notified', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL)))
->orderBy('due_date', 'ASC');
return $this->findEntities($qb);
}
/**
* @return Reminder[]
*/
public function findNotified(DateTime $buffer, ?int $limit = null) {
$qb = $this->db->getQueryBuilder();
$qb->select('id', 'user_id', 'file_id', 'due_date', 'updated_at', 'created_at', 'notified')
->from($this->getTableName())
->where($qb->expr()->eq('notified', $qb->createNamedParameter(true, IQueryBuilder::PARAM_BOOL)))
->andWhere($qb->expr()->lt('due_date', $qb->createNamedParameter($buffer, IQueryBuilder::PARAM_DATE)))
->orderBy('due_date', 'ASC')
->setMaxResults($limit);
return $this->findEntities($qb);
}
}
@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
/**
* @copyright 2023 Christopher Ng <chrng8@gmail.com>
*
* @author Christopher Ng <chrng8@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\FilesReminders\Exception;
use Exception;
class NodeNotFoundException extends Exception {
}
@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
/**
* @copyright 2023 Christopher Ng <chrng8@gmail.com>
*
* @author Christopher Ng <chrng8@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\FilesReminders\Exception;
use Exception;
class UserNotFoundException extends Exception {
}
@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
/**
* @copyright 2023 Christopher Ng <chrng8@gmail.com>
*
* @author Christopher Ng <chrng8@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\FilesReminders\Listener;
use OCA\Files\Event\LoadAdditionalScriptsEvent;
use OCA\FilesReminders\AppInfo\Application;
use OCP\App\IAppManager;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\Util;
class LoadAdditionalScriptsListener implements IEventListener {
public function __construct(
private IAppManager $appManager,
) {
}
public function handle(Event $event): void {
if (!($event instanceof LoadAdditionalScriptsEvent)) {
return;
}
if (!$this->appManager->isEnabledForUser('notifications')) {
return;
}
Util::addInitScript(Application::APP_ID, 'init');
}
}
@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
/**
* @copyright 2023 Christopher Ng <chrng8@gmail.com>
*
* @author Christopher Ng <chrng8@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\FilesReminders\Listener;
use OCA\FilesReminders\Service\ReminderService;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\Files\Events\Node\NodeDeletedEvent;
class NodeDeletedListener implements IEventListener {
public function __construct(
private ReminderService $reminderService,
) {
}
public function handle(Event $event): void {
if (!($event instanceof NodeDeletedEvent)) {
return;
}
$node = $event->getNode();
$this->reminderService->removeAllForNode($node);
}
}
@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
/**
* @copyright 2023 Christopher Ng <chrng8@gmail.com>
*
* @author Christopher Ng <chrng8@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\FilesReminders\Listener;
use OCA\FilesReminders\Service\ReminderService;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\User\Events\UserDeletedEvent;
class UserDeletedListener implements IEventListener {
public function __construct(
private ReminderService $reminderService,
) {
}
public function handle(Event $event): void {
if (!($event instanceof UserDeletedEvent)) {
return;
}
$user = $event->getUser();
$this->reminderService->removeAllForUser($user);
}
}
@@ -0,0 +1,82 @@
<?php
declare(strict_types=1);
/**
* @copyright 2023 Christopher Ng <chrng8@gmail.com>
*
* @author Christopher Ng <chrng8@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\FilesReminders\Migration;
use Closure;
use OCA\FilesReminders\Db\ReminderMapper;
use OCP\DB\ISchemaWrapper;
use OCP\DB\Types;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
class Version10000Date20230725162149 extends SimpleMigrationStep {
/**
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
*/
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
if ($schema->hasTable(ReminderMapper::TABLE_NAME)) {
return null;
}
$table = $schema->createTable(ReminderMapper::TABLE_NAME);
$table->addColumn('id', Types::BIGINT, [
'autoincrement' => true,
'notnull' => true,
'length' => 20,
'unsigned' => true,
]);
$table->addColumn('user_id', Types::STRING, [
'notnull' => true,
'length' => 64,
]);
$table->addColumn('file_id', Types::BIGINT, [
'notnull' => true,
'length' => 20,
'unsigned' => true,
]);
$table->addColumn('due_date', Types::DATETIME, [
'notnull' => true,
]);
$table->addColumn('updated_at', Types::DATETIME, [
'notnull' => true,
]);
$table->addColumn('created_at', Types::DATETIME, [
'notnull' => true,
]);
$table->addColumn('notified', Types::BOOLEAN, [
'notnull' => false,
'default' => false,
]);
$table->setPrimaryKey(['id']);
$table->addUniqueIndex(['user_id', 'file_id', 'due_date'], 'reminders_uniq_idx');
return $schema;
}
}
@@ -0,0 +1,75 @@
<?php
declare(strict_types=1);
/**
* @copyright 2023 Christopher Ng <chrng8@gmail.com>
*
* @author Christopher Ng <chrng8@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\FilesReminders\Model;
use DateTimeInterface;
use JsonSerializable;
use OCA\FilesReminders\Db\Reminder;
use OCA\FilesReminders\Exception\NodeNotFoundException;
use OCP\Files\IRootFolder;
use OCP\Files\Node;
class RichReminder extends Reminder implements JsonSerializable {
public function __construct(
private Reminder $reminder,
private IRootFolder $root,
) {
parent::__construct();
}
/**
* @throws NodeNotFoundException
*/
public function getNode(): Node {
$nodes = $this->root->getUserFolder($this->getUserId())->getById($this->getFileId());
if (empty($nodes)) {
throw new NodeNotFoundException();
}
$node = reset($nodes);
return $node;
}
protected function getter(string $name): mixed {
return $this->reminder->getter($name);
}
public function __call(string $methodName, array $args) {
return $this->reminder->__call($methodName, $args);
}
public function jsonSerialize(): array {
return [
'userId' => $this->getUserId(),
'fileId' => $this->getFileId(),
'path' => $this->getNode()->getPath(),
'dueDate' => $this->getDueDate()->format(DateTimeInterface::ATOM), // ISO 8601
'updatedAt' => $this->getUpdatedAt()->format(DateTimeInterface::ATOM), // ISO 8601
'createdAt' => $this->getCreatedAt()->format(DateTimeInterface::ATOM), // ISO 8601
'notified' => $this->getNotified(),
];
}
}
@@ -0,0 +1,130 @@
<?php
declare(strict_types=1);
/**
* @copyright 2023 Christopher Ng <chrng8@gmail.com>
*
* @author Christopher Ng <chrng8@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\FilesReminders\Notification;
use InvalidArgumentException;
use OCA\FilesReminders\AppInfo\Application;
use OCP\Files\FileInfo;
use OCP\Files\IRootFolder;
use OCP\IURLGenerator;
use OCP\L10N\IFactory;
use OCP\Notification\AlreadyProcessedException;
use OCP\Notification\IAction;
use OCP\Notification\INotification;
use OCP\Notification\INotifier;
class Notifier implements INotifier {
public function __construct(
protected IFactory $l10nFactory,
protected IURLGenerator $urlGenerator,
protected IRootFolder $root,
) {
}
public function getID(): string {
return Application::APP_ID;
}
public function getName(): string {
return $this->l10nFactory->get(Application::APP_ID)->t('File reminders');
}
/**
* @throws InvalidArgumentException
* @throws AlreadyProcessedException
*/
public function prepare(INotification $notification, string $languageCode): INotification {
$l = $this->l10nFactory->get(Application::APP_ID, $languageCode);
if ($notification->getApp() !== Application::APP_ID) {
throw new InvalidArgumentException();
}
switch ($notification->getSubject()) {
case 'reminder-due':
$params = $notification->getSubjectParameters();
$fileId = $params['fileId'];
$nodes = $this->root->getUserFolder($notification->getUser())->getById($fileId);
if (empty($nodes)) {
throw new InvalidArgumentException();
}
$node = reset($nodes);
$path = rtrim($node->getPath(), '/');
if (strpos($path, '/' . $notification->getUser() . '/files/') === 0) {
// Remove /user/files/...
$fullPath = $path;
[,,, $path] = explode('/', $fullPath, 4);
}
$link = $this->urlGenerator->linkToRouteAbsolute(
'files.viewcontroller.showFile',
['fileid' => $node->getId()],
);
// TRANSLATORS The name placeholder is for a file or folder name
$subject = $l->t('Reminder for {name}');
$notification
->setRichSubject(
$subject,
[
'name' => [
'type' => 'highlight',
'id' => $node->getId(),
'name' => $node->getName(),
],
],
)
->setLink($link);
$label = match ($node->getType()) {
FileInfo::TYPE_FILE => $l->t('View file'),
FileInfo::TYPE_FOLDER => $l->t('View folder'),
};
$this->addActionButton($notification, $label);
break;
default:
throw new InvalidArgumentException();
break;
}
return $notification;
}
protected function addActionButton(INotification $notification, string $label): void {
$action = $notification->createAction();
$action->setLabel($label)
->setParsedLabel($label)
->setLink($notification->getLink(), IAction::TYPE_WEB)
->setPrimary(true);
$notification->addParsedAction($action);
}
}
@@ -0,0 +1,181 @@
<?php
declare(strict_types=1);
/**
* @copyright 2023 Christopher Ng <chrng8@gmail.com>
*
* @author Christopher Ng <chrng8@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\FilesReminders\Service;
use DateTime;
use DateTimeZone;
use OCA\FilesReminders\AppInfo\Application;
use OCA\FilesReminders\Db\Reminder;
use OCA\FilesReminders\Db\ReminderMapper;
use OCA\FilesReminders\Exception\NodeNotFoundException;
use OCA\FilesReminders\Exception\UserNotFoundException;
use OCA\FilesReminders\Model\RichReminder;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\Files\IRootFolder;
use OCP\Files\Node;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\IUserManager;
use OCP\Notification\IManager as INotificationManager;
use Psr\Log\LoggerInterface;
use Throwable;
class ReminderService {
public function __construct(
protected IUserManager $userManager,
protected IURLGenerator $urlGenerator,
protected INotificationManager $notificationManager,
protected ReminderMapper $reminderMapper,
protected IRootFolder $root,
protected LoggerInterface $logger,
) {
}
/**
* @throws DoesNotExistException
*/
public function get(int $id): RichReminder {
$reminder = $this->reminderMapper->find($id);
return new RichReminder($reminder, $this->root);
}
/**
* @throws DoesNotExistException
*/
public function getDueForUser(IUser $user, int $fileId): RichReminder {
$reminder = $this->reminderMapper->findDueForUser($user, $fileId);
return new RichReminder($reminder, $this->root);
}
/**
* @return RichReminder[]
*/
public function getAll(?IUser $user = null) {
$reminders = ($user !== null)
? $this->reminderMapper->findAllForUser($user)
: $this->reminderMapper->findAll();
return array_map(
fn (Reminder $reminder) => new RichReminder($reminder, $this->root),
$reminders,
);
}
/**
* @return bool true if created, false if updated
*
* @throws NodeNotFoundException
*/
public function createOrUpdate(IUser $user, int $fileId, DateTime $dueDate): bool {
$now = new DateTime('now', new DateTimeZone('UTC'));
try {
$reminder = $this->reminderMapper->findDueForUser($user, $fileId);
$reminder->setDueDate($dueDate);
$reminder->setUpdatedAt($now);
$this->reminderMapper->update($reminder);
return false;
} catch (DoesNotExistException $e) {
$nodes = $this->root->getUserFolder($user->getUID())->getById($fileId);
if (empty($nodes)) {
throw new NodeNotFoundException();
}
// Create new reminder if no reminder is found
$reminder = new Reminder();
$reminder->setUserId($user->getUID());
$reminder->setFileId($fileId);
$reminder->setDueDate($dueDate);
$reminder->setUpdatedAt($now);
$reminder->setCreatedAt($now);
$this->reminderMapper->insert($reminder);
return true;
}
}
/**
* @throws DoesNotExistException
*/
public function remove(IUser $user, int $fileId): void {
$reminder = $this->reminderMapper->findDueForUser($user, $fileId);
$this->reminderMapper->delete($reminder);
}
public function removeAllForNode(Node $node): void {
$reminders = $this->reminderMapper->findAllForNode($node);
foreach ($reminders as $reminder) {
$this->reminderMapper->delete($reminder);
}
}
public function removeAllForUser(IUser $user): void {
$reminders = $this->reminderMapper->findAllForUser($user);
foreach ($reminders as $reminder) {
$this->reminderMapper->delete($reminder);
}
}
/**
* @throws DoesNotExistException
* @throws UserNotFoundException
*/
public function send(Reminder $reminder): void {
if ($reminder->getNotified()) {
return;
}
$user = $this->userManager->get($reminder->getUserId());
if ($user === null) {
throw new UserNotFoundException();
}
$notification = $this->notificationManager->createNotification();
$notification
->setApp(Application::APP_ID)
->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('files', 'folder.svg')))
->setUser($user->getUID())
->setObject('reminder', (string)$reminder->getId())
->setSubject('reminder-due', [
'fileId' => $reminder->getFileId(),
])
->setDateTime($reminder->getDueDate());
try {
$this->notificationManager->notify($notification);
$this->reminderMapper->markNotified($reminder);
} catch (Throwable $th) {
$this->logger->error($th->getMessage(), $th->getTrace());
}
}
public function cleanUp(?int $limit = null): void {
$buffer = (new DateTime())
->setTimezone(new DateTimeZone('UTC'))
->modify('-1 day');
$reminders = $this->reminderMapper->findNotified($buffer, $limit);
foreach ($reminders as $reminder) {
$this->reminderMapper->delete($reminder);
}
}
}