Create MatrixChat entity, chat rooms for clients

This commit is contained in:
Ben 2022-04-23 16:55:05 +02:00
parent 940c7bed6e
commit 10a54cf6ac
Signed by: ben
GPG key ID: 0F54A7ED232D3319
9 changed files with 265 additions and 45 deletions

View file

@ -3,6 +3,7 @@
namespace OCA\UPschooling\Controller; namespace OCA\UPschooling\Controller;
use Closure; use Closure;
use OCA\UPschooling\Exceptions\RoomNotFoundException;
use OCA\UPschooling\Exceptions\TicketNotFoundException; use OCA\UPschooling\Exceptions\TicketNotFoundException;
use OCP\AppFramework\Http; use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Http\DataResponse;
@ -11,9 +12,9 @@ trait Errors {
protected function handleNotFound(Closure $callback): DataResponse { protected function handleNotFound(Closure $callback): DataResponse {
try { try {
return new DataResponse($callback()); return new DataResponse($callback());
} catch (TicketNotFoundException $e) { } catch (TicketNotFoundException|RoomNotFoundException $e) {
$message = ['message' => $e->getMessage()]; $result = $e->getResult();
return new DataResponse($message, Http::STATUS_NOT_FOUND); return new DataResponse($result->getMeta(), $result->getStatusCode());
} }
} }
} }

View file

