Compare commits
34 commits
1f03d15559
...
c82acc8f52
Author | SHA1 | Date | |
---|---|---|---|
Ben | c82acc8f52 | ||
Ben | 01212543fe | ||
Ben | 1f14932b28 | ||
Ben | 70d59ebece | ||
Ben | 0a90ea80b8 | ||
Ben | 5ccdcec520 | ||
Ben | ec89f04144 | ||
Ben | 51e9a8270d | ||
Ben | 332497ea74 | ||
Ben | 811766b242 | ||
Ben | a7cb94d136 | ||
Ben | 0a7cd19130 | ||
Ben | dd485c8651 | ||
Ben | 9090d384ef | ||
Ben | 0113cdad85 | ||
Ben | c3064395f8 | ||
Ben | 6f45a4ee37 | ||
Ben | f62380a768 | ||
Ben | 58d8695880 | ||
Finn | 29322a4e9c | ||
Finn | 0e2f6f944d | ||
Finn | 5f45d0a3c8 | ||
Finn | 2252a49467 | ||
Finn | d88bd657ef | ||
Finn | 347181f21a | ||
Finn | 3440797555 | ||
Finn | abfd450699 | ||
Finn | 92a41e4865 | ||
Finn | 742168215f | ||
Finn | 8a2d0f9fa3 | ||
Finn | 8631a08e5e | ||
Finn | 2538c745a7 | ||
Finn | 3e9484484d | ||
Finn | 81ae89697f |
209
.gitignore
vendored
209
.gitignore
vendored
|
@ -1,10 +1,207 @@
|
|||
/.idea/
|
||||
*.iml
|
||||
/build/
|
||||
node_modules/
|
||||
/.php_cs.cache
|
||||
/js/
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/phpstorm+all,phpunit,composer,node,vue
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=phpstorm+all,phpunit,composer,node,vue
|
||||
|
||||
### Composer ###
|
||||
composer.phar
|
||||
/vendor/
|
||||
|
||||
# Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control
|
||||
# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file
|
||||
# composer.lock
|
||||
|
||||
### Node ###
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# webpack output
|
||||
js/
|
||||
|
||||
### PhpStorm+all ###
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# AWS User-specific
|
||||
.idea/**/aws.xml
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
# .idea/artifacts
|
||||
# .idea/compiler.xml
|
||||
# .idea/jarRepositories.xml
|
||||
# .idea/modules.xml
|
||||
# .idea/*.iml
|
||||
# .idea/modules
|
||||
# *.iml
|
||||
# *.ipr
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
|
||||
# Android studio 3.1+ serialized cache file
|
||||
.idea/caches/build_file_checksums.ser
|
||||
|
||||
### PhpStorm+all Patch ###
|
||||
# Ignores the whole .idea folder and all .iml files
|
||||
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
|
||||
|
||||
.idea/
|
||||
|
||||
# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
|
||||
|
||||
*.iml
|
||||
modules.xml
|
||||
.idea/misc.xml
|
||||
*.ipr
|
||||
|
||||
# Sonarlint plugin
|
||||
.idea/sonarlint
|
||||
|
||||
### PHPUnit ###
|
||||
# Covers PHPUnit
|
||||
# Reference: https://phpunit.de/
|
||||
|
||||
# Generated files
|
||||
.phpunit.result.cache
|
||||
.phpunit.cache
|
||||
|
||||
# PHPUnit
|
||||
/app/phpunit.xml
|
||||
/phpunit.xml
|
||||
|
||||
# Build data
|
||||
/build/
|
||||
|
||||
### Vue ###
|
||||
# gitignore template for Vue.js projects
|
||||
#
|
||||
# Recommended template: Node.gitignore
|
||||
|
||||
# TODO: where does this rule come from?
|
||||
docs/_book
|
||||
|
||||
# TODO: where does this rule come from?
|
||||
test/
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/phpstorm+all,phpunit,composer,node,vue
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
<category>tools</category>
|
||||
<bugs>https://gitea.rs485.network/UPschooling/Nextcloud-App/issues</bugs>
|
||||
<dependencies>
|
||||
<nextcloud min-version="15" max-version="22"/>
|
||||
<nextcloud min-version="20" max-version="22"/>
|
||||
</dependencies>
|
||||
<navigations>
|
||||
<navigation>
|
||||
|
|
|
@ -2,12 +2,15 @@
|
|||
|
||||
return [
|
||||
'resources' => [
|
||||
'note' => ['url' => '/notes'],
|
||||
'note_api' => ['url' => '/api/0.1/notes']
|
||||
'ticket_api' => ['url' => '/api/v1/tickets']
|
||||
],
|
||||
'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/tickets',
|
||||
'verb' => 'OPTIONS',
|
||||
'requirements' => ['path' => '.+'],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
|
|
@ -2,13 +2,22 @@
|
|||
"name": "upschooling/upschooling",
|
||||
"description": "UPschooling Support Platform",
|
||||
"type": "project",
|
||||
"license": "AGPL",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"authors": [
|
||||
{
|
||||
"name": "UPschooling"
|
||||
}
|
||||
],
|
||||
"require": {},
|
||||
"repositories": [
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/bziemons/matrix-php-sdk.git"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"aryess/php-matrix-sdk": "dev-feature/guzzle7-update",
|
||||
"ext-json": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^8.5",
|
||||
"nextcloud/coding-standard": "^0.5.0"
|
||||
|
@ -17,7 +26,7 @@
|
|||
"optimize-autoloader": true,
|
||||
"classmap-authoritative": true,
|
||||
"platform": {
|
||||
"php": "7.2"
|
||||
"php": "7.2.5"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
|
@ -25,4 +34,4 @@
|
|||
"cs:check": "php-cs-fixer fix --dry-run --diff",
|
||||
"cs:fix": "php-cs-fixer fix"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
1105
composer.lock
generated
1105
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -8,7 +8,7 @@ docker rm -f nextcloud
|
|||
docker run -d --name=nextcloud -p 8080:80 -v "${PWD}:/var/www/html/custom_apps/upschooling" docker.io/nextcloud
|
||||
docker exec nextcloud chown -R 33 /var/www/html/custom_apps
|
||||
docker exec nextcloud chmod -R ug+rw /var/www/html/custom_apps
|
||||
docker exec --user 33 nextcloud bash -c 'cd /var/www/html/custom_apps/upschooling && make'
|
||||
docker exec --user 33 nextcloud bash -c 'cd /var/www/html/custom_apps/upschooling && make composer'
|
||||
docker exec --user 33 nextcloud php occ maintenance:install --database "sqlite" --admin-user "admin" --admin-pass "admin"
|
||||
docker exec --user 33 nextcloud php occ config:system:set --value=true --type=boolean debug
|
||||
docker exec --user 33 nextcloud php occ app:enable --force upschooling
|
||||
|
|
|
@ -3,11 +3,25 @@
|
|||
namespace OCA\UPschooling\AppInfo;
|
||||
|
||||
use OCP\AppFramework\App;
|
||||
use OCP\AppFramework\Bootstrap\IBootContext;
|
||||
use OCP\AppFramework\Bootstrap\IBootstrap;
|
||||
use OCP\AppFramework\Bootstrap\IRegistrationContext;
|
||||
|
||||
class Application extends App {
|
||||
class Application extends App implements IBootstrap {
|
||||
public const APP_ID = 'upschooling';
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(self::APP_ID);
|
||||
}
|
||||
|
||||
public function register(IRegistrationContext $context): void
|
||||
{
|
||||
// Register the composer autoloader for packages shipped by this app, if applicable
|
||||
include_once __DIR__ . '/../../vendor/autoload.php';
|
||||
}
|
||||
|
||||
public function boot(IBootContext $context): void
|
||||
{
|
||||
// nothing to boot?
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -1,80 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace OCA\UPschooling\Controller;
|
||||
|
||||
use OCA\UPschooling\AppInfo\Application;
|
||||
use OCA\UPschooling\Service\NoteService;
|
||||
use OCP\AppFramework\ApiController;
|
||||
use OCP\AppFramework\Http\DataResponse;
|
||||
use OCP\IRequest;
|
||||
|
||||
class NoteApiController extends ApiController {
|
||||
/** @var NoteService */
|
||||
private $service;
|
||||
|
||||
/** @var string */
|
||||
private $userId;
|
||||
|
||||
use Errors;
|
||||
|
||||
public function __construct(IRequest $request,
|
||||
NoteService $service,
|
||||
$userId) {
|
||||
parent::__construct(Application::APP_ID, $request);
|
||||
$this->service = $service;
|
||||
$this->userId = $userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @CORS
|
||||
* @NoCSRFRequired
|
||||
* @NoAdminRequired
|
||||
*/
|
||||
public function index(): DataResponse {
|
||||
return new DataResponse($this->service->findAll($this->userId));
|
||||
}
|
||||
|
||||
/**
|
||||
* @CORS
|
||||
* @NoCSRFRequired
|
||||
* @NoAdminRequired
|
||||
*/
|
||||
public function show(int $id): DataResponse {
|
||||
return $this->handleNotFound(function () use ($id) {
|
||||
return $this->service->find($id, $this->userId);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @CORS
|
||||
* @NoCSRFRequired
|
||||
* @NoAdminRequired
|
||||
*/
|
||||
public function create(string $title, string $content): DataResponse {
|
||||
return new DataResponse($this->service->create($title, $content,
|
||||
$this->userId));
|
||||
}
|
||||
|
||||
/**
|
||||
* @CORS
|
||||
* @NoCSRFRequired
|
||||
* @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);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @CORS
|
||||
* @NoCSRFRequired
|
||||
* @NoAdminRequired
|
||||
*/
|
||||
public function destroy(int $id): DataResponse {
|
||||
return $this->handleNotFound(function () use ($id) {
|
||||
return $this->service->delete($id, $this->userId);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -3,13 +3,14 @@
|
|||
namespace OCA\UPschooling\Controller;
|
||||
|
||||
use OCA\UPschooling\AppInfo\Application;
|
||||
use OCA\UPschooling\Service\NoteService;
|
||||
use OCP\AppFramework\Controller;
|
||||
use OCA\UPschooling\Service\TicketService;
|
||||
use OCP\AppFramework\ApiController;
|
||||
use OCP\AppFramework\Http\DataResponse;
|
||||
use OCP\IRequest;
|
||||
|
||||
class NoteController extends Controller {
|
||||
/** @var NoteService */
|
||||
class TicketApiController extends ApiController
|
||||
{
|
||||
/** @var TicketService */
|
||||
private $service;
|
||||
|
||||
/** @var string */
|
||||
|
@ -17,9 +18,8 @@ class NoteController extends Controller {
|
|||
|
||||
use Errors;
|
||||
|
||||
public function __construct(IRequest $request,
|
||||
NoteService $service,
|
||||
$userId) {
|
||||
public function __construct(IRequest $request, TicketService $service, $userId)
|
||||
{
|
||||
parent::__construct(Application::APP_ID, $request);
|
||||
$this->service = $service;
|
||||
$this->userId = $userId;
|
||||
|
@ -28,14 +28,16 @@ class NoteController extends Controller {
|
|||
/**
|
||||
* @NoAdminRequired
|
||||
*/
|
||||
public function index(): DataResponse {
|
||||
public function index(): DataResponse
|
||||
{
|
||||
return new DataResponse($this->service->findAll($this->userId));
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
*/
|
||||
public function show(int $id): DataResponse {
|
||||
public function show(int $id): DataResponse
|
||||
{
|
||||
return $this->handleNotFound(function () use ($id) {
|
||||
return $this->service->find($id, $this->userId);
|
||||
});
|
||||
|
@ -44,25 +46,23 @@ class NoteController extends Controller {
|
|||
/**
|
||||
* @NoAdminRequired
|
||||
*/
|
||||
public function create(string $title, string $content): DataResponse {
|
||||
return new DataResponse($this->service->create($title, $content,
|
||||
$this->userId));
|
||||
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 {
|
||||
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 {
|
||||
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
27
lib/Db/MatrixTicket.php
Normal 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,
|
||||
];
|
||||
}
|
||||
}
|
21
lib/Db/MatrixUser.php
Normal file
21
lib/Db/MatrixUser.php
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace OCA\UPschooling\Db;
|
||||
|
||||
use JsonSerializable;
|
||||
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,
|
||||
];
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace OCA\UPschooling\Db;
|
||||
|
||||
use JsonSerializable;
|
||||
|
||||
use OCP\AppFramework\Db\Entity;
|
||||
|
||||
class Note extends Entity implements JsonSerializable {
|
||||
protected $title;
|
||||
protected $content;
|
||||
protected $userId;
|
||||
|
||||
public function jsonSerialize(): array {
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'title' => $this->title,
|
||||
'content' => $this->content
|
||||
];
|
||||
}
|
||||
}
|
|
@ -8,25 +8,28 @@ use OCP\AppFramework\Db\QBMapper;
|
|||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\IDBConnection;
|
||||
|
||||
class NoteMapper extends QBMapper {
|
||||
public function __construct(IDBConnection $db) {
|
||||
parent::__construct($db, 'upschooling', Note::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|Note
|
||||
* @return Entity|MatrixTicket
|
||||
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
|
||||
* @throws DoesNotExistException
|
||||
*/
|
||||
public function find(int $id, string $userId): Note {
|
||||
public function find(int $id, MatrixUser $matrixUser): MatrixTicket
|
||||
{
|
||||
/* @var $qb IQueryBuilder */
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
->from('upschooling')
|
||||
->from('upschooling_tickets')
|
||||
->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)))
|
||||
->andWhere($qb->expr()->eq('user_id', $qb->createNamedParameter($userId)));
|
||||
->andWhere($qb->expr()->in($matrixUser->getMatrixUser(), ['matrix_assisted_user', 'matrix_helper_user']));
|
||||
return $this->findEntity($qb);
|
||||
}
|
||||
|
||||
|
@ -34,12 +37,13 @@ class NoteMapper 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')
|
||||
->where($qb->expr()->eq('user_id', $qb->createNamedParameter($userId)));
|
||||
->from('upschooling_tickets')
|
||||
->where($qb->expr()->in($matrixUser->getMatrixUser(), ['matrix_assisted_user', 'matrix_helper_user']));
|
||||
return $this->findEntities($qb);
|
||||
}
|
||||
}
|
31
lib/Db/UserMapper.php
Normal file
31
lib/Db/UserMapper.php
Normal 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);
|
||||
}
|
||||
}
|
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'));
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
84
lib/Migration/Version000000Date20210918151800.php
Normal file
84
lib/Migration/Version000000Date20210918151800.php
Normal file
|
@ -0,0 +1,84 @@
|
|||
<?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' => 200,
|
||||
]);
|
||||
$table->addColumn('matrix_assisted_user', 'string', [
|
||||
'notnull' => true,
|
||||
'length' => 200,
|
||||
]);
|
||||
$table->addColumn('matrix_helper_user', 'string', [
|
||||
'notnull' => false,
|
||||
'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' => 63,
|
||||
]);
|
||||
$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' => 64,
|
||||
]);
|
||||
$table->addColumn('matrix_user', 'string', [
|
||||
'notnull' => true,
|
||||
'length' => 200,
|
||||
]);
|
||||
$table->addColumn('matrix_token', 'string', [
|
||||
'notnull' => true,
|
||||
'length' => 200,
|
||||
]);
|
||||
|
||||
$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'], ['uid'], [], 'upschooling_mx_user_nc_fk');
|
||||
}
|
||||
return $schema;
|
||||
}
|
||||
}
|
179
lib/Service/MatrixService.php
Normal file
179
lib/Service/MatrixService.php
Normal file
|
@ -0,0 +1,179 @@
|
|||
<?php
|
||||
|
||||
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 MatrixClient */
|
||||
private $client;
|
||||
|
||||
/** @var string */
|
||||
private $registrationSecret = "~d9fJpPKDZIV67A7=tPCvok:=fTBLV;MFf=9FRxtAazW@-GwSo";
|
||||
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace OCA\UPschooling\Service;
|
||||
|
||||
use Exception;
|
||||
|
||||
use OCP\AppFramework\Db\DoesNotExistException;
|
||||
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
||||
|
||||
use OCA\UPschooling\Db\Note;
|
||||
use OCA\UPschooling\Db\NoteMapper;
|
||||
|
||||
class NoteService {
|
||||
|
||||
/** @var NoteMapper */
|
||||
private $mapper;
|
||||
|
||||
public function __construct(NoteMapper $mapper) {
|
||||
$this->mapper = $mapper;
|
||||
}
|
||||
|
||||
public function findAll(string $userId): array {
|
||||
return $this->mapper->findAll($userId);
|
||||
}
|
||||
|
||||
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) {
|
||||
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 Note();
|
||||
$note->setTitle($title);
|
||||
$note->setContent($content);
|
||||
$note->setUserId($userId);
|
||||
return $this->mapper->insert($note);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public function delete($id, $userId) {
|
||||
try {
|
||||
$note = $this->mapper->find($id, $userId);
|
||||
$this->mapper->delete($note);
|
||||
return $note;
|
||||
} catch (Exception $e) {
|
||||
$this->handleException($e);
|
||||
}
|
||||
}
|
||||
}
|
116
lib/Service/TicketService.php
Normal file
116
lib/Service/TicketService.php
Normal file
|
@ -0,0 +1,116 @@
|
|||
<?php
|
||||
|
||||
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 MatrixService */
|
||||
private $matrix;
|
||||
|
||||
/** @var TicketMapper */
|
||||
private $ticketMapper;
|
||||
|
||||
/** @var UserMapper */
|
||||
private $userMapper;
|
||||
|
||||
public function __construct(MatrixService $matrix, TicketMapper $ticketMapper, UserMapper $userMapper) {
|
||||
$this->matrix = $matrix;
|
||||
$this->ticketMapper = $ticketMapper;
|
||||
$this->userMapper = $userMapper;
|
||||
}
|
||||
|
||||
public function findAll(string $userId): array {
|
||||
$dbTickets = $this->ticketMapper->findAll($this->getOrCreateUser($userId));
|
||||
return array_map(function ($ticket) { return $this->resolveTicket($ticket); }, $dbTickets);
|
||||
}
|
||||
|
||||
public function find($id, $userId): array {
|
||||
return $this->resolveTicket($this->ticketMapper->find($id, $this->getOrCreateUser($userId)));
|
||||
}
|
||||
|
||||
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,
|
||||
"status" => "open",
|
||||
"version" => "1",
|
||||
));
|
||||
$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) {
|
||||
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 {
|
||||
$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;
|
||||
}
|
||||
}
|
||||
}
|
152
package-lock.json
generated
152
package-lock.json
generated
|
@ -7,7 +7,7 @@
|
|||
"": {
|
||||
"name": "upschooling",
|
||||
"version": "19.0.0",
|
||||
"license": "agpl",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@nextcloud/axios": "^1.6.0",
|
||||
"@nextcloud/dialogs": "^3.1.2",
|
||||
|
@ -2942,17 +2942,6 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bindings": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
||||
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"file-uri-to-path": "1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bluebird": {
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
|
||||
|
@ -6157,14 +6146,6 @@
|
|||
"node": "^10.12.0 || >=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/file-uri-to-path": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
|
||||
|
@ -6450,21 +6431,6 @@
|
|||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
|
@ -9046,14 +9012,6 @@
|
|||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/nan": {
|
||||
"version": "2.14.2",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz",
|
||||
"integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/nanomatch": {
|
||||
"version": "1.2.13",
|
||||
"resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
|
||||
|
@ -10327,6 +10285,7 @@
|
|||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
|
||||
"integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=",
|
||||
"deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
|
@ -11549,9 +11508,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/sockjs-client": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.5.1.tgz",
|
||||
"integrity": "sha512-VnVAb663fosipI/m6pqRXakEOw7nvd7TUgdr3PlR/8V2I95QIdwT8L4nMxhyU8SmDBHYXU1TOElaKOmKLfYzeQ==",
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.5.2.tgz",
|
||||
"integrity": "sha512-ZzRxPBISQE7RpzlH4tKJMQbHM9pabHluk0WBaxAQ+wm/UieeBVBou0p4wVnSQGN9QmpAZygQ0cDIypWuqOFmFQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
|
@ -11560,7 +11519,7 @@
|
|||
"faye-websocket": "^0.11.3",
|
||||
"inherits": "^2.0.4",
|
||||
"json3": "^3.3.3",
|
||||
"url-parse": "^1.5.1"
|
||||
"url-parse": "^1.5.3"
|
||||
}
|
||||
},
|
||||
"node_modules/sockjs-client/node_modules/debug": {
|
||||
|
@ -12030,9 +11989,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/striptags": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/striptags/-/striptags-3.1.1.tgz",
|
||||
"integrity": "sha1-yMPn/db7S7OjKjt1LltePjgJPr0="
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/striptags/-/striptags-3.2.0.tgz",
|
||||
"integrity": "sha512-g45ZOGzHDMe2bdYMdIvdAfCQkCTDMGBazSw1ypMowwGIee7ZQ5dU0rBJ8Jqgl+jAKIv4dbeE1jscZq9wid1Tkw=="
|
||||
},
|
||||
"node_modules/style-loader": {
|
||||
"version": "2.0.0",
|
||||
|
@ -13091,9 +13050,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/url-parse": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.1.tgz",
|
||||
"integrity": "sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q==",
|
||||
"version": "1.5.3",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.3.tgz",
|
||||
"integrity": "sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
|
@ -13810,26 +13769,6 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-dev-server/node_modules/fsevents": {
|
||||
"version": "1.2.13",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
|
||||
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
|
||||
"deprecated": "fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"bindings": "^1.5.0",
|
||||
"nan": "^2.12.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-dev-server/node_modules/glob-parent": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
|
||||
|
@ -16628,17 +16567,6 @@
|
|||
"integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==",
|
||||
"devOptional": true
|
||||
},
|
||||
"bindings": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
||||
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"file-uri-to-path": "1.0.0"
|
||||
}
|
||||
},
|
||||
"bluebird": {
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
|
||||
|
@ -19218,14 +19146,6 @@
|
|||
"flat-cache": "^3.0.4"
|
||||
}
|
||||
},
|
||||
"file-uri-to-path": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"fill-range": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
|
||||
|
@ -19441,14 +19361,6 @@
|
|||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
|
||||
},
|
||||
"fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"function-bind": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
|
@ -21416,14 +21328,6 @@
|
|||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"nan": {
|
||||
"version": "2.14.2",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz",
|
||||
"integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"nanomatch": {
|
||||
"version": "1.2.13",
|
||||
"resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
|
||||
|
@ -23405,9 +23309,9 @@
|
|||
}
|
||||
},
|
||||
"sockjs-client": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.5.1.tgz",
|
||||
"integrity": "sha512-VnVAb663fosipI/m6pqRXakEOw7nvd7TUgdr3PlR/8V2I95QIdwT8L4nMxhyU8SmDBHYXU1TOElaKOmKLfYzeQ==",
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.5.2.tgz",
|
||||
"integrity": "sha512-ZzRxPBISQE7RpzlH4tKJMQbHM9pabHluk0WBaxAQ+wm/UieeBVBou0p4wVnSQGN9QmpAZygQ0cDIypWuqOFmFQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
|
@ -23416,7 +23320,7 @@
|
|||
"faye-websocket": "^0.11.3",
|
||||
"inherits": "^2.0.4",
|
||||
"json3": "^3.3.3",
|
||||
"url-parse": "^1.5.1"
|
||||
"url-parse": "^1.5.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
|
@ -23818,9 +23722,9 @@
|
|||
"peer": true
|
||||
},
|
||||
"striptags": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/striptags/-/striptags-3.1.1.tgz",
|
||||
"integrity": "sha1-yMPn/db7S7OjKjt1LltePjgJPr0="
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/striptags/-/striptags-3.2.0.tgz",
|
||||
"integrity": "sha512-g45ZOGzHDMe2bdYMdIvdAfCQkCTDMGBazSw1ypMowwGIee7ZQ5dU0rBJ8Jqgl+jAKIv4dbeE1jscZq9wid1Tkw=="
|
||||
},
|
||||
"style-loader": {
|
||||
"version": "2.0.0",
|
||||
|
@ -24631,9 +24535,9 @@
|
|||
}
|
||||
},
|
||||
"url-parse": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.1.tgz",
|
||||
"integrity": "sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q==",
|
||||
"version": "1.5.3",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.3.tgz",
|
||||
"integrity": "sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
|
@ -25175,18 +25079,6 @@
|
|||
"locate-path": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"fsevents": {
|
||||
"version": "1.2.13",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
|
||||
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"bindings": "^1.5.0",
|
||||
"nan": "^2.12.1"
|
||||
}
|
||||
},
|
||||
"glob-parent": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
|
||||
|
|
13
package.json
13
package.json
|
@ -2,19 +2,20 @@
|
|||
"name": "upschooling",
|
||||
"description": "A simple Nextcloud app tutorial for building a notes app",
|
||||
"version": "19.0.0",
|
||||
"author": "Julius Härtl <jus@bitgrid.net",
|
||||
"author": "UPschooling",
|
||||
"contributors": [
|
||||
"John Molakvoæ <skjnldsv@protonmail.com>"
|
||||
"Finn",
|
||||
"Ben"
|
||||
],
|
||||
"bugs": {
|
||||
"url": "https://github.com/nextcloud/app-tutorial/issues"
|
||||
"url": "https://gitea.rs485.network/UPschooling/Nextcloud-App/issues"
|
||||
},
|
||||
"repository": {
|
||||
"url": "https://github.com/nextcloud/app-tutorial",
|
||||
"url": "https://gitea.rs485.network/UPschooling/Nextcloud-App.git",
|
||||
"type": "git"
|
||||
},
|
||||
"homepage": "https://github.com/nextcloud/app-tutorial",
|
||||
"license": "agpl",
|
||||
"homepage": "https://gitea.rs485.network/UPschooling/Nextcloud-App",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "NODE_ENV=production webpack --progress --config webpack.js",
|
||||
|
|
|
@ -5,10 +5,67 @@ IFS=$'\n\t'
|
|||
|
||||
DIR="${0%/*}"
|
||||
|
||||
podman run -d --name=nextcloud --replace=true -p 8080:80 -v "$DIR:/var/www/html/custom_apps/upschooling" docker.io/nextcloud
|
||||
# replace containers
|
||||
podman rm -if synapse
|
||||
podman rm -if 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'
|
||||
podman exec --user 33 nextcloud php occ maintenance:install --database sqlite --admin-user admin --admin-pass admin
|
||||
podman exec --user 33 nextcloud php occ config:system:set --value=true --type=boolean debug
|
||||
podman exec --user 33 nextcloud php occ app:enable --force upschooling
|
||||
|
||||
if podman volume exists synapse-data; then
|
||||
echo "Found existing synapse-data volume"
|
||||
else
|
||||
podman run --rm \
|
||||
--name=synapse \
|
||||
--hostname synapse \
|
||||
"--mount=type=volume,src=synapse-data,dst=/data" \
|
||||
-e SYNAPSE_SERVER_NAME=synapse \
|
||||
-e SYNAPSE_REPORT_STATS=no \
|
||||
docker.io/matrixdotorg/synapse \
|
||||
generate
|
||||
echo "Generated fresh synapse-data volume"
|
||||
fi
|
||||
|
||||
podman run -d \
|
||||
--name=synapse \
|
||||
"--mount=type=volume,src=synapse-data,dst=/data" \
|
||||
"--network=container:$(podman inspect --format "{{.Id}}" nextcloud)" \
|
||||
--hostname synapse \
|
||||
docker.io/matrixdotorg/synapse
|
||||
|
||||
# wait for synapse to start
|
||||
MAX_TRIES=15
|
||||
for ((i = 0 ; i < $MAX_TRIES ; i++)); do
|
||||
if podman logs synapse 2>&1 | grep -q "Synapse now listening on TCP port 8008"; then
|
||||
echo -e "Synapse has started. \e[1;38;5;2mOK\033[0m"
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
if [[ $i -ge $MAX_TRIES ]]; then
|
||||
echo "Synapse did not start in time! Use \`podman logs synapse\` to investigate"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set +e
|
||||
REGISTER_USER_OUTPUT="$(podman exec synapse register_new_matrix_user -u upschooling -p secret -a -c /data/homeserver.yaml http://localhost:8008)"
|
||||
REGISTER_USER_SUCCESS=$?
|
||||
set -e
|
||||
|
||||
if [[ "$REGISTER_USER_SUCCESS" != "0" ]]; then
|
||||
if echo $REGISTER_USER_OUTPUT | grep -q "User ID already taken."; then
|
||||
echo -e "User @upschooling:synapse already exists. \e[1;38;5;2mOK\033[0m"
|
||||
else
|
||||
echo "Could not create user @upschooling:synapse"
|
||||
echo $REGISTER_USER_OUTPUT
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo -e "Matrix user @upschooling:synapse created. \e[1;38;5;2mOK\033[0m"
|
||||
fi
|
||||
|
|
253
src/App.vue
253
src/App.vue
|
@ -1,214 +1,103 @@
|
|||
<template>
|
||||
<div id="content" class="app-upschooling">
|
||||
<AppNavigation>
|
||||
<AppNavigationNew v-if="!loading"
|
||||
:text="t('upschooling', 'New note')"
|
||||
:disabled="false"
|
||||
button-id="new-upschooling-button"
|
||||
button-class="icon-add"
|
||||
@click="newNote" />
|
||||
<ul>
|
||||
<AppNavigationItem v-for="note in notes"
|
||||
:key="note.id"
|
||||
:title="note.title ? note.title : t('upschooling', 'New note')"
|
||||
:class="{active: currentNoteId === note.id}"
|
||||
@click="openNote(note)">
|
||||
<template slot="actions">
|
||||
<ActionButton v-if="note.id === -1"
|
||||
icon="icon-close"
|
||||
@click="cancelNewNote(note)">
|
||||
{{ t('upschooling', 'Cancel note creation') }}
|
||||
</ActionButton>
|
||||
<ActionButton v-else
|
||||
icon="icon-delete"
|
||||
@click="deleteNote(note)">
|
||||
{{ t('upschooling', 'Delete note') }}
|
||||
</ActionButton>
|
||||
</template>
|
||||
</AppNavigationItem>
|
||||
</ul>
|
||||
</AppNavigation>
|
||||
<AppNavigation />
|
||||
<AppContent>
|
||||
<div v-if="currentNote">
|
||||
<input ref="title"
|
||||
v-model="currentNote.title"
|
||||
type="text"
|
||||
:disabled="updating">
|
||||
<textarea ref="content" v-model="currentNote.content" :disabled="updating" />
|
||||
<input type="button"
|
||||
class="primary"
|
||||
:value="t('upschooling', 'Save')"
|
||||
:disabled="updating || !savePossible"
|
||||
@click="saveNote">
|
||||
<div v-if="currentTicket">
|
||||
<Ticket :ticket="currentTicket" @save-ticket="saveTicket" @show-ticket-list="deselectTicket" />
|
||||
</div>
|
||||
<div v-else id="emptycontent">
|
||||
<div class="icon-file" />
|
||||
<h2>{{ t('upschooling', 'Create a note to get started') }}</h2>
|
||||
<div v-else>
|
||||
<TicketList :tickets="tickets" @open-ticket="openTicket" />
|
||||
</div>
|
||||
</AppContent>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
|
||||
import AppContent from '@nextcloud/vue/dist/Components/AppContent'
|
||||
import AppNavigation from '@nextcloud/vue/dist/Components/AppNavigation'
|
||||
import AppNavigationItem from '@nextcloud/vue/dist/Components/AppNavigationItem'
|
||||
import AppNavigationNew from '@nextcloud/vue/dist/Components/AppNavigationNew'
|
||||
|
||||
import TicketList from './components/TicketList'
|
||||
import Ticket from './Ticket'
|
||||
import '@nextcloud/dialogs/styles/toast.scss'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import { showError, showSuccess } from '@nextcloud/dialogs'
|
||||
import axios from '@nextcloud/axios'
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
ActionButton,
|
||||
Ticket,
|
||||
TicketList,
|
||||
AppContent,
|
||||
AppNavigation,
|
||||
AppNavigationItem,
|
||||
AppNavigationNew,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
notes: [],
|
||||
currentNoteId: null,
|
||||
updating: false,
|
||||
loading: true,
|
||||
/**
|
||||
* Return the currently selected ticket object or null, if none is selected.
|
||||
*
|
||||
* @type {object|null|undefined}
|
||||
*/
|
||||
currentTicket: undefined,
|
||||
|
||||
ticketsFetched: false,
|
||||
tickets: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
/**
|
||||
* Return the currently selected note object
|
||||
* @returns {Object|null}
|
||||
*/
|
||||
currentNote() {
|
||||
if (this.currentNoteId === null) {
|
||||
return null
|
||||
}
|
||||
return this.notes.find((note) => note.id === this.currentNoteId)
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if a note is selected and its title is not empty
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
savePossible() {
|
||||
return this.currentNote && this.currentNote.title !== ''
|
||||
},
|
||||
watch: {
|
||||
$route: 'fetchTickets',
|
||||
},
|
||||
/**
|
||||
* Fetch list of notes when the component is loaded
|
||||
*/
|
||||
async mounted() {
|
||||
try {
|
||||
const response = await axios.get(generateUrl('/apps/upschooling/notes'))
|
||||
this.notes = response.data
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
showError(t('upschooling', 'Could not fetch notes'))
|
||||
}
|
||||
this.loading = false
|
||||
created() {
|
||||
this.createExampleContent()
|
||||
this.fetchTickets()
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Create a new note and focus the note content field automatically
|
||||
* @param {Object} note Note object
|
||||
*/
|
||||
openNote(note) {
|
||||
if (this.updating) {
|
||||
return
|
||||
}
|
||||
this.currentNoteId = note.id
|
||||
this.$nextTick(() => {
|
||||
this.$refs.content.focus()
|
||||
})
|
||||
},
|
||||
/**
|
||||
* Action tiggered when clicking the save button
|
||||
* create a new note or save
|
||||
*/
|
||||
saveNote() {
|
||||
if (this.currentNoteId === -1) {
|
||||
this.createNote(this.currentNote)
|
||||
} else {
|
||||
this.updateNote(this.currentNote)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Create a new note and focus the note content field automatically
|
||||
* The note is not yet saved, therefore an id of -1 is used until it
|
||||
* has been persisted in the backend
|
||||
*/
|
||||
newNote() {
|
||||
if (this.currentNoteId !== -1) {
|
||||
this.currentNoteId = -1
|
||||
this.notes.push({
|
||||
id: -1,
|
||||
title: '',
|
||||
content: '',
|
||||
})
|
||||
this.$nextTick(() => {
|
||||
this.$refs.title.focus()
|
||||
})
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Abort creating a new note
|
||||
*/
|
||||
cancelNewNote() {
|
||||
this.notes.splice(this.notes.findIndex((note) => note.id === -1), 1)
|
||||
this.currentNoteId = null
|
||||
},
|
||||
/**
|
||||
* Create a new note by sending the information to the server
|
||||
* @param {Object} note Note object
|
||||
*/
|
||||
async createNote(note) {
|
||||
this.updating = true
|
||||
try {
|
||||
const response = await axios.post(generateUrl('/apps/upschooling/notes'), note)
|
||||
const index = this.notes.findIndex((match) => match.id === this.currentNoteId)
|
||||
this.$set(this.notes, index, response.data)
|
||||
this.currentNoteId = response.data.id
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
showError(t('upschooling', 'Could not create the note'))
|
||||
}
|
||||
this.updating = false
|
||||
},
|
||||
/**
|
||||
* Update an existing note on the server
|
||||
* @param {Object} note Note object
|
||||
*/
|
||||
async updateNote(note) {
|
||||
this.updating = true
|
||||
try {
|
||||
await axios.put(generateUrl(`/apps/upschooling/notes/${note.id}`), note)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
showError(t('upschooling', 'Could not update the note'))
|
||||
}
|
||||
this.updating = false
|
||||
},
|
||||
/**
|
||||
* Delete a note, remove it from the frontend and show a hint
|
||||
* @param {Object} note Note object
|
||||
*/
|
||||
async deleteNote(note) {
|
||||
try {
|
||||
await axios.delete(generateUrl(`/apps/upschooling/notes/${note.id}`))
|
||||
this.notes.splice(this.notes.indexOf(note), 1)
|
||||
if (this.currentNoteId === note.id) {
|
||||
this.currentNoteId = null
|
||||
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)
|
||||
}
|
||||
showSuccess(t('upschooling', 'Note deleted'))
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
showError(t('upschooling', 'Could not delete the note'))
|
||||
}
|
||||
}).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 = response.data
|
||||
console.debug(this.tickets) // FIXME
|
||||
} else {
|
||||
console.debug('Empty ticket list :(')
|
||||
}
|
||||
} else {
|
||||
console.error('API did not return array: ', response)
|
||||
}
|
||||
}).catch(console.error)
|
||||
},
|
||||
saveTicket(ticketId, data) {
|
||||
// TODO send to API (dont forget permission check in API)
|
||||
console.debug('upschooling', 'saveTicket', ticketId, data)
|
||||
},
|
||||
openTicket(ticketId) {
|
||||
this.currentTicket = this.tickets.find((obj) => obj.ticketId === ticketId)
|
||||
},
|
||||
deselectTicket() {
|
||||
this.currentTicket = null
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
78
src/Ticket.vue
Normal file
78
src/Ticket.vue
Normal file
|
@ -0,0 +1,78 @@
|
|||
<template>
|
||||
<div class="single-ticket">
|
||||
<div class="header-bar">
|
||||
<button @click="back">
|
||||
{{ t('upschooling', 'Ticket Schließen') }}
|
||||
</button>
|
||||
<button @click="save">
|
||||
{{ t('upschooling', 'Speichern') }}
|
||||
</button>
|
||||
</div>
|
||||
<h2>Ticket "ding"</h2>
|
||||
<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">
|
||||
{{ t('upschooling', 'Speichern') }}
|
||||
</button>
|
||||
<hr>
|
||||
<div class="placeholder" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import KeyValueTable from './components/KeyValueTable'
|
||||
export default {
|
||||
name: 'Ticket',
|
||||
components: { KeyValueTable },
|
||||
props: {
|
||||
ticket: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
description: this.ticket.description,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toLocaleDate(timestamp) {
|
||||
const date = new Date(timestamp)
|
||||
return date.toLocaleString()
|
||||
},
|
||||
|
||||
save() {
|
||||
this.$emit('save-ticket', this.ticket.ticketId, {}) // TODO: give it only the changed data
|
||||
},
|
||||
|
||||
back() {
|
||||
this.$emit('show-ticket-list')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
textarea {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
height: 400px;
|
||||
width: 100%;
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
.header-bar {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
</style>
|
34
src/components/HeaderlessKeyValueTable.vue
Normal file
34
src/components/HeaderlessKeyValueTable.vue
Normal file
|
@ -0,0 +1,34 @@
|
|||
<template>
|
||||
<table>
|
||||
<tr v-for="(value, key, i) in dataRows" :key="i">
|
||||
<td>{{ key }}</td>
|
||||
<td>{{ value }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'HeaderlessKeyValueTable',
|
||||
props: {
|
||||
dataRows: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
td {
|
||||
border-bottom: solid #000 1px;
|
||||
}
|
||||
|
||||
tr:nth-child(2n) {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
</style>
|
63
src/components/KeyValueTable.vue
Normal file
63
src/components/KeyValueTable.vue
Normal file
|
@ -0,0 +1,63 @@
|
|||
<template>
|
||||
<table>
|
||||
<thead>
|
||||
<tr v-for="(value, key, i) in getHeaders" :key="i">
|
||||
<th>{{ key }}</th>
|
||||
<th>{{ value }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(value, key, i) in dataRows" :key="i+1">
|
||||
<td>{{ key }}</td>
|
||||
<td>{{ value }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'KeyValueTable',
|
||||
props: {
|
||||
dataRows: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
header: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
getHeaders() {
|
||||
const key = Object.keys(this.header)[0]
|
||||
const value = this.header[key]
|
||||
if (key && value) return { [key]: value }
|
||||
|
||||
return {}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
td, th {
|
||||
border-bottom: solid #000 1px;
|
||||
}
|
||||
|
||||
th {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
thead {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
tbody tr:nth-child(2n) {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
</style>
|
103
src/components/TicketList.vue
Normal file
103
src/components/TicketList.vue
Normal file
|
@ -0,0 +1,103 @@
|
|||
<template>
|
||||
<table
|
||||
id="ticketlist"
|
||||
class="list-container has-controls">
|
||||
<thead>
|
||||
<tr>
|
||||
<th id="headerName" class="column-name">
|
||||
<div id="headerName-container">
|
||||
<a class="name sort columntitle" data-sort="name">
|
||||
<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>{{ 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>{{ 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.ticketId">
|
||||
<td class="filename ui-draggable ui-draggable-handle">
|
||||
<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.ticketId }})</span>
|
||||
</span>
|
||||
</a>
|
||||
</td>
|
||||
<td class="status">
|
||||
{{ item.status }}
|
||||
</td>
|
||||
<td class="date">
|
||||
<span
|
||||
class="modified live-relative-timestamp"
|
||||
:title="toLocaleDate(item.lastModified)"
|
||||
:data-timestamp="item.lastModified"
|
||||
style="color:rgb(81,81,81)">
|
||||
{{ toLocaleDate(item.lastModified) }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'TicketList',
|
||||
props: {
|
||||
tickets: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toLocaleDate(timestamp) {
|
||||
const date = new Date(timestamp)
|
||||
return date.toLocaleString()
|
||||
},
|
||||
openTicket(ticketId) {
|
||||
this.$emit('open-ticket', ticketId)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#ticketlist {
|
||||
margin-top: 4rem;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
td {
|
||||
border-bottom: solid #000 1px;
|
||||
}
|
||||
|
||||
tr:nth-child(2n) {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
a .ticket-number {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
a:hover .ticket-number, a:focus .ticket-number, a:active .ticket-number {
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
|
@ -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\Note;
|
||||
use OCA\UPschooling\Db\NoteMapper;
|
||||
use OCA\UPschooling\Controller\NoteController;
|
||||
|
||||
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(NoteController::class);
|
||||
$this->mapper = $container->query(NoteMapper::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 Note();
|
||||
$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 = Note::fromRow([
|
||||
$updatedNote = MatrixTicket::fromRow([
|
||||
'id' => $id,
|
||||
'user_id' => $this->userId
|
||||
]);
|
49
tests/Integration/MatrixUserIntegrationTest.php
Normal file
49
tests/Integration/MatrixUserIntegrationTest.php
Normal 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());
|
||||
}
|
||||
}
|
|
@ -2,11 +2,55 @@
|
|||
|
||||
namespace OCA\UPschooling\Tests\Unit\Controller;
|
||||
|
||||
use OCA\UPschooling\Controller\NoteApiController;
|
||||
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();
|
||||
$this->controller = new NoteApiController($this->request, $this->service, $this->userId);
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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\NoteService;
|
||||
use OCA\UPschooling\Controller\NoteController;
|
||||
|
||||
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(NoteService::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$this->controller = new NoteController($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());
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -1,45 +1,61 @@
|
|||
<?php
|
||||
|
||||
namespace OCA\UPschooling\Tests\Unit\Service;
|
||||
|
||||
use OCA\UPschooling\Service\NoteNotFound;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
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\TicketService;
|
||||
use OCP\AppFramework\Db\DoesNotExistException;
|
||||
|
||||
use OCA\UPschooling\Db\Note;
|
||||
use OCA\UPschooling\Service\NoteService;
|
||||
use OCA\UPschooling\Db\NoteMapper;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\NullLogger;
|
||||
|
||||
class NoteServiceTest extends TestCase {
|
||||
|
||||
/** @var TicketService */
|
||||
private $service;
|
||||
private $mapper;
|
||||
|
||||
/** @var MatrixService */
|
||||
private $matrixService;
|
||||
|
||||
/** @var TicketMapper */
|
||||
private $ticketMapper;
|
||||
|
||||
/** @var UserMapper */
|
||||
private $userMapper;
|
||||
|
||||
/** @var string */
|
||||
private $userId = 'john';
|
||||
|
||||
public function setUp(): void {
|
||||
$this->mapper = $this->getMockBuilder(NoteMapper::class)
|
||||
$this->ticketMapper = $this->getMockBuilder(TicketMapper::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$this->service = new NoteService($this->mapper);
|
||||
$this->userMapper = $this->getMockBuilder(UserMapper::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$this->matrixService = new MatrixService(new NullLogger());
|
||||
$this->service = new TicketService($this->matrixService, $this->ticketMapper, $this->userMapper);
|
||||
}
|
||||
|
||||
public function testUpdate() {
|
||||
// the existing note
|
||||
$note = Note::fromRow([
|
||||
$note = MatrixTicket::fromRow([
|
||||
'id' => 3,
|
||||
'title' => 'yo',
|
||||
'content' => 'nope'
|
||||
]);
|
||||
$this->mapper->expects($this->once())
|
||||
$this->ticketMapper->expects($this->once())
|
||||
->method('find')
|
||||
->with($this->equalTo(3))
|
||||
->will($this->returnValue($note));
|
||||
|
||||
// the note when updated
|
||||
$updatedNote = Note::fromRow(['id' => 3]);
|
||||
$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));
|
||||
|
@ -50,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('')));
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
<?php
|
||||
|
||||
require_once __DIR__ . '/../../../tests/bootstrap.php';
|
||||
require_once __DIR__ . '/../tests/bootstrap.php';
|
||||
|
|
Loading…
Reference in a new issue