Implement Matrix as data backend
This commit is contained in:
parent
1f14932b28
commit
01212543fe
8
composer.lock
generated
8
composer.lock
generated
|
@ -12,12 +12,12 @@
|
|||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/bziemons/matrix-php-sdk.git",
|
||||
"reference": "54aa76a82237b1abea6b6d7157b03b39d207846c"
|
||||
"reference": "f0d759e56bf7013c1b0d9936acd1406ebcfcecb9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/bziemons/matrix-php-sdk/zipball/54aa76a82237b1abea6b6d7157b03b39d207846c",
|
||||
"reference": "54aa76a82237b1abea6b6d7157b03b39d207846c",
|
||||
"url": "https://api.github.com/repos/bziemons/matrix-php-sdk/zipball/f0d759e56bf7013c1b0d9936acd1406ebcfcecb9",
|
||||
"reference": "f0d759e56bf7013c1b0d9936acd1406ebcfcecb9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -73,7 +73,7 @@
|
|||
"support": {
|
||||
"source": "https://github.com/bziemons/matrix-php-sdk/tree/feature/guzzle7-update"
|
||||
},
|
||||
"time": "2021-09-18T10:11:20+00:00"
|
||||
"time": "2021-09-19T12:54:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/guzzle",
|
||||
|
|
|
@ -45,8 +45,7 @@ class TicketApiController extends ApiController
|
|||
|
||||
public function create(string $title, string $content): DataResponse
|
||||
{
|
||||
return new DataResponse($this->service->create($title, $content,
|
||||
$this->userId));
|
||||
return new DataResponse($this->service->create($title, $content, $this->userId));
|
||||
}
|
||||
|
||||
public function update(int $id, string $title, string $content): DataResponse
|
||||
|
|
|
@ -8,12 +8,14 @@ use OCP\AppFramework\Db\Entity;
|
|||
class MatrixUser extends Entity implements JsonSerializable {
|
||||
protected $userId;
|
||||
protected $matrixUser;
|
||||
protected $matrixToken;
|
||||
|
||||
public function jsonSerialize(): array {
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'userId' => $this->userId,
|
||||
'matrixUser' => $this->matrixUser,
|
||||
'matrixToken' => $this->matrixToken,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,20 +22,14 @@ class TicketMapper extends QBMapper
|
|||
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
|
||||
* @throws DoesNotExistException
|
||||
*/
|
||||
public function find(int $id, string $userId): MatrixTicket
|
||||
public function find(int $id, MatrixUser $matrixUser): MatrixTicket
|
||||
{
|
||||
/* @var $qb IQueryBuilder */
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
->from('upschooling_tickets', 't')
|
||||
->from('upschooling_tickets')
|
||||
->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)))
|
||||
->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)));
|
||||
->andWhere($qb->expr()->in($matrixUser->getMatrixUser(), ['matrix_assisted_user', 'matrix_helper_user']));
|
||||
return $this->findEntity($qb);
|
||||
}
|
||||
|
||||
|
@ -43,19 +37,13 @@ class TicketMapper extends QBMapper
|
|||
* @param string $userId
|
||||
* @return array
|
||||
*/
|
||||
public function findAll(string $userId): array
|
||||
public function findAll(MatrixUser $matrixUser): array
|
||||
{
|
||||
/* @var $qb IQueryBuilder */
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
->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'])
|
||||
);
|
||||
->from('upschooling_tickets')
|
||||
->where($qb->expr()->in($matrixUser->getMatrixUser(), ['matrix_assisted_user', 'matrix_helper_user']));
|
||||
return $this->findEntities($qb);
|
||||
}
|
||||
}
|
||||
|
|
13
lib/Exceptions/RoomNotFoundException.php
Normal file
13
lib/Exceptions/RoomNotFoundException.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace OCA\UPschooling\Exceptions;
|
||||
|
||||
use OC\OCS\Exception;
|
||||
use OC\OCS\Result;
|
||||
|
||||
class RoomNotFoundException extends Exception {
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(new Result(null, 404, 'Room not found'));
|
||||
}
|
||||
}
|
|
@ -29,22 +29,22 @@ class Version000000Date20210918151800 extends SimpleMigrationStep {
|
|||
]);
|
||||
$table->addColumn('matrix_room', 'string', [
|
||||
'notnull' => true,
|
||||
'length' => 63,
|
||||
'length' => 200,
|
||||
]);
|
||||
$table->addColumn('matrix_assisted_user', 'string', [
|
||||
'notnull' => true,
|
||||
'length' => 63,
|
||||
'length' => 200,
|
||||
]);
|
||||
$table->addColumn('matrix_helper_user', 'string', [
|
||||
'notnull' => false,
|
||||
'length' => 63,
|
||||
'length' => 200,
|
||||
]);
|
||||
// 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,
|
||||
'length' => 63,
|
||||
]);
|
||||
$table->addColumn('version', 'integer', [
|
||||
'notnull' => true,
|
||||
|
@ -63,11 +63,15 @@ class Version000000Date20210918151800 extends SimpleMigrationStep {
|
|||
]);
|
||||
$table->addColumn('user_id', 'string', [
|
||||
'notnull' => true,
|
||||
'length' => 100,
|
||||
'length' => 200,
|
||||
]);
|
||||
$table->addColumn('matrix_user', 'string', [
|
||||
'notnull' => true,
|
||||
'length' => 63,
|
||||
'length' => 200,
|
||||
]);
|
||||
$table->addColumn('matrix_token', 'string', [
|
||||
'notnull' => true,
|
||||
'length' => 200,
|
||||
]);
|
||||
|
||||
$table->setPrimaryKey(['id']);
|
||||
|
|
|
@ -3,69 +3,175 @@
|
|||
namespace OCA\UPschooling\Service;
|
||||
|
||||
use Aryess\PhpMatrixSdk\Exceptions\MatrixException;
|
||||
use Aryess\PhpMatrixSdk\Exceptions\MatrixHttpLibException;
|
||||
use Aryess\PhpMatrixSdk\Exceptions\MatrixRequestException;
|
||||
use Aryess\PhpMatrixSdk\Exceptions\ValidationException;
|
||||
use Aryess\PhpMatrixSdk\MatrixClient;
|
||||
use Aryess\PhpMatrixSdk\Room;
|
||||
use OCA\UPschooling\Db\MatrixUser;
|
||||
use OCA\UPschooling\Exceptions\RoomNotFoundException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class MatrixService
|
||||
{
|
||||
|
||||
/** @var LoggerInterface */
|
||||
private $logger;
|
||||
/** @var LoggerInterface */
|
||||
private $logger;
|
||||
|
||||
/** @var string */
|
||||
private $channel;
|
||||
/** @var MatrixClient */
|
||||
private $client;
|
||||
|
||||
/** @var MatrixClient */
|
||||
private $client;
|
||||
/** @var string */
|
||||
private $registrationSecret = "~d9fJpPKDZIV67A7=tPCvok:=fTBLV;MFf=9FRxtAazW@-GwSo";
|
||||
|
||||
/** @var string */
|
||||
private $token;
|
||||
/**
|
||||
* @throws MatrixRequestException
|
||||
* @throws MatrixHttpLibException
|
||||
* @throws ValidationException
|
||||
* @throws MatrixException
|
||||
*/
|
||||
public function __construct(LoggerInterface $logger)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
$serverUrl = "http://synapse:8008";
|
||||
$user = "upschooling";
|
||||
$this->client = new MatrixClient($serverUrl);
|
||||
$this->client->login($user, "secret", true);
|
||||
$this->logger->debug("Logged in as " . $user . " on server " . $serverUrl);
|
||||
}
|
||||
|
||||
/** @var Room */
|
||||
private $room;
|
||||
/**
|
||||
* @param string $roomId a room id of an existing and joined room.
|
||||
* @param string $eventType a unique property identifier with reverse domain notation, e.g. com.example.property.
|
||||
* @param array $content the contents as a JSON serializable array.
|
||||
* @throws RoomNotFoundException
|
||||
* @throws MatrixException
|
||||
*/
|
||||
public function setProperty(string $roomId, string $eventType, array $content)
|
||||
{
|
||||
$room = $this->findRoom($roomId);
|
||||
$room->sendStateEvent($eventType, $content);
|
||||
$this->logger->debug(
|
||||
"Set property " . $eventType . " on room " . $roomId,
|
||||
array("room" => $roomId, "key" => $eventType, "value" => $content)
|
||||
);
|
||||
}
|
||||
|
||||
public function __construct(LoggerInterface $logger) {
|
||||
$this->logger = $logger;
|
||||
$this->channel = "#issue:synapse";
|
||||
$this->client = new MatrixClient("http://synapse:8008");
|
||||
$this->token = $this->client->login("upschooling", "secret");
|
||||
// try {
|
||||
// $this->room = $this->client->createRoom($this->channel, false, array());
|
||||
// } catch (MatrixException $createE) {
|
||||
// try {
|
||||
// $this->room = $this->client->joinRoom($this->channel);
|
||||
// } catch (MatrixException $e) {
|
||||
// $this->logger->error(
|
||||
// "Could not create room ".$this->channel,
|
||||
// array('exception' => $e, 'causedBy' => $createE)
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
}
|
||||
/**
|
||||
* @param string $roomId a room id of an existing and joined room.
|
||||
* @param string $eventType a unique property identifier with reverse domain notation, e.g. com.example.property.
|
||||
* @return array the contents of the room state.
|
||||
* @throws MatrixException
|
||||
* @throws RoomNotFoundException
|
||||
*/
|
||||
public function getProperty(string $roomId, string $eventType): array
|
||||
{
|
||||
$this->findRoom($roomId); // make sure the room exists/is joined
|
||||
$content = $this->client->api()->getStateEvent($roomId, $eventType);
|
||||
$this->logger->debug(
|
||||
"Got property " . $eventType . " from room " . $roomId,
|
||||
array("room" => $roomId, "key" => $eventType, "value" => $content)
|
||||
);
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a property on a channel.
|
||||
*
|
||||
* @param string $eventType a unique property identifier with reverse domain notation, e.g. com.example.property.
|
||||
* @param array $content the contents as a JSON serializable array.
|
||||
* @throws MatrixException
|
||||
*/
|
||||
public function setProperty(string $eventType, array $content)
|
||||
{
|
||||
$this->room->sendStateEvent($eventType, $content);
|
||||
}
|
||||
/**
|
||||
* @param string $roomId a room id of an existing and joined room.
|
||||
* @throws RoomNotFoundException
|
||||
* @returns int the origin server timestamp of the most recent event.
|
||||
*/
|
||||
public function getLastEventDate(string $roomId): int
|
||||
{
|
||||
$room = $this->findRoom($roomId);
|
||||
$events = $room->getEvents();
|
||||
if (count($events) === 0) {
|
||||
$this->logger->debug("Did not have any events for room " . $roomId);
|
||||
return 0;
|
||||
} else {
|
||||
$timestamp = array_get($events[0], 'origin_server_ts', 1);
|
||||
if ($timestamp === 1) {
|
||||
$this->logger->debug("Could not find origin_server_ts in last event of room " . $roomId);
|
||||
} else {
|
||||
$this->logger->debug("Last event in room " . $roomId . " was at " . $timestamp);
|
||||
}
|
||||
return $timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $eventType a unique property identifier with reverse domain notation, e.g. com.example.property.
|
||||
* @return array the contents of the room state.
|
||||
* @throws MatrixException|MatrixRequestException
|
||||
*/
|
||||
public function getProperty(string $eventType): array
|
||||
{
|
||||
// first parameter should be $this->room->roomId
|
||||
return $this->client->api()->getStateEvent($this->channel, $eventType);
|
||||
}
|
||||
/**
|
||||
* Registers a new matrix user with the local matrix server.
|
||||
*
|
||||
* @throws MatrixException
|
||||
* @throws MatrixHttpLibException
|
||||
* @throws MatrixRequestException
|
||||
* @return MatrixUser user object without id or Nextcloud user id set.
|
||||
*/
|
||||
public function registerNewUser(): MatrixUser
|
||||
{
|
||||
$nonceResponse = $this->client->api()->send(
|
||||
'GET',
|
||||
'/register',
|
||||
null,
|
||||
[],
|
||||
[],
|
||||
'/_synapse/admin/v1', true
|
||||
);
|
||||
$randUsername = trim(str_replace(["/", "+"], ".", base64_encode(random_bytes(6))), "=");
|
||||
$username = "upschooling_" . $randUsername;
|
||||
$password = base64_encode(random_bytes(32));
|
||||
$hmacData = $nonceResponse["nonce"] . "\x00" . $username . "\x00" . $password . "\x00notadmin";
|
||||
$hmac = hash_hmac("sha1", $hmacData, $this->registrationSecret, false);
|
||||
$registrationResponse = $this->client->api()->send(
|
||||
'POST',
|
||||
'/register',
|
||||
array(
|
||||
"nonce" => $nonceResponse["nonce"],
|
||||
"username" => $username,
|
||||
"password" => $password,
|
||||
"displayname" => "UPschooling Support User " . $randUsername,
|
||||
"admin" => false,
|
||||
"mac" => $hmac,
|
||||
),
|
||||
[],
|
||||
[],
|
||||
'/_synapse/admin/v1', true
|
||||
);
|
||||
|
||||
$matrixUser = new MatrixUser();
|
||||
$matrixUser->setMatrixUser($registrationResponse["user_id"]);
|
||||
$matrixUser->setMatrixToken($registrationResponse["access_token"]);
|
||||
$this->logger->debug("Created a new user: " . $matrixUser->getMatrixUser());
|
||||
return $matrixUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $roomId a room id of an existing and joined room.
|
||||
* @throws RoomNotFoundException
|
||||
* @returns Room the room object, if found.
|
||||
*/
|
||||
private function findRoom(string $roomId): Room
|
||||
{
|
||||
foreach ($this->client->getRooms() as $room) {
|
||||
if ($room->getRoomId() === $roomId) {
|
||||
$this->logger->debug("Found room " . $roomId . " on matrix client");
|
||||
return $room;
|
||||
}
|
||||
}
|
||||
$this->logger->error("Room " . $roomId . " was not found on matrix client");
|
||||
throw new RoomNotFoundException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new room.
|
||||
*
|
||||
* @throws MatrixException
|
||||
* @return string the room id of the newly created room.
|
||||
*/
|
||||
public function createRoom(): string
|
||||
{
|
||||
$roomId = $this->client->createRoom()->getRoomId();
|
||||
$this->logger->debug("Created a new room: " . $roomId);
|
||||
return $roomId;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace OCA\UPschooling\Service;
|
||||
|
||||
class NoteNotFound extends \Exception {
|
||||
}
|
|
@ -2,76 +2,113 @@
|
|||
|
||||
namespace OCA\UPschooling\Service;
|
||||
|
||||
use Aryess\PhpMatrixSdk\Exceptions\MatrixException;
|
||||
use Aryess\PhpMatrixSdk\Exceptions\MatrixHttpLibException;
|
||||
use Aryess\PhpMatrixSdk\Exceptions\MatrixRequestException;
|
||||
use Exception;
|
||||
use OCA\UPschooling\Db\MatrixTicket;
|
||||
use OCA\UPschooling\Db\MatrixUser;
|
||||
use OCA\UPschooling\Db\TicketMapper;
|
||||
use OCA\UPschooling\Db\UserMapper;
|
||||
use OCA\UPschooling\Exceptions\RoomNotFoundException;
|
||||
use OCP\AppFramework\Db\DoesNotExistException;
|
||||
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
||||
|
||||
class TicketService {
|
||||
|
||||
/** @var TicketMapper */
|
||||
private $mapper;
|
||||
|
||||
/** @var MatrixService */
|
||||
private $matrix;
|
||||
|
||||
public function __construct(MatrixService $matrix, TicketMapper $mapper) {
|
||||
/** @var TicketMapper */
|
||||
private $ticketMapper;
|
||||
|
||||
/** @var UserMapper */
|
||||
private $userMapper;
|
||||
|
||||
public function __construct(MatrixService $matrix, TicketMapper $ticketMapper, UserMapper $userMapper) {
|
||||
$this->matrix = $matrix;
|
||||
$this->mapper = $mapper;
|
||||
$this->ticketMapper = $ticketMapper;
|
||||
$this->userMapper = $userMapper;
|
||||
}
|
||||
|
||||
public function findAll(string $userId): array {
|
||||
return $this->mapper->findAll($userId);
|
||||
$dbTickets = $this->ticketMapper->findAll($this->getOrCreateUser($userId));
|
||||
return array_map(function ($ticket) { return $this->resolveTicket($ticket); }, $dbTickets);
|
||||
}
|
||||
|
||||
private function handleException(Exception $e): void {
|
||||
if ($e instanceof DoesNotExistException ||
|
||||
$e instanceof MultipleObjectsReturnedException) {
|
||||
throw new NoteNotFound($e->getMessage());
|
||||
} else {
|
||||
throw $e;
|
||||
}
|
||||
public function find($id, $userId): array {
|
||||
return $this->resolveTicket($this->ticketMapper->find($id, $this->getOrCreateUser($userId)));
|
||||
}
|
||||
|
||||
public function find($id, $userId) {
|
||||
try {
|
||||
return $this->mapper->find($id, $userId);
|
||||
|
||||
// in order to be able to plug in different storage backends like files
|
||||
// for instance it is a good idea to turn storage related exceptions
|
||||
// into service related exceptions so controllers and service users
|
||||
// have to deal with only one type of exception
|
||||
} catch (Exception $e) {
|
||||
$this->handleException($e);
|
||||
}
|
||||
}
|
||||
|
||||
public function create($title, $content, $userId) {
|
||||
$note = new MatrixTicket();
|
||||
$note->setTitle($title);
|
||||
$note->setContent($content);
|
||||
$note->setUserId($userId);
|
||||
return $this->mapper->insert($note);
|
||||
public function create($title, $content, $userId): array {
|
||||
$matrixUser = $this->getOrCreateUser($userId);
|
||||
// FIXME: room must be joined on the remote support-platform Nextcloud
|
||||
$roomId = $this->matrix->createRoom();
|
||||
$this->matrix->setProperty($roomId, "upschooling.ticket", array(
|
||||
// id is to be set from the remote support-platform Nextcloud
|
||||
"title" => $title,
|
||||
"description" => $content,
|
||||
));
|
||||
$ticket = new MatrixTicket();
|
||||
$ticket->setMatrixRoom($roomId);
|
||||
$ticket->setMatrixAssistedUser($matrixUser->getMatrixUser());
|
||||
$ticket->setStatus("open");
|
||||
$ticket->setVersion(1);
|
||||
return $this->resolveTicket($this->ticketMapper->insert($ticket));
|
||||
}
|
||||
|
||||
public function update($id, $title, $content, $userId) {
|
||||
try {
|
||||
$note = $this->mapper->find($id, $userId);
|
||||
$note->setTitle($title);
|
||||
$note->setContent($content);
|
||||
return $this->mapper->update($note);
|
||||
} catch (Exception $e) {
|
||||
$this->handleException($e);
|
||||
}
|
||||
throw new Exception("Not implemented");
|
||||
}
|
||||
|
||||
public function delete($id, $userId) {
|
||||
throw new Exception("Not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param MatrixTicket $ticket the database object.
|
||||
* @return array a JSON serializable representation of the resolved ticket, for the frontend.
|
||||
*/
|
||||
private function resolveTicket(MatrixTicket $ticket): array
|
||||
{
|
||||
try {
|
||||
$note = $this->mapper->find($id, $userId);
|
||||
$this->mapper->delete($note);
|
||||
return $note;
|
||||
} catch (Exception $e) {
|
||||
$this->handleException($e);
|
||||
$matrixTicketContent = $this->matrix->getProperty($ticket->getMatrixRoom(), "upschooling.ticket");
|
||||
$ticketId = array_get($matrixTicketContent, "id", $ticket->getId());
|
||||
$title = array_get($matrixTicketContent, "title", "Untitled");
|
||||
$description = array_get($matrixTicketContent, "description", "");
|
||||
$lastModified = $this->matrix->getLastEventDate($ticket->getMatrixRoom());
|
||||
return array(
|
||||
'ticketId' => $ticketId,
|
||||
'status' => $ticket->getStatus(),
|
||||
'lastModified' => $lastModified,
|
||||
'title' => $title,
|
||||
'description' => $description,
|
||||
);
|
||||
} catch (MatrixException | RoomNotFoundException $e) {
|
||||
return array(
|
||||
'ticketId' => $ticket->getId(),
|
||||
'status' => 'error',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $userId string Nextcloud user id
|
||||
* @throws MatrixException
|
||||
* @throws MatrixHttpLibException
|
||||
* @throws MatrixRequestException
|
||||
* @throws MultipleObjectsReturnedException
|
||||
* @throws \OCP\DB\Exception
|
||||
*/
|
||||
private function getOrCreateUser(string $userId): MatrixUser
|
||||
{
|
||||
try {
|
||||
return $this->userMapper->find($userId);
|
||||
} catch (DoesNotExistException $e) {
|
||||
$matrixUser = $this->matrix->registerNewUser();
|
||||
$matrixUser->setUserId($userId);
|
||||
$this->userMapper->insert($matrixUser);
|
||||
return $matrixUser;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ DIR="${0%/*}"
|
|||
podman rm -if synapse
|
||||
podman rm -if nextcloud
|
||||
|
||||
podman run -d --name=nextcloud -p 8080:80 -v "$DIR:/var/www/html/custom_apps/upschooling" docker.io/nextcloud
|
||||
podman run -d --name=nextcloud -p 8080:80 -p 8008:8008 -v "$DIR:/var/www/html/custom_apps/upschooling" docker.io/nextcloud
|
||||
podman exec nextcloud chown -R 33 /var/www/html/custom_apps
|
||||
"$DIR/podman-reown.sh"
|
||||
podman exec --user 33 nextcloud bash -c 'cd /var/www/html/custom_apps/upschooling && make composer'
|
||||
|
|
31
src/App.vue
31
src/App.vue
|
@ -38,6 +38,7 @@ export default {
|
|||
*/
|
||||
currentTicket: undefined,
|
||||
|
||||
ticketsFetched: false,
|
||||
tickets: [],
|
||||
}
|
||||
},
|
||||
|
@ -45,19 +46,43 @@ export default {
|
|||
$route: 'fetchTickets',
|
||||
},
|
||||
created() {
|
||||
this.createExampleContent()
|
||||
this.fetchTickets()
|
||||
},
|
||||
methods: {
|
||||
createExampleContent() {
|
||||
// this obviously shouldn't survive version 1.0
|
||||
axios.post(
|
||||
'api/v1/tickets',
|
||||
{ title: 'Erstes Ticket', content: 'Erstes Beispiel' },
|
||||
{ headers: { 'Content-Type': 'application/json', Accept: 'application/json' } },
|
||||
).then((response) => {
|
||||
if (this.ticketsFetched === false) {
|
||||
this.tickets.push(response.data)
|
||||
}
|
||||
}).catch(console.error)
|
||||
axios.post(
|
||||
'api/v1/tickets',
|
||||
{ title: 'Zweites Ticket', content: 'Zweites Beispiel' },
|
||||
{ headers: { 'Content-Type': 'application/json', Accept: 'application/json' } },
|
||||
).then((response) => {
|
||||
if (this.ticketsFetched === false) {
|
||||
this.tickets.push(response.data)
|
||||
}
|
||||
}).catch(console.error)
|
||||
},
|
||||
fetchTickets() {
|
||||
axios.get(
|
||||
'api/v1/tickets',
|
||||
{ headers: { Accept: 'application/json' } }
|
||||
).then((response) => {
|
||||
if (Array.isArray(response.data)) {
|
||||
this.ticketsFetched = true
|
||||
if (response.data.length !== 0) {
|
||||
this.tickets.push(response.data)
|
||||
this.tickets = response.data
|
||||
console.debug(this.tickets) // FIXME
|
||||
} else {
|
||||
console.warn('Empty ticket list :(')
|
||||
console.debug('Empty ticket list :(')
|
||||
}
|
||||
} else {
|
||||
console.error('API did not return array: ', response)
|
||||
|
@ -69,7 +94,7 @@ export default {
|
|||
console.debug('upschooling', 'saveTicket', ticketId, data)
|
||||
},
|
||||
openTicket(ticketId) {
|
||||
this.currentTicket = this.tickets.find((obj) => obj.id === ticketId)
|
||||
this.currentTicket = this.tickets.find((obj) => obj.ticketId === ticketId)
|
||||
},
|
||||
deselectTicket() {
|
||||
this.currentTicket = null
|
||||
|
|
|
@ -9,13 +9,13 @@
|
|||
</button>
|
||||
</div>
|
||||
<h2>Ticket "ding"</h2>
|
||||
<KeyValueTable :data-rows="{Name: ticket.title, Status: ticket.status, Ablaufdatum: '<<gestern>>'}" />
|
||||
<KeyValueTable :data-rows="{Id: ticket.ticketId, Name: ticket.title, Status: ticket.status, Geaendert: toLocaleDate(ticket.lastModified)}" />
|
||||
<br>
|
||||
<label for="description">{{ t('upschooling', 'Beschreibung') }}</label>
|
||||
<textarea id="description" v-model.trim.lazy="description" rows="15" />
|
||||
<br>
|
||||
<button @click="save">
|
||||
Speichern
|
||||
{{ t('upschooling', 'Speichern') }}
|
||||
</button>
|
||||
<hr>
|
||||
<div class="placeholder" />
|
||||
|
@ -41,8 +41,13 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
toLocaleDate(timestamp) {
|
||||
const date = new Date(timestamp)
|
||||
return date.toLocaleString()
|
||||
},
|
||||
|
||||
save() {
|
||||
this.$emit('save-ticket', this.ticket.id, {}) // TODO: give it only the changed data
|
||||
this.$emit('save-ticket', this.ticket.ticketId, {}) // TODO: give it only the changed data
|
||||
},
|
||||
|
||||
back() {
|
||||
|
|
|
@ -7,32 +7,32 @@
|
|||
<th id="headerName" class="column-name">
|
||||
<div id="headerName-container">
|
||||
<a class="name sort columntitle" data-sort="name">
|
||||
<span>Titel (#Ticket-Nummer)</span>
|
||||
<span>{{ t('upschooling', 'Titel (#Ticket-Nummer)') }}</span>
|
||||
<span class="sort-indicator icon-triangle-n" />
|
||||
</a>
|
||||
</div>
|
||||
</th>
|
||||
<th id="headerStatus" class="column-status">
|
||||
<a class="status sort columntitle" data-sort="status">
|
||||
<span>Status</span>
|
||||
<span>{{ t('upschooling', 'Status') }}</span>
|
||||
<span class="sort-indicator hidden icon-triangle-s" />
|
||||
</a>
|
||||
</th>
|
||||
<th id="headerDate" class="column-mtime">
|
||||
<a id="modified" class="columntitle" data-sort="mtime">
|
||||
<span>Geändert</span>
|
||||
<span>{{ t('upschooling', 'Zuletzt Geändert') }}</span>
|
||||
<span class="sort-indicator hidden icon-triangle-s" />
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="tickettbody">
|
||||
<tr v-for="item in tickets" :key="item.id">
|
||||
<tr v-for="item in tickets" :key="item.ticketId">
|
||||
<td class="filename ui-draggable ui-draggable-handle">
|
||||
<a class="name" :href="'#ticket-' + item.id" @click="openTicket(item.id)">
|
||||
<a class="name" :href="'#ticket-' + item.ticketId" @click="openTicket(item.ticketId)">
|
||||
<span class="nametext">
|
||||
<span class="innernametext">{{ item.title }}</span>
|
||||
<span class="ticket-number"> (<span class="icon icon-ticket" />#{{ item.id }})</span>
|
||||
<span class="ticket-number"> (<span class="icon icon-ticket" />#{{ item.ticketId }})</span>
|
||||
</span>
|
||||
</a>
|
||||
</td>
|
||||
|
@ -42,11 +42,10 @@
|
|||
<td class="date">
|
||||
<span
|
||||
class="modified live-relative-timestamp"
|
||||
title=""
|
||||
data-timestamp="1628157115000"
|
||||
style="color:rgb(81,81,81)"
|
||||
data-original-title="5. August 2021 11:51">
|
||||
vor 16 Tagen <!-- ToDo -->
|
||||
:title="toLocaleDate(item.lastModified)"
|
||||
:data-timestamp="item.lastModified"
|
||||
style="color:rgb(81,81,81)">
|
||||
{{ toLocaleDate(item.lastModified) }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -66,6 +65,10 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
toLocaleDate(timestamp) {
|
||||
const date = new Date(timestamp)
|
||||
return date.toLocaleString()
|
||||
},
|
||||
openTicket(ticketId) {
|
||||
this.$emit('open-ticket', ticketId)
|
||||
},
|
||||
|
|
|
@ -4,8 +4,8 @@ namespace Unit\Service;
|
|||
|
||||
use OCA\UPschooling\Db\MatrixTicket;
|
||||
use OCA\UPschooling\Db\TicketMapper;
|
||||
use OCA\UPschooling\Db\UserMapper;
|
||||
use OCA\UPschooling\Service\MatrixService;
|
||||
use OCA\UPschooling\Service\NoteNotFound;
|
||||
use OCA\UPschooling\Service\TicketService;
|
||||
use OCP\AppFramework\Db\DoesNotExistException;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
@ -20,17 +20,23 @@ class NoteServiceTest extends TestCase {
|
|||
private $matrixService;
|
||||
|
||||
/** @var TicketMapper */
|
||||
private $mapper;
|
||||
private $ticketMapper;
|
||||
|
||||
/** @var UserMapper */
|
||||
private $userMapper;
|
||||
|
||||
/** @var string */
|
||||
private $userId = 'john';
|
||||
|
||||
public function setUp(): void {
|
||||
$this->mapper = $this->getMockBuilder(TicketMapper::class)
|
||||
$this->ticketMapper = $this->getMockBuilder(TicketMapper::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$this->userMapper = $this->getMockBuilder(UserMapper::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$this->matrixService = new MatrixService(new NullLogger());
|
||||
$this->service = new TicketService($this->matrixService, $this->mapper);
|
||||
$this->service = new TicketService($this->matrixService, $this->ticketMapper, $this->userMapper);
|
||||
}
|
||||
|
||||
public function testUpdate() {
|
||||
|
@ -40,7 +46,7 @@ class NoteServiceTest extends TestCase {
|
|||
'title' => 'yo',
|
||||
'content' => 'nope'
|
||||
]);
|
||||
$this->mapper->expects($this->once())
|
||||
$this->ticketMapper->expects($this->once())
|
||||
->method('find')
|
||||
->with($this->equalTo(3))
|
||||
->will($this->returnValue($note));
|
||||
|
@ -49,7 +55,7 @@ class NoteServiceTest extends TestCase {
|
|||
$updatedNote = MatrixTicket::fromRow(['id' => 3]);
|
||||
$updatedNote->setTitle('title');
|
||||
$updatedNote->setContent('content');
|
||||
$this->mapper->expects($this->once())
|
||||
$this->ticketMapper->expects($this->once())
|
||||
->method('update')
|
||||
->with($this->equalTo($updatedNote))
|
||||
->will($this->returnValue($updatedNote));
|
||||
|
@ -60,9 +66,9 @@ class NoteServiceTest extends TestCase {
|
|||
}
|
||||
|
||||
public function testUpdateNotFound() {
|
||||
$this->expectException(NoteNotFound::class);
|
||||
// $this->expectException(NoteNotFound::class);
|
||||
// test the correct status code if no note is found
|
||||
$this->mapper->expects($this->once())
|
||||
$this->ticketMapper->expects($this->once())
|
||||
->method('find')
|
||||
->with($this->equalTo(3))
|
||||
->will($this->throwException(new DoesNotExistException('')));
|
||||
|
|
Loading…
Reference in a new issue