@ -4,13 +4,14 @@ namespace OCA\UPschooling\Controller;
use OCA\UPschooling\AppInfo\Application; use OCA\UPschooling\AppInfo\Application;
use OCA\UPschooling\Db\TicketMapper; use OCA\UPschooling\Db\TicketMapper;
use OCA\UPschooling\Exceptions\RoomNotFoundException;
use OCA\UPschooling\Service\ChatService;
use OCA\UPschooling\Service\MatrixService; use OCA\UPschooling\Service\MatrixService;
use OCA\UPschooling\Service\TicketService; use OCA\UPschooling\Service\TicketService;
use OCP\AppFramework\ApiController; use OCP\AppFramework\ApiController;
use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Http\DataResponse;
use OCP\IRequest; use OCP\IRequest;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Punic\Data;
class TicketApiController extends ApiController class TicketApiController extends ApiController
{ {
@ -24,6 +25,9 @@ class TicketApiController extends ApiController
/** @var MatrixService */ /** @var MatrixService */
private $matrixService; private $matrixService;
/** @var ChatService */
private $chatService;
/** @var TicketMapper */ /** @var TicketMapper */
private $ticketMapper; private $ticketMapper;
@ -38,6 +42,7 @@ class TicketApiController extends ApiController
TicketService $ticketService, TicketService $ticketService,
TicketMapper $ticketMapper, TicketMapper $ticketMapper,
MatrixService $matrixService, MatrixService $matrixService,
ChatService $chatService,
string $userId string $userId
) { ) {
parent::__construct(Application::APP_ID, $request); parent::__construct(Application::APP_ID, $request);
@ -45,6 +50,7 @@ class TicketApiController extends ApiController
$this->ticketService = $ticketService; $this->ticketService = $ticketService;
$this->ticketMapper = $ticketMapper; $this->ticketMapper = $ticketMapper;
$this->matrixService = $matrixService; $this->matrixService = $matrixService;
$this->chatService = $chatService;
$this->userId = $userId; $this->userId = $userId;
} }
@ -74,9 +80,12 @@ class TicketApiController extends ApiController
return $this->handleNotFound(function () use ($id) { return $this->handleNotFound(function () use ($id) {
$matrixUser = $this->ticketService->getOrCreateUser($this->userId); $matrixUser = $this->ticketService->getOrCreateUser($this->userId);
$ticket = $this->ticketMapper->findForUser($id, $matrixUser); $ticket = $this->ticketMapper->findForUser($id, $matrixUser);
$this->logger->debug("fetchChat found matrix data for room " . $ticket->getMatrixRoom()); $this->logger->debug("fetchChat found matrix data for ticket " . print_r($ticket->jsonSerialize(), true));
// returns 404 RoomNotFoundException if no room could/should be created
$chatRoom = $this->chatService->getOrCreateChatRoom($ticket);
$this->logger->debug("fetchChat got chat room " . print_r($chatRoom->jsonSerialize(), true));
return array( return array(
'matrixRoom' => $ticket->getMatrixRoom(), // FIXME: wrong room, create one for helper and user 'matrixChatRoom' => $chatRoom->getMatrixRoomId(),
'matrixAccessToken' => $matrixUser->getMatrixToken(), 'matrixAccessToken' => $matrixUser->getMatrixToken(),
'matrixServerUrl' => $this->matrixService->getServerUrl(), 'matrixServerUrl' => $this->matrixService->getServerUrl(),
); );

63
lib/Db/ChatMapper.php Normal file
View file

@ -0,0 +1,63 @@
<?php
namespace OCA\UPschooling\Db;
use Doctrine\DBAL\Types\Type;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\Entity;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
class ChatMapper extends QBMapper
{
public function __construct(IDBConnection $db)
{
parent::__construct($db, 'upschooling_chats', MatrixChat::class);
}
/**
* @param int $ticketId
* @return array array of Entity|MatrixChat
*/
public function findForTicket(int $ticketId): array
{
/* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from('upschooling_chats')
->where($qb->expr()->eq('ticket_id', $qb->createNamedParameter($ticketId, IQueryBuilder::PARAM_INT)));
return $this->findEntities($qb);
}
/**
* @param int $ticketId
* @return Entity|MatrixChat
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws DoesNotExistException
*/
public function findCurrent(int $ticketId): MatrixChat
{
/* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from('upschooling_chats')
->where(
$qb->expr()->eq('ticket_id', $qb->createNamedParameter($ticketId, IQueryBuilder::PARAM_INT)),
$qb->expr()->isNull('end_date')
);
return $this->findEntity($qb);
}
/**
* @param \DateTime $dateTime
* @return mixed The database representation of the datetime with timezone.
* @throws \Doctrine\DBAL\Exception
* @throws \Doctrine\DBAL\Types\ConversionException
*/
public function convertDateTimeTz(\DateTime $dateTime)
{
$dateTimeTzType = Type::getType("datetimetz");
return $dateTimeTzType->convertToDatabaseValue($dateTime, $this->db->getDatabasePlatform());
}
}

29
lib/Db/MatrixChat.php Normal file
View file

@ -0,0 +1,29 @@
<?php
namespace OCA\UPschooling\Db;
use JsonSerializable;
use OCP\AppFramework\Db\Entity;
class MatrixChat extends Entity implements JsonSerializable
{
protected $ticketId;
protected $matrixRoomId;
protected $matrixHelperUser;
protected $dateStart;
protected $dateEnd;
protected $version;
public function jsonSerialize(): array
{
return [
'id' => $this->id,
'ticketId' => $this->ticketId,
'matrixRoomId' => $this->matrixRoomId,
'matrixHelperUser' => $this->matrixHelperUser,
'dateStart' => $this->dateStart,
'dateEnd' => $this->dateEnd,
'version' => $this->version,
];
}
}

View file

@ -55,30 +55,61 @@ class Version000000Date20210918151800 extends SimpleMigrationStep {
$table->addIndex(['matrix_control_room'], 'upschooling_mx_room_id_idx'); $table->addIndex(['matrix_control_room'], 'upschooling_mx_room_id_idx');
} }
if (!$schema->hasTable('upschooling_users')) { if (!$schema->hasTable('upschooling_users')) {
$table = $schema->createTable('upschooling_users'); $table = $schema->createTable('upschooling_users');
$table->addColumn('id', 'integer', [ $table->addColumn('id', 'integer', [
'autoincrement' => true, 'autoincrement' => true,
'notnull' => true, 'notnull' => true,
]); ]);
$table->addColumn('user_id', 'string', [ $table->addColumn('user_id', 'string', [
'notnull' => true, 'notnull' => true,
'length' => 64, 'length' => 64,
]); ]);
$table->addColumn('matrix_user', 'string', [ $table->addColumn('matrix_user', 'string', [
'notnull' => true, 'notnull' => true,
'length' => 200, 'length' => 200,
]); ]);
$table->addColumn('matrix_token', 'string', [ $table->addColumn('matrix_token', 'string', [
'notnull' => true, 'notnull' => true,
'length' => 200, 'length' => 200,
]); ]);
$table->setPrimaryKey(['id']); $table->setPrimaryKey(['id']);
$table->addUniqueConstraint(['user_id'], 'upschooling_mx_user_nc_uniq'); $table->addUniqueConstraint(['user_id'], 'upschooling_mx_user_nc_uniq');
$table->addUniqueConstraint(['matrix_user'], 'upschooling_mx_user_mx_uniq'); $table->addUniqueConstraint(['matrix_user'], 'upschooling_mx_user_mx_uniq');
$table->addForeignKeyConstraint('users', ['user_id'], ['uid'], [], 'upschooling_mx_user_nc_fk'); $table->addForeignKeyConstraint('users', ['user_id'], ['uid'], [], 'upschooling_mx_user_nc_fk');
} }
if (!$schema->hasTable('upschooling_chats')) {
$table = $schema->createTable('upschooling_chats');
$table->addColumn('id', 'integer', [
'autoincrement' => true,
'notnull' => true,
]);
$table->addColumn('ticket_id', 'integer', [
'notnull' => true,
]);
$table->addColumn('matrix_room_id', 'string', [
'notnull' => true,
'length' => 200,
]);
$table->addColumn('matrix_helper_user', 'string', [
'notnull' => true,
'length' => 200,
]);
$table->addColumn('date_start', 'datetimetz', [
'notnull' => true,
]);
$table->addColumn('date_end', 'datetimetz', [
'notnull' => false,
]);
$table->addColumn('version', 'integer', [
'notnull' => true,
]);
$table->setPrimaryKey(['id']);
$table->addForeignKeyConstraint('upschooling_tickets', ['ticket_id'], ['id'], [], 'upschooling_tckt_id_fk');
}
return $schema; return $schema;
} }
} }

View file

@ -0,0 +1,62 @@
<?php
namespace OCA\UPschooling\Service;
use Aryess\PhpMatrixSdk\Room;
use OCA\UPschooling\Db\ChatMapper;
use OCA\UPschooling\Db\MatrixChat;
use OCA\UPschooling\Db\MatrixTicket;
use OCA\UPschooling\Db\TicketMapper;
use OCA\UPschooling\Exceptions\RoomNotFoundException;
use OCP\AppFramework\Db\DoesNotExistException;
use Psr\Log\LoggerInterface;
class ChatService
{
/** @var ChatMapper */
private $chatMapper;
/** @var MatrixService */
private $matrixService;
/** @var LoggerInterface */
private $logger;
public function __construct(ChatMapper $chatMapper, MatrixService $matrixService, LoggerInterface $logger)
{
$this->chatMapper = $chatMapper;
$this->matrixService = $matrixService;
$this->logger = $logger;
}
/**
* @param $ticket MatrixTicket Support ticket
* @throws RoomNotFoundException
*/
public function getOrCreateChatRoom(MatrixTicket $ticket): MatrixChat
{
try {
return $this->chatMapper->findCurrent($ticket->getId());
} catch (DoesNotExistException $e) {
if ($ticket->getMatrixHelperUser() == null) {
$this->logger->debug("No helper assigned to ticket " . $ticket->getId() . ". Not creating chat room");
throw new RoomNotFoundException();
}
$this->logger->debug("Chat room for ticket " . $ticket->getId() . " does not exist. Creating room..");
$roomId = $this->matrixService->createRoom();
$startDate = new \DateTime('now', new \DateTimeZone("utc"));
$matrixChat = new MatrixChat();
$matrixChat->setTicketId($ticket->getId());
$matrixChat->setMatrixRoomId($roomId);
$matrixChat->setMatrixHelperUser($ticket->getMatrixHelperUser());
$matrixChat->setDateStart($this->chatMapper->convertDateTimeTz($startDate));
$matrixChat->setVersion(1);
$this->matrixService->inviteUser($roomId, $ticket->getMatrixHelperUser());
$this->matrixService->inviteUser($roomId, $ticket->getMatrixAssistedUser());
return $this->chatMapper->insert($matrixChat);
}
}
}

View file

@ -255,4 +255,10 @@ class MatrixService
$this->logger->debug("No ratelimiting for " . $this->superuser); $this->logger->debug("No ratelimiting for " . $this->superuser);
} }
} }
public function inviteUser(string $roomId, string $matrixUserId)
{
$room = $this->client->joinRoom($roomId);
$room->inviteUser($matrixUserId);
}
} }

