Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,7 @@
'textBlockShares' => ['url' => '/api/textBlockshares'],
'quickActions' => ['url' => '/api/quick-actions'],
'actionStep' => ['url' => '/api/action-step'],
'messageTemplate' => ['url' => 'api/message-template']
],
'ocs' => [
[
Expand Down
94 changes: 94 additions & 0 deletions lib/Controller/MessageTemplateController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Mail\Controller;

use OCA\Mail\Exception\ClientException;
use OCA\Mail\Http\TrapError;
use OCA\Mail\Service\MessageTemplateService;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\OpenAPI;
use OCP\AppFramework\Http\JSONResponse;
use OCP\IRequest;

#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
class MessageTemplateController extends Controller {
private ?string $currentUserId;

public function __construct(
string $appName,
IRequest $request,
private MessageTemplateService $messageTemplateService,
?string $userId,
) {
parent::__construct($appName, $request);
$this->currentUserId = $userId;
}

/**
* @NoAdminRequired
*/
#[TrapError]
public function index(): JSONResponse {
$this->checkUser();
$messageTemplates = $this->messageTemplateService->findMessageTemplates($this->currentUserId);

Check failure on line 42 in lib/Controller/MessageTemplateController.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-stable34

PossiblyNullArgument

lib/Controller/MessageTemplateController.php:42:75: PossiblyNullArgument: Argument 1 of OCA\Mail\Service\MessageTemplateService::findMessageTemplates cannot be null, possibly null value provided (see https://psalm.dev/078)

Check failure on line 42 in lib/Controller/MessageTemplateController.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-stable33

PossiblyNullArgument

lib/Controller/MessageTemplateController.php:42:75: PossiblyNullArgument: Argument 1 of OCA\Mail\Service\MessageTemplateService::findMessageTemplates cannot be null, possibly null value provided (see https://psalm.dev/078)

Check failure on line 42 in lib/Controller/MessageTemplateController.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-stable32

PossiblyNullArgument

lib/Controller/MessageTemplateController.php:42:75: PossiblyNullArgument: Argument 1 of OCA\Mail\Service\MessageTemplateService::findMessageTemplates cannot be null, possibly null value provided (see https://psalm.dev/078)

Check failure on line 42 in lib/Controller/MessageTemplateController.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-master

PossiblyNullArgument

lib/Controller/MessageTemplateController.php:42:75: PossiblyNullArgument: Argument 1 of OCA\Mail\Service\MessageTemplateService::findMessageTemplates cannot be null, possibly null value provided (see https://psalm.dev/078)
return new JSONResponse($messageTemplates, Http::STATUS_OK);
}

/**
* @NoAdminRequired
* @throws ClientException
*/
#[TrapError]
public function create(string $title, string $body): JSONResponse {
$this->checkUser();
$messageTemplate = $this->messageTemplateService->createMessageTemplate($this->currentUserId, $title, $body);

Check failure on line 53 in lib/Controller/MessageTemplateController.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-stable34

PossiblyNullArgument

lib/Controller/MessageTemplateController.php:53:75: PossiblyNullArgument: Argument 1 of OCA\Mail\Service\MessageTemplateService::createMessageTemplate cannot be null, possibly null value provided (see https://psalm.dev/078)

Check failure on line 53 in lib/Controller/MessageTemplateController.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-stable33

PossiblyNullArgument

lib/Controller/MessageTemplateController.php:53:75: PossiblyNullArgument: Argument 1 of OCA\Mail\Service\MessageTemplateService::createMessageTemplate cannot be null, possibly null value provided (see https://psalm.dev/078)

Check failure on line 53 in lib/Controller/MessageTemplateController.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-stable32

PossiblyNullArgument

lib/Controller/MessageTemplateController.php:53:75: PossiblyNullArgument: Argument 1 of OCA\Mail\Service\MessageTemplateService::createMessageTemplate cannot be null, possibly null value provided (see https://psalm.dev/078)

Check failure on line 53 in lib/Controller/MessageTemplateController.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-master

PossiblyNullArgument

lib/Controller/MessageTemplateController.php:53:75: PossiblyNullArgument: Argument 1 of OCA\Mail\Service\MessageTemplateService::createMessageTemplate cannot be null, possibly null value provided (see https://psalm.dev/078)
return new JSONResponse($messageTemplate, Http::STATUS_CREATED);
}

/**
* @NoAdminRequired
* @throws ClientException
*/
#[TrapError]
public function update(int $id, string $title, string $body): JSONResponse {
$this->checkUser();

try {
$messageTemplate = $this->messageTemplateService->updateMessageTemplate($this->currentUserId, $id, $title, $body);

Check failure on line 66 in lib/Controller/MessageTemplateController.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-stable34

PossiblyNullArgument

lib/Controller/MessageTemplateController.php:66:76: PossiblyNullArgument: Argument 1 of OCA\Mail\Service\MessageTemplateService::updateMessageTemplate cannot be null, possibly null value provided (see https://psalm.dev/078)

Check failure on line 66 in lib/Controller/MessageTemplateController.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-stable33

PossiblyNullArgument

lib/Controller/MessageTemplateController.php:66:76: PossiblyNullArgument: Argument 1 of OCA\Mail\Service\MessageTemplateService::updateMessageTemplate cannot be null, possibly null value provided (see https://psalm.dev/078)

Check failure on line 66 in lib/Controller/MessageTemplateController.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-stable32

PossiblyNullArgument

lib/Controller/MessageTemplateController.php:66:76: PossiblyNullArgument: Argument 1 of OCA\Mail\Service\MessageTemplateService::updateMessageTemplate cannot be null, possibly null value provided (see https://psalm.dev/078)

Check failure on line 66 in lib/Controller/MessageTemplateController.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-master

PossiblyNullArgument

lib/Controller/MessageTemplateController.php:66:76: PossiblyNullArgument: Argument 1 of OCA\Mail\Service\MessageTemplateService::updateMessageTemplate cannot be null, possibly null value provided (see https://psalm.dev/078)
} catch (DoesNotExistException $e) {
return new JSONResponse(['message' => 'Message template not found'], Http::STATUS_NOT_FOUND);
}

return new JSONResponse($messageTemplate, Http::STATUS_OK);
}

/**
* @NoAdminRequired
*/
#[TrapError]
public function destroy(int $id): JSONResponse {
$this->checkUser();

try {
$this->messageTemplateService->deleteMessageTemplate($this->currentUserId, $id);

Check failure on line 82 in lib/Controller/MessageTemplateController.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-stable34

PossiblyNullArgument

lib/Controller/MessageTemplateController.php:82:57: PossiblyNullArgument: Argument 1 of OCA\Mail\Service\MessageTemplateService::deleteMessageTemplate cannot be null, possibly null value provided (see https://psalm.dev/078)

Check failure on line 82 in lib/Controller/MessageTemplateController.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-stable33

PossiblyNullArgument

lib/Controller/MessageTemplateController.php:82:57: PossiblyNullArgument: Argument 1 of OCA\Mail\Service\MessageTemplateService::deleteMessageTemplate cannot be null, possibly null value provided (see https://psalm.dev/078)

Check failure on line 82 in lib/Controller/MessageTemplateController.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-stable32

PossiblyNullArgument

lib/Controller/MessageTemplateController.php:82:57: PossiblyNullArgument: Argument 1 of OCA\Mail\Service\MessageTemplateService::deleteMessageTemplate cannot be null, possibly null value provided (see https://psalm.dev/078)

Check failure on line 82 in lib/Controller/MessageTemplateController.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-master

PossiblyNullArgument

lib/Controller/MessageTemplateController.php:82:57: PossiblyNullArgument: Argument 1 of OCA\Mail\Service\MessageTemplateService::deleteMessageTemplate cannot be null, possibly null value provided (see https://psalm.dev/078)
return new JSONResponse([], Http::STATUS_OK);
} catch (DoesNotExistException $e) {
return new JSONResponse(['message' => 'Message template not found'], Http::STATUS_NOT_FOUND);
}
}

private function checkUser(): void {
if ($this->currentUserId === null) {
throw new ClientException('No user specified');
}
}
}
48 changes: 48 additions & 0 deletions lib/Db/MessageTemplate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Mail\Db;

use JsonSerializable;
use OCP\AppFramework\Db\Entity;
use ReturnTypeWillChange;

/**
* @method string getUserId()
* @method void setUserId(string $userId)
* @method string getTitle()
* @method void setTitle(string $title)
* @method string getBody()
* @method void setBody(string $body)
*/
class MessageTemplate extends Entity implements JsonSerializable {
/** @var string */
protected $userId;
/** @var string */
protected $title;
/** @var string */
protected $body;

public function __construct() {
$this->addType('userId', 'string');
$this->addType('title', 'string');
$this->addType('body', 'string');
}

#[\Override]
#[ReturnTypeWillChange]
public function jsonSerialize() {
return [
'id' => $this->getId(),
'userId' => $this->getUserId(),
'title' => $this->getTitle(),
'body' => $this->getBody()
];
}
}
82 changes: 82 additions & 0 deletions lib/Db/MessageTemplateMapper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Mail\Db;

use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\Exception;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;

/**
* @template-extends QBMapper<MessageTemplate>
*/
class MessageTemplateMapper extends QBMapper {

public function __construct(IDBConnection $db) {
parent::__construct($db, 'mail_message_templates');
}

/**
* @throws DoesNotExistException
*/
public function find(int $id): MessageTemplate {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
return $this->findEntity($qb);
}

/**
* @throws Exception
*/
public function findAll(string $userId): array {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->where($qb->expr()->eq('user_id', $qb->createNamedParameter($userId)));

return $this->findEntities($qb);
}

/**
* @throws Exception
*/
public function saveTemplate(string $userId, string $title, string $body): MessageTemplate {
$template = new MessageTemplate();
$template->setUserId($userId);
$template->setTitle($title);
$template->setBody($body);

return $this->insert($template);
}

/**
* @throws DoesNotExistException
*/
public function updateTemplate(int $id, $title, string $body): MessageTemplate {
$messageTemplate = $this->find($id);
$messageTemplate->setTitle($title);
$messageTemplate->setBody($body);

return $this->update($messageTemplate);
}

/**
* @throws DoesNotExistException
*/
public function deleteTemplate(int $id): MessageTemplate {
$messageTemplate = $this->find($id);
return $this->delete($messageTemplate);
}

}
57 changes: 57 additions & 0 deletions lib/Migration/Version5201Date20260707120000.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Mail\Migration;

use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\DB\Types;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
use Override;

class Version5201Date20260707120000 extends SimpleMigrationStep {
/**
* @param IOutput $output
* @param Closure(): ISchemaWrapper $schemaClosure
* @param array $options
* @return null|ISchemaWrapper
*/
#[Override]
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
$schema = $schemaClosure();

if (!$schema->hasTable('mail_message_templates')) {
$table = $schema->createTable('mail_message_templates');
$table->addColumn('id', Types::INTEGER, [
'autoincrement' => true,
'notnull' => true,
]);

$table->addColumn('user_id', Types::STRING, [
'notnull' => true,
'length' => 64,
]);
$table->addColumn('title', Types::STRING, [
'notnull' => true,
'length' => 255,
]);

$table->addColumn('body', Types::TEXT, [
'notnull' => false,
]);

$table->setPrimaryKey(['id']);
$table->addIndex(['user_id'], 'mail_tmpl_user_idx');
}

return $schema;
}

}
81 changes: 81 additions & 0 deletions lib/Service/MessageTemplateService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

declare(strict_types=1);

namespace OCA\Mail\Service;

use OCA\Mail\Db\MessageTemplate;
use OCA\Mail\Db\MessageTemplateMapper;
use OCA\Mail\Exception\ClientException;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\DB\Exception;

class MessageTemplateService {
public function __construct(
private MessageTemplateMapper $messageTemplateMapper,
) {
}

/**
* @throws Exception
*/
public function findMessageTemplates(string $userId): array {
return $this->messageTemplateMapper->findAll($userId);
}

/**
* @throws ClientException
*/
public function createMessageTemplate(string $userId, string $title, string $body): MessageTemplate {
$this->validateFields($title, $body);
try {
return $this->messageTemplateMapper->saveTemplate($userId, $title, $body);
} catch (Exception $e) {
throw new ClientException('Failed to save message template');
}
}

/**
* @throws DoesNotExistException
* @throws ClientException
*/
public function updateMessageTemplate(string $userId, int $id, string $title, string $body): MessageTemplate {
$messageTemplate = $this->messageTemplateMapper->find($id);
if ($userId !== $messageTemplate->getUserId()) {
throw new ClientException('Message template does not belong to this user');
}

try {
return $this->messageTemplateMapper->updateTemplate($id, $title, $body);
} catch (DoesNotExistException $e) {
throw new ClientException('Message template does not exist');
}
}

/**
* @throws DoesNotExistException
* @throws ClientException
*/
public function deleteMessageTemplate(string $userId, int $id): MessageTemplate {
$messageTemplate = $this->messageTemplateMapper->find($id);
if ($userId !== $messageTemplate->getUserId()) {
throw new ClientException('Message template does not belong to this user');
}

try {
return $this->messageTemplateMapper->deleteTemplate($id);
} catch (DoesNotExistException $e) {
throw new ClientException('Message template does not exist');
}
}

private function validateFields(string $title, string $body): void {
if (trim($title) === '') {
throw new ClientException('Template title can not be empty');
}

if (trim($body) === '') {
throw new ClientException('Template body can not be empty');
}
}
}
Loading