Update database tables for matrix data backend

and move API to /api/v1/ticket
This commit is contained in:
Ben 2021-09-18 19:28:10 +02:00
parent 5ccdcec520
commit 0a90ea80b8
Signed by: ben
GPG key ID: 0F54A7ED232D3319
21 changed files with 320 additions and 261 deletions

View file

@ -2,12 +2,15 @@
return [
'resources' => [
'note' => ['url' => '/notes'],
'note_api' => ['url' => '/api/0.1/notes']
'ticket_api' => ['url' => '/api/v1/ticket']
],
'routes' => [
['name' => 'page#index', 'url' => '/', 'verb' => 'GET'],
['name' => 'note_api#preflighted_cors', 'url' => '/api/0.1/{path}',
'verb' => 'OPTIONS', 'requirements' => ['path' => '.+']]
]
[
'name' => 'ticket_api#preflighted_cors',
'url' => '/api/v1/{path}',
'verb' => 'OPTIONS',
'requirements' => ['path' => '.+'],
],
],
];

View file

@ -15,7 +15,8 @@
}
],
"require": {
"aryess/php-matrix-sdk": "dev-feature/guzzle7-update"
"aryess/php-matrix-sdk": "dev-feature/guzzle7-update",
"ext-json": "*"
},
"require-dev": {
"phpunit/phpunit": "^8.5",

6
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "50de7fbcac58915f4bc01c0cf85e68ae",
"content-hash": "4f8aeff7f36c2b13932751ca0c8eb5cd",
"packages": [
{
"name": "aryess/php-matrix-sdk",
@ -4344,7 +4344,9 @@
},
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform": {
"ext-json": "*"
},
"platform-dev": [],
"platform-overrides": {
"php": "7.2.5"

View file

@ -3,12 +3,10 @@
namespace OCA\UPschooling\Controller;
use Closure;
use OCA\UPschooling\Service\NoteNotFound;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCA\UPschooling\Service\NoteNotFound;
trait Errors {
protected function handleNotFound(Closure $callback): DataResponse {
try {

View file

@ -1,77 +0,0 @@
<?php
namespace OCA\UPschooling\Controller;
use OCA\UPschooling\AppInfo\Application;
use OCA\UPschooling\Service\MatrixService;
use OCA\UPschooling\Service\TicketService;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\DataResponse;
use OCP\IRequest;
class TicketController extends Controller {
/** @var TicketService */
private $service;
/** @var MatrixService */
private $matrix;
/** @var string */
private $userId;
use Errors;
public function __construct(IRequest $request,
TicketService $service,
MatrixService $matrix,
$userId) {
parent::__construct(Application::APP_ID, $request);
$this->service = $service;
$this->matrix = $matrix;
$this->userId = $userId;
}
/**
* @NoAdminRequired
*/
public function index(): DataResponse {
return new DataResponse($this->service->findAll($this->userId));
}
/**
* @NoAdminRequired
*/
public function show(int $id): DataResponse {
return $this->handleNotFound(function () use ($id) {
return $this->service->find($id, $this->userId);
});
}
/**
* @NoAdminRequired
*/
public function create(string $title, string $content): DataResponse {
return new DataResponse($this->service->create($title, $content,
$this->userId));
}
/**
* @NoAdminRequired
*/
public function update(int $id, string $title,
string $content): DataResponse {
return $this->handleNotFound(function () use ($id, $title, $content) {
return $this->service->update($id, $title, $content, $this->userId);
});
}
/**
* @NoAdminRequired
*/
public function destroy(int $id): DataResponse {
return $this->handleNotFound(function () use ($id) {
return $this->service->delete($id, $this->userId);
});
}
}

27
lib/Db/MatrixTicket.php Normal file
View file

@ -0,0 +1,27 @@
<?php
namespace OCA\UPschooling\Db;
use JsonSerializable;
use OCP\AppFramework\Db\Entity;
class MatrixTicket extends Entity implements JsonSerializable
{
protected $matrixRoom;
protected $matrixAssistedUser; // The ID of the person who created the Ticked. Usually the person who needs help.
protected $matrixHelperUser;
protected $status;
protected $version;
public function jsonSerialize(): array
{
return [
'id' => $this->id,
'matrixRoom' => $this->matrixRoom,
'matrixAssistedUser' => $this->matrixAssistedUser,
'matrixHelperUser' => $this->matrixHelperUser,
'status' => $this->status,
'version' => $this->version,
];
}
}

19
lib/Db/MatrixUser.php Normal file
View file

@ -0,0 +1,19 @@
<?php
namespace OCA\UPschooling\Db;
use JsonSerializable;
use OCP\AppFramework\Db\Entity;
class MatrixUser extends Entity implements JsonSerializable {
protected $userId;
protected $matrixUser;
public function jsonSerialize(): array {
return [
'id' => $this->id,
'userId' => $this->userId,
'matrixUser' => $this->matrixUser,
];
}
}

View file

@ -1,27 +0,0 @@
<?php
namespace OCA\UPschooling\Db;
use JsonSerializable;
use OCP\AppFramework\Db\Entity;
class Ticket extends Entity implements JsonSerializable {
protected $title;
protected $description;
protected $helperId;
protected $creatorId; // The ID of the person who created the Ticked. Usually the person who needs help.
protected $dueDate;
protected $status;
public function jsonSerialize(): array {
return [
'id' => $this->id,
'title' => $this->title,
'description' => $this->description,
'helperId' => $this->helperId,
'dueDate' => $this->dueDate,
'status' => $this->status,
];
}
}

View file

@ -8,25 +8,34 @@ use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
class TicketMapper extends QBMapper {
public function __construct(IDBConnection $db) {
parent::__construct($db, 'upschooling', Ticket::class);
class TicketMapper extends QBMapper
{
public function __construct(IDBConnection $db)
{
parent::__construct($db, 'upschooling_tickets', MatrixTicket::class);
}
/**
* @param int $id
* @param string $userId
* @return Entity|Ticket
* @return Entity|MatrixTicket
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws DoesNotExistException
*/
public function find(int $id, string $userId): Ticket {
public function find(int $id, string $userId): MatrixTicket
{
/* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from('upschooling')
->from('upschooling_tickets', 't')
->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('user_id', $qb->createNamedParameter($userId)));
->innerJoin(
't',
'upschooling_users',
'u',
$qb->expr()->in('u.matrix_user', ['t.matrix_assisted_user', 't.matrix_helper_user'])
)
->andWhere($qb->expr()->eq('u.user_id', $qb->createNamedParameter($userId)));
return $this->findEntity($qb);
}
@ -34,12 +43,19 @@ class TicketMapper extends QBMapper {
* @param string $userId
* @return array
*/
public function findAll(string $userId): array {
public function findAll(string $userId): array
{
/* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from('upschooling')
->where($qb->expr()->eq('user_id', $qb->createNamedParameter($userId)));
->from('upschooling_users', 'u')
->where($qb->expr()->eq('u.user_id', $qb->createNamedParameter($userId)))
->innerJoin(
'u',
'upschooling_tickets',
't',
$qb->expr()->in('u.matrix_user', ['t.matrix_assisted_user', 't.matrix_helper_user'])
);
return $this->findEntities($qb);
}
}

31
lib/Db/UserMapper.php Normal file
View file

@ -0,0 +1,31 @@
<?php
namespace OCA\UPschooling\Db;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\Entity;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
class UserMapper extends QBMapper {
public function __construct(IDBConnection $db) {
parent::__construct($db, 'upschooling_users', MatrixUser::class);
}
/**
* @param string $userId
* @return Entity|MatrixUser
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws DoesNotExistException
* @throws \OCP\DB\Exception
*/
public function find(string $userId): MatrixUser {
/* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from('upschooling_users')
->where($qb->expr()->eq('user_id', $qb->createNamedParameter($userId)));
return $this->findEntity($qb);
}
}

View file

@ -1,48 +0,0 @@
<?php
declare(strict_types=1);
namespace OCA\UPschooling\Migration;
use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\Migration\SimpleMigrationStep;
use OCP\Migration\IOutput;
class Version000000Date20181013124731 extends SimpleMigrationStep {
/**
* @param IOutput $output
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
* @param array $options
* @return null|ISchemaWrapper
*/
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
if (!$schema->hasTable('upschooling')) {
$table = $schema->createTable('upschooling');
$table->addColumn('id', 'integer', [
'autoincrement' => true,
'notnull' => true,
]);
$table->addColumn('title', 'string', [
'notnull' => true,
'length' => 200
]);
$table->addColumn('user_id', 'string', [
'notnull' => true,
'length' => 200,
]);
$table->addColumn('content', 'text', [
'notnull' => true,
'default' => ''
]);
$table->setPrimaryKey(['id']);
$table->addIndex(['user_id'], 'upschooling_user_id_index');
}
return $schema;
}
}

View file

@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
namespace OCA\UPschooling\Migration;
use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
class Version000000Date20210918151800 extends SimpleMigrationStep {
/**
* @param IOutput $output
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
* @param array $options
* @return null|ISchemaWrapper
*/
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
if (!$schema->hasTable('upschooling_tickets')) {
$table = $schema->createTable('upschooling_tickets');
$table->addColumn('id', 'integer', [
'autoincrement' => true,
'notnull' => true,
]);
$table->addColumn('matrix_room', 'string', [
'notnull' => true,
'length' => 63,
]);
$table->addColumn('matrix_assisted_user', 'string', [
'notnull' => true,
'length' => 63,
]);
$table->addColumn('matrix_helper_user', 'string', [
'notnull' => false,
'length' => 63,
]);
// could be optimized by having a "localUser" with foreign key to users.id,
// but that would create consistency issues
$table->addColumn('status', 'string', [
'notnull' => true,
'length' => 100,
]);
$table->addColumn('version', 'integer', [
'notnull' => true,
]);
$table->setPrimaryKey(['id']);
$table->addUniqueConstraint(['matrix_room'], 'upschooling_mx_room_id_uniq');
$table->addIndex(['matrix_room'], 'upschooling_mx_room_id_idx');
}
if (!$schema->hasTable('upschooling_users')) {
$table = $schema->createTable('upschooling_users');
$table->addColumn('id', 'integer', [
'autoincrement' => true,
'notnull' => true,
]);
$table->addColumn('user_id', 'string', [
'notnull' => true,
'length' => 100,
]);
$table->addColumn('matrix_user', 'string', [
'notnull' => true,
'length' => 63,
]);
$table->setPrimaryKey(['id']);
$table->addUniqueConstraint(['user_id'], 'upschooling_mx_user_nc_uniq');
$table->addUniqueConstraint(['matrix_user'], 'upschooling_mx_user_mx_uniq');
$table->addForeignKeyConstraint('users', ['user_id'], ['id'], [], 'upschooling_mx_user_nc_fk');
}
return $schema;
}
}

View file

@ -2,12 +2,11 @@
namespace OCA\UPschooling\Service;
use Aryess\PhpMatrixSdk\Exceptions\MatrixRequestException;
use \Psr\Log\LoggerInterface;
use Aryess\PhpMatrixSdk\Exceptions\MatrixException;
use Aryess\PhpMatrixSdk\Exceptions\MatrixRequestException;
use Aryess\PhpMatrixSdk\MatrixClient;
use Aryess\PhpMatrixSdk\Room;
use Psr\Log\LoggerInterface;
class MatrixService
{

View file

@ -3,19 +3,20 @@
namespace OCA\UPschooling\Service;
use Exception;
use OCA\UPschooling\Db\MatrixTicket;
use OCA\UPschooling\Db\TicketMapper;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCA\UPschooling\Db\Ticket;
use OCA\UPschooling\Db\TicketMapper;
class TicketService {
/** @var TicketMapper */
private $mapper;
public function __construct(TicketMapper $mapper) {
private $matrix;
public function __construct(MatrixService $matrix, TicketMapper $mapper) {
$this->matrix = $matrix;
$this->mapper = $mapper;
}
@ -46,7 +47,7 @@ class TicketService {
}
public function create($title, $content, $userId) {
$note = new Ticket();
$note = new MatrixTicket();
$note->setTitle($title);
$note->setContent($content);
$note->setUserId($userId);

View file

@ -2,16 +2,15 @@
namespace OCA\UPschooling\Tests\Integration\Controller;
use OCA\UPschooling\Controller\TicketApiController;
use OCA\UPschooling\Db\MatrixTicket;
use OCA\UPschooling\Db\TicketMapper;
use OCP\AppFramework\App;
use OCP\IRequest;
use PHPUnit\Framework\TestCase;
use OCA\UPschooling\Db\Ticket;
use OCA\UPschooling\Db\TicketMapper;
use OCA\UPschooling\Controller\TicketController;
class NoteIntegrationTest extends TestCase {
class MatrixTicketIntegrationTest extends TestCase {
private $controller;
private $mapper;
private $userId = 'john';
@ -30,13 +29,13 @@ class NoteIntegrationTest extends TestCase {
return $this->createMock(IRequest::class);
});
$this->controller = $container->query(TicketController::class);
$this->controller = $container->query(TicketApiController::class);
$this->mapper = $container->query(TicketMapper::class);
}
public function testUpdate() {
// create a new note that should be updated
$note = new Ticket();
$note = new MatrixTicket();
$note->setTitle('old_title');
$note->setContent('old_content');
$note->setUserId($this->userId);
@ -44,7 +43,7 @@ class NoteIntegrationTest extends TestCase {
$id = $this->mapper->insert($note)->getId();
// fromRow does not set the fields as updated
$updatedNote = Ticket::fromRow([
$updatedNote = MatrixTicket::fromRow([
'id' => $id,
'user_id' => $this->userId
]);

View file

@ -0,0 +1,49 @@
<?php
namespace OCA\UPschooling\Tests\Integration\Controller;
use OCA\UPschooling\Db\MatrixUser;
use OCA\UPschooling\Db\UserMapper;
use OCP\AppFramework\App;
use OCP\IRequest;
use PHPUnit\Framework\TestCase;
class MatrixUserIntegrationTest extends TestCase {
private $mapper;
private $userId = 'john';
private $matrixUserId = '@john:synapse';
public function setUp(): void {
$app = new App('upschooling');
$container = $app->getContainer();
// only replace the user id
$container->registerService('userId', function () {
return $this->userId;
});
// we do not care about the request but the controller needs it
$container->registerService(IRequest::class, function () {
return $this->createMock(IRequest::class);
});
$this->mapper = $container->query(UserMapper::class);
}
public function testUpdate() {
// create a new user
$user = new MatrixUser();
$user->setMatrixUser($this->matrixUserId);
$user->setUserId($this->userId);
$this->mapper->insert($user);
// test that user is in database
$result = $this->mapper->find($this->userId);
$this->assertEquals($user, $result->getData());
// clean up
$this->mapper->delete($result->getData());
}
}

View file

@ -3,10 +3,54 @@
namespace OCA\UPschooling\Tests\Unit\Controller;
use OCA\UPschooling\Controller\TicketApiController;
use OCA\UPschooling\Service\NoteNotFound;
use OCA\UPschooling\Service\TicketService;
use OCP\AppFramework\Http;
use OCP\IRequest;
use PHPUnit\Framework\TestCase;
class NoteApiControllerTest extends NoteControllerTest {
public function setUp(): void {
parent::setUp();
class NoteApiControllerTest extends TestCase
{
protected $controller;
protected $service;
protected $userId = 'john';
protected $request;
public function setUp(): void
{
$this->request = $this->getMockBuilder(IRequest::class)->getMock();
$this->service = $this->getMockBuilder(TicketService::class)
->disableOriginalConstructor()
->getMock();
$this->controller = new TicketApiController($this->request, $this->service, $this->userId);
}
public function testUpdate()
{
$note = 'just check if this value is returned correctly';
$this->service->expects($this->once())
->method('update')
->with($this->equalTo(3),
$this->equalTo('title'),
$this->equalTo('content'),
$this->equalTo($this->userId))
->will($this->returnValue($note));
$result = $this->controller->update(3, 'title', 'content');
$this->assertEquals($note, $result->getData());
}
public function testUpdateNotFound()
{
// test the correct status code if no note is found
$this->service->expects($this->once())
->method('update')
->will($this->throwException(new NoteNotFound()));
$result = $this->controller->update(3, 'title', 'content');
$this->assertEquals(Http::STATUS_NOT_FOUND, $result->getStatus());
}
}

View file

@ -1,54 +0,0 @@
<?php
namespace OCA\UPschooling\Tests\Unit\Controller;
use PHPUnit\Framework\TestCase;
use OCP\AppFramework\Http;
use OCP\IRequest;
use OCA\UPschooling\Service\NoteNotFound;
use OCA\UPschooling\Service\TicketService;
use OCA\UPschooling\Controller\TicketController;
class NoteControllerTest extends TestCase {
protected $controller;
protected $service;
protected $userId = 'john';
protected $request;
public function setUp(): void {
$this->request = $this->getMockBuilder(IRequest::class)->getMock();
$this->service = $this->getMockBuilder(TicketService::class)
->disableOriginalConstructor()
->getMock();
$this->controller = new TicketController($this->request, $this->service, $this->userId);
}
public function testUpdate() {
$note = 'just check if this value is returned correctly';
$this->service->expects($this->once())
->method('update')
->with($this->equalTo(3),
$this->equalTo('title'),
$this->equalTo('content'),
$this->equalTo($this->userId))
->will($this->returnValue($note));
$result = $this->controller->update(3, 'title', 'content');
$this->assertEquals($note, $result->getData());
}
public function testUpdateNotFound() {
// test the correct status code if no note is found
$this->service->expects($this->once())
->method('update')
->will($this->throwException(new NoteNotFound()));
$result = $this->controller->update(3, 'title', 'content');
$this->assertEquals(Http::STATUS_NOT_FOUND, $result->getStatus());
}
}

View file

@ -2,9 +2,8 @@
namespace OCA\UPschooling\Controller;
use PHPUnit\Framework\TestCase;
use OCP\AppFramework\Http\TemplateResponse;
use PHPUnit\Framework\TestCase;
class PageControllerTest extends TestCase {
private $controller;
@ -14,7 +13,6 @@ class PageControllerTest extends TestCase {
$this->controller = new PageController($request);
}
public function testIndex() {
$result = $this->controller->index();

View file

@ -1,15 +1,13 @@
<?php
namespace OCA\UPschooling\Tests\Unit\Service;
namespace Unit\Service;
use OCA\UPschooling\Service\NoteNotFound;
use PHPUnit\Framework\TestCase;
use OCP\AppFramework\Db\DoesNotExistException;
use OCA\UPschooling\Db\Ticket;
use OCA\UPschooling\Service\TicketService;
use OCA\UPschooling\Db\MatrixTicket;
use OCA\UPschooling\Db\TicketMapper;
use OCA\UPschooling\Service\NoteNotFound;
use OCA\UPschooling\Service\TicketService;
use OCP\AppFramework\Db\DoesNotExistException;
use PHPUnit\Framework\TestCase;
class NoteServiceTest extends TestCase {
private $service;
@ -25,7 +23,7 @@ class NoteServiceTest extends TestCase {
public function testUpdate() {
// the existing note
$note = Ticket::fromRow([
$note = MatrixTicket::fromRow([
'id' => 3,
'title' => 'yo',
'content' => 'nope'
@ -36,7 +34,7 @@ class NoteServiceTest extends TestCase {
->will($this->returnValue($note));
// the note when updated
$updatedNote = Ticket::fromRow(['id' => 3]);
$updatedNote = MatrixTicket::fromRow(['id' => 3]);
$updatedNote->setTitle('title');
$updatedNote->setContent('content');
$this->mapper->expects($this->once())

View file

@ -1,3 +1,3 @@
<?php
require_once __DIR__ . '/../../../tests/bootstrap.php';
require_once __DIR__ . '/../tests/bootstrap.php';