View file

@ -15,6 +15,7 @@ use OCA\UPschooling\Exceptions\TicketNotFoundException;
use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException; use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\IUserManager; use OCP\IUserManager;
use Psr\Log\LoggerInterface;
class TicketService { class TicketService {
@ -30,16 +31,21 @@ class TicketService {
/** @var UserMapper */ /** @var UserMapper */
private $userMapper; private $userMapper;
/** @var LoggerInterface */
private $logger;
public function __construct( public function __construct(
IUserManager $userManager, IUserManager $userManager,
MatrixService $matrix, MatrixService $matrix,
TicketMapper $ticketMapper, TicketMapper $ticketMapper,
UserMapper $userMapper UserMapper $userMapper,
LoggerInterface $logger
) { ) {
$this->userManager = $userManager; $this->userManager = $userManager;
$this->matrix = $matrix; $this->matrix = $matrix;
$this->ticketMapper = $ticketMapper; $this->ticketMapper = $ticketMapper;
$this->userMapper = $userMapper; $this->userMapper = $userMapper;
$this->logger = $logger;
} }
public function findAll(string $userId): array { public function findAll(string $userId): array {
@ -83,6 +89,7 @@ class TicketService {
} }
public function assign($id, $userId): array{ public function assign($id, $userId): array{
$this->logger->debug("Assigning $userId to ticket $id..");
$matrixUser = $this->getOrCreateUser($userId); $matrixUser = $this->getOrCreateUser($userId);
$ticket = $this->ticketMapper->findTicket($id); $ticket = $this->ticketMapper->findTicket($id);
$roomID = $ticket->getMatrixControlRoom(); $roomID = $ticket->getMatrixControlRoom();

View file

@ -27,6 +27,7 @@
</template> </template>
<script> <script>
import KeyValueTable from './components/KeyValueTable' import KeyValueTable from './components/KeyValueTable'
import axios from '@nextcloud/axios' import axios from '@nextcloud/axios'
@ -50,13 +51,24 @@ export default {
/** @type {HTMLIFrameElement} */ /** @type {HTMLIFrameElement} */
const elementWebFrame = document.getElementById('element-web-frame') const elementWebFrame = document.getElementById('element-web-frame')
this.loadChat(elementWebFrame).catch((err) => { this.loadChat(elementWebFrame).catch((err) => {
console.error('Could not load Element Web in iframe', err) if (err.message === 'Room not found') {
elementWebFrame.src = 'about:blank' console.debug('No chat room assigned to ticket')
elementWebFrame.onload = function() { elementWebFrame.src = 'about:blank'
const textElement = elementWebFrame.contentDocument.createElement('strong') elementWebFrame.onload = function() {
textElement.innerText = 'Element Web konnte nicht geladen werden.' const textElement = elementWebFrame.contentDocument.createElement('strong')
elementWebFrame.contentDocument.body.appendChild(textElement) textElement.innerText = 'Textchat nicht verfügbar. Es ist noch kein Helfer zugewiesen.'
elementWebFrame.onload = undefined elementWebFrame.contentDocument.body.appendChild(textElement)
elementWebFrame.onload = undefined
}
} else {
console.error('Could not load Element Web in iframe', err)
elementWebFrame.src = 'about:blank'
elementWebFrame.onload = function() {
const textElement = elementWebFrame.contentDocument.createElement('strong')
textElement.innerText = 'Element Web konnte nicht geladen werden.'
elementWebFrame.contentDocument.body.appendChild(textElement)
elementWebFrame.onload = undefined
}
} }
}) })
}, },
@ -77,8 +89,11 @@ export default {
async loadChat(elementWebFrame) { async loadChat(elementWebFrame) {
const matrixInfoResponse = await axios.get( const matrixInfoResponse = await axios.get(
`api/v1/tickets/${this.ticket.ticketId}/chat`, `api/v1/tickets/${this.ticket.ticketId}/chat`,
{ headers: { Accept: 'application/json' } }, { headers: { Accept: 'application/json' }, validateStatus: status => status === 200 || status === 404 },
) )
if (matrixInfoResponse.status === 404 && matrixInfoResponse.data.message === 'Room not found') {
throw Error(matrixInfoResponse.data.message)
}
if (matrixInfoResponse.status !== 200) { if (matrixInfoResponse.status !== 200) {
throw Error(`Received unexpected status code ${matrixInfoResponse.status} for fetching matrix chat data`) throw Error(`Received unexpected status code ${matrixInfoResponse.status} for fetching matrix chat data`)
} }
@ -113,15 +128,12 @@ export default {
// load Element Web // load Element Web
elementWebFrame.src = '/upschooling/element-web/' elementWebFrame.src = '/upschooling/element-web/'
loginPromise.then(() => { loginPromise.then(async () => {
console.warn('LOGGED IN') const matrixClient = elementWebFrame.contentWindow.mxMatrixClientPeg.get()
elementWebFrame.contentWindow.mxDispatcher.dispatch({ await matrixClient.joinRoom(matrixInfoResponse.data.matrixChatRoom)
action: 'view_home_page',
justRegistered: false,
})
elementWebFrame.contentWindow.mxDispatcher.dispatch({ elementWebFrame.contentWindow.mxDispatcher.dispatch({
action: 'view_room', action: 'view_room',
room_id: matrixInfoResponse.data.matrixRoom, room_id: matrixInfoResponse.data.matrixChatRoom,
}) })
}).catch(console.error) }).catch(console.error)
}, },