diff --git a/composer.json b/composer.json
index dfe31c11aba27c0eb351111ad07abe651d6f7b29..50b3f8a6ec937cd12206e702991703900f633589 100644
--- a/composer.json
+++ b/composer.json
@@ -9,17 +9,16 @@
         "camspiers/json-pretty": "~1.0.2",
         "monolog/monolog": "~1.21.0",
         "php-http/curl-client": "~1.7.0",
-        "woohoolabs/yang": "~0.9.0",
+        "woohoolabs/yang": "2.3.2",
         "codeception/codeception": "~4.1.21",
         "codeception/module-asserts": "^1.3"
     },
     "require": {
         "php": "^7.2",
         "guzzlehttp/psr7": "~1.4.2",
-        "neomerx/json-api": "~1.0.9",
-        "slim/slim": "~3.12.3",
+        "neomerx/json-api": "4.0.1",
         "spomky-labs/otphp": "^8.3.3",
-        "tuupola/cors-middleware": "~0.5.2",
+        "tuupola/cors-middleware": "1.2.1",
         "tecnickcom/tcpdf": "^6.3",
         "scssphp/scssphp": "^1.4",
         "symfony/yaml": "^3.4",
@@ -43,6 +42,9 @@
         "ext-pcre": "*",
         "ext-pdo": "*",
         "ext-mbstring": "*",
-        "opis/json-schema": "^1.0"
+        "opis/json-schema": "^1.0",
+        "slim/psr7": "1.4",
+        "slim/slim": "4.7.1",
+        "php-di/php-di": "6.3.4"
     }
 }
diff --git a/composer.lock b/composer.lock
index 0532937da51c366b8db4fd5e52a8cd7c28f8a3b1..440bb705a837ef291243ddaeb13a2e385cdf13cf 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "dc13abed7b7557def19c639c0b91cb4f",
+    "content-hash": "98af1effd6c2da72cc51ac552e851451",
     "packages": [
         {
             "name": "algo26-matthias/idna-convert",
@@ -274,6 +274,62 @@
             },
             "time": "2020-06-29T00:56:53+00:00"
         },
+        {
+            "name": "fig/http-message-util",
+            "version": "1.1.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/http-message-util.git",
+                "reference": "9d94dc0154230ac39e5bf89398b324a86f63f765"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/http-message-util/zipball/9d94dc0154230ac39e5bf89398b324a86f63f765",
+                "reference": "9d94dc0154230ac39e5bf89398b324a86f63f765",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.3 || ^7.0 || ^8.0"
+            },
+            "suggest": {
+                "psr/http-message": "The package containing the PSR-7 interfaces"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Fig\\Http\\Message\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "https://www.php-fig.org/"
+                }
+            ],
+            "description": "Utility classes and constants for use with PSR-7 (psr/http-message)",
+            "keywords": [
+                "http",
+                "http-message",
+                "psr",
+                "psr-7",
+                "request",
+                "response"
+            ],
+            "support": {
+                "issues": "https://github.com/php-fig/http-message-util/issues",
+                "source": "https://github.com/php-fig/http-message-util/tree/1.1.5"
+            },
+            "time": "2020-11-24T22:02:12+00:00"
+        },
         {
             "name": "gossi/docblock",
             "version": "v1.6",
@@ -657,36 +713,37 @@
         },
         {
             "name": "neomerx/json-api",
-            "version": "v1.0.9",
+            "version": "v4.0.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/neomerx/json-api.git",
-                "reference": "c911b7494496e79f9de72ee40d6a2b791caaf95b"
+                "reference": "0e45254a4574a3118e0ed663312b43aca23b89c7"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/neomerx/json-api/zipball/c911b7494496e79f9de72ee40d6a2b791caaf95b",
-                "reference": "c911b7494496e79f9de72ee40d6a2b791caaf95b",
+                "url": "https://api.github.com/repos/neomerx/json-api/zipball/0e45254a4574a3118e0ed663312b43aca23b89c7",
+                "reference": "0e45254a4574a3118e0ed663312b43aca23b89c7",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.5.0",
-                "psr/http-message": "^1.0",
-                "psr/log": "^1.0"
+                "php": ">=7.1.0"
             },
             "require-dev": {
-                "mockery/mockery": "~0.9.4",
-                "monolog/monolog": "^1.18",
+                "friendsofphp/php-cs-fixer": "^2.14",
+                "mockery/mockery": "^1.0",
                 "phpmd/phpmd": "^2.6",
-                "phpunit/phpunit": "^4.6 || ^5.0 || ^6.0",
-                "scrutinizer/ocular": "^1.3",
-                "squizlabs/php_codesniffer": "^2.5"
+                "phpunit/phpunit": "^7.0",
+                "scrutinizer/ocular": "^1.4",
+                "squizlabs/php_codesniffer": "^2.9"
             },
             "type": "library",
             "autoload": {
                 "psr-4": {
                     "Neomerx\\JsonApi\\": "src/"
-                }
+                },
+                "files": [
+                    "src/I18n/format.php"
+                ]
             },
             "notification-url": "https://packagist.org/downloads/",
             "license": [
@@ -710,9 +767,9 @@
             ],
             "support": {
                 "issues": "https://github.com/neomerx/json-api/issues",
-                "source": "https://github.com/neomerx/json-api/tree/v1.x"
+                "source": "https://github.com/neomerx/json-api/tree/develop"
             },
-            "time": "2018-02-21T13:45:30+00:00"
+            "time": "2020-03-03T05:56:54+00:00"
         },
         {
             "name": "nikic/fast-route",
@@ -764,6 +821,71 @@
             },
             "time": "2018-02-13T20:26:39+00:00"
         },
+        {
+            "name": "opis/closure",
+            "version": "3.6.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/opis/closure.git",
+                "reference": "06e2ebd25f2869e54a306dda991f7db58066f7f6"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/opis/closure/zipball/06e2ebd25f2869e54a306dda991f7db58066f7f6",
+                "reference": "06e2ebd25f2869e54a306dda991f7db58066f7f6",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.4 || ^7.0 || ^8.0"
+            },
+            "require-dev": {
+                "jeremeamia/superclosure": "^2.0",
+                "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.6.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Opis\\Closure\\": "src/"
+                },
+                "files": [
+                    "functions.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Marius Sarca",
+                    "email": "marius.sarca@gmail.com"
+                },
+                {
+                    "name": "Sorin Sarca",
+                    "email": "sarca_sorin@hotmail.com"
+                }
+            ],
+            "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.",
+            "homepage": "https://opis.io/closure",
+            "keywords": [
+                "anonymous functions",
+                "closure",
+                "function",
+                "serializable",
+                "serialization",
+                "serialize"
+            ],
+            "support": {
+                "issues": "https://github.com/opis/closure/issues",
+                "source": "https://github.com/opis/closure/tree/3.6.2"
+            },
+            "time": "2021-04-09T13:42:10+00:00"
+        },
         {
             "name": "opis/json-schema",
             "version": "1.1.0",
@@ -1053,6 +1175,171 @@
             },
             "time": "2018-08-04T14:22:05+00:00"
         },
+        {
+            "name": "php-di/invoker",
+            "version": "2.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/PHP-DI/Invoker.git",
+                "reference": "540c27c86f663e20fe39a24cd72fa76cdb21d41a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/540c27c86f663e20fe39a24cd72fa76cdb21d41a",
+                "reference": "540c27c86f663e20fe39a24cd72fa76cdb21d41a",
+                "shasum": ""
+            },
+            "require": {
+                "psr/container": "~1.0"
+            },
+            "require-dev": {
+                "athletic/athletic": "~0.1.8",
+                "phpunit/phpunit": "~4.5"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Invoker\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "description": "Generic and extensible callable invoker",
+            "homepage": "https://github.com/PHP-DI/Invoker",
+            "keywords": [
+                "callable",
+                "dependency",
+                "dependency-injection",
+                "injection",
+                "invoke",
+                "invoker"
+            ],
+            "support": {
+                "issues": "https://github.com/PHP-DI/Invoker/issues",
+                "source": "https://github.com/PHP-DI/Invoker/tree/master"
+            },
+            "time": "2017-03-20T19:28:22+00:00"
+        },
+        {
+            "name": "php-di/php-di",
+            "version": "6.3.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/PHP-DI/PHP-DI.git",
+                "reference": "f53bcba06ab31b18e911b77c039377f4ccd1f7a5"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/f53bcba06ab31b18e911b77c039377f4ccd1f7a5",
+                "reference": "f53bcba06ab31b18e911b77c039377f4ccd1f7a5",
+                "shasum": ""
+            },
+            "require": {
+                "opis/closure": "^3.5.5",
+                "php": ">=7.2.0",
+                "php-di/invoker": "^2.0",
+                "php-di/phpdoc-reader": "^2.0.1",
+                "psr/container": "^1.0"
+            },
+            "provide": {
+                "psr/container-implementation": "^1.0"
+            },
+            "require-dev": {
+                "doctrine/annotations": "~1.2",
+                "friendsofphp/php-cs-fixer": "^2.4",
+                "mnapoli/phpunit-easymock": "^1.2",
+                "ocramius/proxy-manager": "^2.0.2",
+                "phpstan/phpstan": "^0.12",
+                "phpunit/phpunit": "^8.5|^9.0"
+            },
+            "suggest": {
+                "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)",
+                "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "DI\\": "src/"
+                },
+                "files": [
+                    "src/functions.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "description": "The dependency injection container for humans",
+            "homepage": "https://php-di.org/",
+            "keywords": [
+                "PSR-11",
+                "container",
+                "container-interop",
+                "dependency injection",
+                "di",
+                "ioc",
+                "psr11"
+            ],
+            "support": {
+                "issues": "https://github.com/PHP-DI/PHP-DI/issues",
+                "source": "https://github.com/PHP-DI/PHP-DI/tree/6.3.4"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/mnapoli",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/php-di/php-di",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2021-06-10T08:04:48+00:00"
+        },
+        {
+            "name": "php-di/phpdoc-reader",
+            "version": "2.2.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/PHP-DI/PhpDocReader.git",
+                "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c",
+                "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2.0"
+            },
+            "require-dev": {
+                "mnapoli/hard-mode": "~0.3.0",
+                "phpunit/phpunit": "^8.5|^9.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "PhpDocReader\\": "src/PhpDocReader"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)",
+            "keywords": [
+                "phpdoc",
+                "reflection"
+            ],
+            "support": {
+                "issues": "https://github.com/PHP-DI/PhpDocReader/issues",
+                "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1"
+            },
+            "time": "2020-10-12T12:39:22+00:00"
+        },
         {
             "name": "phpseclib/phpseclib",
             "version": "2.0.31",
@@ -1268,35 +1555,31 @@
             "time": "2020-03-04T10:26:33+00:00"
         },
         {
-            "name": "pimple/pimple",
-            "version": "v3.2.3",
+            "name": "psr/container",
+            "version": "1.0.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/silexphp/Pimple.git",
-                "reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32"
+                "url": "https://github.com/php-fig/container.git",
+                "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/silexphp/Pimple/zipball/9e403941ef9d65d20cba7d54e29fe906db42cf32",
-                "reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32",
+                "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
+                "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.3.0",
-                "psr/container": "^1.0"
-            },
-            "require-dev": {
-                "symfony/phpunit-bridge": "^3.2"
+                "php": ">=5.3.0"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.2.x-dev"
+                    "dev-master": "1.0.x-dev"
                 }
             },
             "autoload": {
-                "psr-0": {
-                    "Pimple": "src/"
+                "psr-4": {
+                    "Psr\\Container\\": "src/"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
@@ -1305,38 +1588,42 @@
             ],
             "authors": [
                 {
-                    "name": "Fabien Potencier",
-                    "email": "fabien@symfony.com"
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
                 }
             ],
-            "description": "Pimple, a simple Dependency Injection Container",
-            "homepage": "http://pimple.sensiolabs.org",
+            "description": "Common Container Interface (PHP FIG PSR-11)",
+            "homepage": "https://github.com/php-fig/container",
             "keywords": [
+                "PSR-11",
                 "container",
-                "dependency injection"
+                "container-interface",
+                "container-interop",
+                "psr"
             ],
             "support": {
-                "issues": "https://github.com/silexphp/Pimple/issues",
-                "source": "https://github.com/silexphp/Pimple/tree/master"
+                "issues": "https://github.com/php-fig/container/issues",
+                "source": "https://github.com/php-fig/container/tree/master"
             },
-            "time": "2018-01-21T07:42:36+00:00"
+            "time": "2017-02-14T16:28:37+00:00"
         },
         {
-            "name": "psr/container",
-            "version": "1.0.0",
+            "name": "psr/http-factory",
+            "version": "1.0.1",
             "source": {
                 "type": "git",
-                "url": "https://github.com/php-fig/container.git",
-                "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f"
+                "url": "https://github.com/php-fig/http-factory.git",
+                "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
-                "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
+                "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be",
+                "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.3.0"
+                "php": ">=7.0.0",
+                "psr/http-message": "^1.0"
             },
             "type": "library",
             "extra": {
@@ -1346,7 +1633,7 @@
             },
             "autoload": {
                 "psr-4": {
-                    "Psr\\Container\\": "src/"
+                    "Psr\\Http\\Message\\": "src/"
                 }
             },
             "notification-url": "https://packagist.org/downloads/",
@@ -1359,20 +1646,21 @@
                     "homepage": "http://www.php-fig.org/"
                 }
             ],
-            "description": "Common Container Interface (PHP FIG PSR-11)",
-            "homepage": "https://github.com/php-fig/container",
+            "description": "Common interfaces for PSR-7 HTTP message factories",
             "keywords": [
-                "PSR-11",
-                "container",
-                "container-interface",
-                "container-interop",
-                "psr"
+                "factory",
+                "http",
+                "message",
+                "psr",
+                "psr-17",
+                "psr-7",
+                "request",
+                "response"
             ],
             "support": {
-                "issues": "https://github.com/php-fig/container/issues",
-                "source": "https://github.com/php-fig/container/tree/master"
+                "source": "https://github.com/php-fig/http-factory/tree/master"
             },
-            "time": "2017-02-14T16:28:37+00:00"
+            "time": "2019-04-30T12:38:16+00:00"
         },
         {
             "name": "psr/http-message",
@@ -1427,6 +1715,120 @@
             },
             "time": "2016-08-06T14:39:51+00:00"
         },
+        {
+            "name": "psr/http-server-handler",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/http-server-handler.git",
+                "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7",
+                "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.0",
+                "psr/http-message": "^1.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Http\\Server\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for HTTP server-side request handler",
+            "keywords": [
+                "handler",
+                "http",
+                "http-interop",
+                "psr",
+                "psr-15",
+                "psr-7",
+                "request",
+                "response",
+                "server"
+            ],
+            "support": {
+                "issues": "https://github.com/php-fig/http-server-handler/issues",
+                "source": "https://github.com/php-fig/http-server-handler/tree/master"
+            },
+            "time": "2018-10-30T16:46:14+00:00"
+        },
+        {
+            "name": "psr/http-server-middleware",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/http-server-middleware.git",
+                "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/2296f45510945530b9dceb8bcedb5cb84d40c5f5",
+                "reference": "2296f45510945530b9dceb8bcedb5cb84d40c5f5",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.0",
+                "psr/http-message": "^1.0",
+                "psr/http-server-handler": "^1.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Http\\Server\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for HTTP server-side middleware",
+            "keywords": [
+                "http",
+                "http-interop",
+                "middleware",
+                "psr",
+                "psr-15",
+                "psr-7",
+                "request",
+                "response"
+            ],
+            "support": {
+                "issues": "https://github.com/php-fig/http-server-middleware/issues",
+                "source": "https://github.com/php-fig/http-server-middleware/tree/master"
+            },
+            "time": "2018-10-30T17:12:04+00:00"
+        },
         {
             "name": "psr/log",
             "version": "1.1.3",
@@ -1477,6 +1879,50 @@
             },
             "time": "2020-03-23T09:12:05+00:00"
         },
+        {
+            "name": "ralouphie/getallheaders",
+            "version": "3.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/ralouphie/getallheaders.git",
+                "reference": "120b605dfeb996808c31b6477290a714d356e822"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
+                "reference": "120b605dfeb996808c31b6477290a714d356e822",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.6"
+            },
+            "require-dev": {
+                "php-coveralls/php-coveralls": "^2.1",
+                "phpunit/phpunit": "^5 || ^6.5"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/getallheaders.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Ralph Khattar",
+                    "email": "ralph.khattar@gmail.com"
+                }
+            ],
+            "description": "A polyfill for getallheaders.",
+            "support": {
+                "issues": "https://github.com/ralouphie/getallheaders/issues",
+                "source": "https://github.com/ralouphie/getallheaders/tree/develop"
+            },
+            "time": "2019-03-08T08:55:37+00:00"
+        },
         {
             "name": "scssphp/scssphp",
             "version": "v1.4.0",
@@ -1544,36 +1990,133 @@
             },
             "time": "2020-11-07T20:53:41+00:00"
         },
+        {
+            "name": "slim/psr7",
+            "version": "1.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/slimphp/Slim-Psr7.git",
+                "reference": "0dca983ca32a26f4a91fb11173b7b9eaee29e9d6"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/slimphp/Slim-Psr7/zipball/0dca983ca32a26f4a91fb11173b7b9eaee29e9d6",
+                "reference": "0dca983ca32a26f4a91fb11173b7b9eaee29e9d6",
+                "shasum": ""
+            },
+            "require": {
+                "fig/http-message-util": "^1.1.5",
+                "php": "^7.2 || ^8.0",
+                "psr/http-factory": "^1.0",
+                "psr/http-message": "^1.0",
+                "ralouphie/getallheaders": "^3",
+                "symfony/polyfill-php80": "^1.22"
+            },
+            "provide": {
+                "psr/http-factory-implementation": "1.0",
+                "psr/http-message-implementation": "1.0"
+            },
+            "require-dev": {
+                "adriansuter/php-autoload-override": "^1.2",
+                "ext-json": "*",
+                "http-interop/http-factory-tests": "^0.9.0",
+                "php-http/psr7-integration-tests": "dev-master",
+                "phpstan/phpstan": "^0.12",
+                "phpunit/phpunit": "^8.5 || ^9.5",
+                "squizlabs/php_codesniffer": "^3.6",
+                "weirdan/prophecy-shim": "^1.0 || ^2.0.2"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Slim\\Psr7\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Josh Lockhart",
+                    "email": "hello@joshlockhart.com",
+                    "homepage": "http://joshlockhart.com"
+                },
+                {
+                    "name": "Andrew Smith",
+                    "email": "a.smith@silentworks.co.uk",
+                    "homepage": "http://silentworks.co.uk"
+                },
+                {
+                    "name": "Rob Allen",
+                    "email": "rob@akrabat.com",
+                    "homepage": "http://akrabat.com"
+                },
+                {
+                    "name": "Pierre Berube",
+                    "email": "pierre@lgse.com",
+                    "homepage": "http://www.lgse.com"
+                }
+            ],
+            "description": "Strict PSR-7 implementation",
+            "homepage": "https://www.slimframework.com",
+            "keywords": [
+                "http",
+                "psr-7",
+                "psr7"
+            ],
+            "support": {
+                "issues": "https://github.com/slimphp/Slim-Psr7/issues",
+                "source": "https://github.com/slimphp/Slim-Psr7/tree/1.4"
+            },
+            "time": "2021-05-08T18:22:56+00:00"
+        },
         {
             "name": "slim/slim",
-            "version": "3.12.3",
+            "version": "4.7.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/slimphp/Slim.git",
-                "reference": "1c9318a84ffb890900901136d620b4f03a59da38"
+                "reference": "0905e0775f8c1cfb3bbcfabeb6588dcfd8b82d3f"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/slimphp/Slim/zipball/1c9318a84ffb890900901136d620b4f03a59da38",
-                "reference": "1c9318a84ffb890900901136d620b4f03a59da38",
+                "url": "https://api.github.com/repos/slimphp/Slim/zipball/0905e0775f8c1cfb3bbcfabeb6588dcfd8b82d3f",
+                "reference": "0905e0775f8c1cfb3bbcfabeb6588dcfd8b82d3f",
                 "shasum": ""
             },
             "require": {
                 "ext-json": "*",
-                "ext-libxml": "*",
-                "ext-simplexml": "*",
-                "nikic/fast-route": "^1.0",
-                "php": ">=5.5.0",
-                "pimple/pimple": "^3.0",
+                "nikic/fast-route": "^1.3",
+                "php": "^7.2 || ^8.0",
                 "psr/container": "^1.0",
-                "psr/http-message": "^1.0"
-            },
-            "provide": {
-                "psr/http-message-implementation": "1.0"
+                "psr/http-factory": "^1.0",
+                "psr/http-message": "^1.0",
+                "psr/http-server-handler": "^1.0",
+                "psr/http-server-middleware": "^1.0",
+                "psr/log": "^1.1"
             },
             "require-dev": {
-                "phpunit/phpunit": "^4.0",
-                "squizlabs/php_codesniffer": "^2.5"
+                "adriansuter/php-autoload-override": "^1.2",
+                "ext-simplexml": "*",
+                "guzzlehttp/psr7": "^1.7",
+                "http-interop/http-factory-guzzle": "^1.0",
+                "laminas/laminas-diactoros": "^2.4",
+                "nyholm/psr7": "^1.3",
+                "nyholm/psr7-server": "^1.0.1",
+                "phpspec/prophecy": "^1.12",
+                "phpstan/phpstan": "^0.12.58",
+                "phpunit/phpunit": "^8.5.13",
+                "slim/http": "^1.2",
+                "slim/psr7": "^1.3",
+                "squizlabs/php_codesniffer": "^3.5",
+                "weirdan/prophecy-shim": "^1.0 || ^2.0.2"
+            },
+            "suggest": {
+                "ext-simplexml": "Needed to support XML format in BodyParsingMiddleware",
+                "ext-xml": "Needed to support XML format in BodyParsingMiddleware",
+                "php-di/php-di": "PHP-DI is the recommended container library to be used with Slim",
+                "slim/psr7": "Slim PSR-7 implementation. See https://www.slimframework.com/docs/v4/start/installation.html for more information."
             },
             "type": "library",
             "autoload": {
@@ -1601,6 +2144,11 @@
                     "email": "rob@akrabat.com",
                     "homepage": "http://akrabat.com"
                 },
+                {
+                    "name": "Pierre Berube",
+                    "email": "pierre@lgse.com",
+                    "homepage": "http://www.lgse.com"
+                },
                 {
                     "name": "Gabriel Manricks",
                     "email": "gmanricks@me.com",
@@ -1608,7 +2156,7 @@
                 }
             ],
             "description": "Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs",
-            "homepage": "https://slimframework.com",
+            "homepage": "https://www.slimframework.com",
             "keywords": [
                 "api",
                 "framework",
@@ -1616,10 +2164,26 @@
                 "router"
             ],
             "support": {
+                "docs": "https://www.slimframework.com/docs/v4/",
+                "forum": "https://discourse.slimframework.com/",
+                "irc": "irc://irc.freenode.net:6667/slimphp",
                 "issues": "https://github.com/slimphp/Slim/issues",
-                "source": "https://github.com/slimphp/Slim/tree/3.x"
+                "rss": "https://www.slimframework.com/blog/feed.rss",
+                "slack": "https://slimphp.slack.com/",
+                "source": "https://github.com/slimphp/Slim",
+                "wiki": "https://github.com/slimphp/Slim/wiki"
             },
-            "time": "2019-11-28T17:40:33+00:00"
+            "funding": [
+                {
+                    "url": "https://opencollective.com/slimphp",
+                    "type": "open_collective"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/slim/slim",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2020-12-01T19:41:22+00:00"
         },
         {
             "name": "spomky-labs/otphp",
@@ -1773,30 +2337,94 @@
             "version": "v1.10.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/symfony/polyfill-mbstring.git",
-                "reference": "c79c051f5b3a46be09205c73b80b346e4153e494"
+                "url": "https://github.com/symfony/polyfill-mbstring.git",
+                "reference": "c79c051f5b3a46be09205c73b80b346e4153e494"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/c79c051f5b3a46be09205c73b80b346e4153e494",
+                "reference": "c79c051f5b3a46be09205c73b80b346e4153e494",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "suggest": {
+                "ext-mbstring": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.9-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Mbstring\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for the Mbstring extension",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "mbstring",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-mbstring/tree/master"
+            },
+            "time": "2018-09-21T13:07:52+00:00"
+        },
+        {
+            "name": "symfony/polyfill-php56",
+            "version": "v1.18.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-php56.git",
+                "reference": "13df84e91cd168f247c2f2ec82cc0fa24901c011"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/c79c051f5b3a46be09205c73b80b346e4153e494",
-                "reference": "c79c051f5b3a46be09205c73b80b346e4153e494",
+                "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/13df84e91cd168f247c2f2ec82cc0fa24901c011",
+                "reference": "13df84e91cd168f247c2f2ec82cc0fa24901c011",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.3.3"
-            },
-            "suggest": {
-                "ext-mbstring": "For best performance"
+                "php": ">=5.3.3",
+                "symfony/polyfill-util": "~1.0"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.9-dev"
+                    "dev-master": "1.18-dev"
+                },
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
                 }
             },
             "autoload": {
                 "psr-4": {
-                    "Symfony\\Polyfill\\Mbstring\\": ""
+                    "Symfony\\Polyfill\\Php56\\": ""
                 },
                 "files": [
                     "bootstrap.php"
@@ -1816,42 +2444,54 @@
                     "homepage": "https://symfony.com/contributors"
                 }
             ],
-            "description": "Symfony polyfill for the Mbstring extension",
+            "description": "Symfony polyfill backporting some PHP 5.6+ features to lower PHP versions",
             "homepage": "https://symfony.com",
             "keywords": [
                 "compatibility",
-                "mbstring",
                 "polyfill",
                 "portable",
                 "shim"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-mbstring/tree/master"
+                "source": "https://github.com/symfony/polyfill-php56/tree/v1.18.1"
             },
-            "time": "2018-09-21T13:07:52+00:00"
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2020-07-14T12:35:20+00:00"
         },
         {
-            "name": "symfony/polyfill-php56",
-            "version": "v1.18.1",
+            "name": "symfony/polyfill-php80",
+            "version": "v1.23.0",
             "source": {
                 "type": "git",
-                "url": "https://github.com/symfony/polyfill-php56.git",
-                "reference": "13df84e91cd168f247c2f2ec82cc0fa24901c011"
+                "url": "https://github.com/symfony/polyfill-php80.git",
+                "reference": "eca0bf41ed421bed1b57c4958bab16aa86b757d0"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/13df84e91cd168f247c2f2ec82cc0fa24901c011",
-                "reference": "13df84e91cd168f247c2f2ec82cc0fa24901c011",
+                "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/eca0bf41ed421bed1b57c4958bab16aa86b757d0",
+                "reference": "eca0bf41ed421bed1b57c4958bab16aa86b757d0",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.3.3",
-                "symfony/polyfill-util": "~1.0"
+                "php": ">=7.1"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.18-dev"
+                    "dev-main": "1.23-dev"
                 },
                 "thanks": {
                     "name": "symfony/polyfill",
@@ -1860,10 +2500,13 @@
             },
             "autoload": {
                 "psr-4": {
-                    "Symfony\\Polyfill\\Php56\\": ""
+                    "Symfony\\Polyfill\\Php80\\": ""
                 },
                 "files": [
                     "bootstrap.php"
+                ],
+                "classmap": [
+                    "Resources/stubs"
                 ]
             },
             "notification-url": "https://packagist.org/downloads/",
@@ -1871,6 +2514,10 @@
                 "MIT"
             ],
             "authors": [
+                {
+                    "name": "Ion Bazan",
+                    "email": "ion.bazan@gmail.com"
+                },
                 {
                     "name": "Nicolas Grekas",
                     "email": "p@tchwork.com"
@@ -1880,7 +2527,7 @@
                     "homepage": "https://symfony.com/contributors"
                 }
             ],
-            "description": "Symfony polyfill backporting some PHP 5.6+ features to lower PHP versions",
+            "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
             "homepage": "https://symfony.com",
             "keywords": [
                 "compatibility",
@@ -1889,7 +2536,7 @@
                 "shim"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-php56/tree/v1.18.1"
+                "source": "https://github.com/symfony/polyfill-php80/tree/v1.23.0"
             },
             "funding": [
                 {
@@ -1905,7 +2552,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2020-07-14T12:35:20+00:00"
+            "time": "2021-02-19T12:13:01+00:00"
         },
         {
             "name": "symfony/polyfill-util",
@@ -2117,28 +2764,97 @@
             },
             "time": "2020-02-14T14:20:12+00:00"
         },
+        {
+            "name": "tuupola/callable-handler",
+            "version": "1.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/tuupola/callable-handler.git",
+                "reference": "0bc7b88630ca753de9aba8f411046856f5ca6f8c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/tuupola/callable-handler/zipball/0bc7b88630ca753de9aba8f411046856f5ca6f8c",
+                "reference": "0bc7b88630ca753de9aba8f411046856f5ca6f8c",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1|^8.0",
+                "psr/http-server-middleware": "^1.0"
+            },
+            "require-dev": {
+                "overtrue/phplint": "^1.0",
+                "phpunit/phpunit": "^7.0|^8.0|^9.0",
+                "squizlabs/php_codesniffer": "^3.2",
+                "tuupola/http-factory": "^0.4.0|^1.0",
+                "zendframework/zend-diactoros": "^1.6.0|^2.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Tuupola\\Middleware\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Mika Tuupola",
+                    "email": "tuupola@appelsiini.net",
+                    "homepage": "https://appelsiini.net/",
+                    "role": "Developer"
+                }
+            ],
+            "description": "Compatibility layer for PSR-7 double pass and PSR-15 middlewares.",
+            "homepage": "https://github.com/tuupola/callable-handler",
+            "keywords": [
+                "middleware",
+                "psr-15",
+                "psr-7"
+            ],
+            "support": {
+                "issues": "https://github.com/tuupola/callable-handler/issues",
+                "source": "https://github.com/tuupola/callable-handler/tree/1.1.0"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/tuupola",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-09-09T08:31:54+00:00"
+        },
         {
             "name": "tuupola/cors-middleware",
-            "version": "0.5.2",
+            "version": "1.2.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/tuupola/cors-middleware.git",
-                "reference": "db69d8e67b99570b16e8cd5f78c423ed1167cb21"
+                "reference": "4f085d11f349e83d18f1eb5802551353b2b093a3"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/tuupola/cors-middleware/zipball/db69d8e67b99570b16e8cd5f78c423ed1167cb21",
-                "reference": "db69d8e67b99570b16e8cd5f78c423ed1167cb21",
+                "url": "https://api.github.com/repos/tuupola/cors-middleware/zipball/4f085d11f349e83d18f1eb5802551353b2b093a3",
+                "reference": "4f085d11f349e83d18f1eb5802551353b2b093a3",
                 "shasum": ""
             },
             "require": {
-                "neomerx/cors-psr7": "^1.0",
-                "php": "^5.5 || ^7.0"
+                "neomerx/cors-psr7": "^1.0.4",
+                "php": "^7.1|^8.0",
+                "psr/http-message": "^1.0.1",
+                "psr/http-server-middleware": "^1.0",
+                "tuupola/callable-handler": "^1.0",
+                "tuupola/http-factory": "^1.0.2"
             },
             "require-dev": {
-                "phpunit/phpunit": "^4.8",
-                "squizlabs/php_codesniffer": "^2.5",
-                "zendframework/zend-diactoros": "^1.3"
+                "equip/dispatch": "^2.0",
+                "overtrue/phplint": "^1.0",
+                "phpstan/phpstan": "^0.12.42",
+                "phpunit/phpunit": "^7.0|^8.0|^9.0",
+                "squizlabs/php_codesniffer": "^3.5",
+                "zendframework/zend-diactoros": "^1.0|^2.0"
             },
             "type": "library",
             "autoload": {
@@ -2154,22 +2870,96 @@
                 {
                     "name": "Mika Tuupola",
                     "email": "tuupola@appelsiini.net",
-                    "homepage": "http://www.appelsiini.net/",
+                    "homepage": "https://appelsiini.net/",
                     "role": "Developer"
                 }
             ],
-            "description": "PSR-7 CORS Middleware",
+            "description": "PSR-7 and PSR-15 CORS middleware",
             "homepage": "https://github.com/tuupola/cors-middleware",
             "keywords": [
                 "cors",
                 "middleware",
-                "slim"
+                "psr-15",
+                "psr-7"
             ],
             "support": {
                 "issues": "https://github.com/tuupola/cors-middleware/issues",
-                "source": "https://github.com/tuupola/cors-middleware/tree/master"
+                "source": "https://github.com/tuupola/cors-middleware/tree/1.2.1"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/tuupola",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-10-29T11:01:06+00:00"
+        },
+        {
+            "name": "tuupola/http-factory",
+            "version": "1.3.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/tuupola/http-factory.git",
+                "reference": "aa48841a9f572b9cebe9d3ac5d5d3362a83f57ac"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/tuupola/http-factory/zipball/aa48841a9f572b9cebe9d3ac5d5d3362a83f57ac",
+                "reference": "aa48841a9f572b9cebe9d3ac5d5d3362a83f57ac",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1|^8.0",
+                "psr/http-factory": "^1.0"
+            },
+            "conflict": {
+                "nyholm/psr7": "<1.0"
+            },
+            "provide": {
+                "psr/http-factory-implementation": "^1.0"
+            },
+            "require-dev": {
+                "http-interop/http-factory-tests": "^0.7.0",
+                "overtrue/phplint": "^1.0",
+                "phpunit/phpunit": "^7.0|^8.0|^9.0",
+                "squizlabs/php_codesniffer": "^3.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Tuupola\\Http\\Factory\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Mika Tuupola",
+                    "email": "tuupola@appelsiini.net",
+                    "homepage": "https://appelsiini.net/",
+                    "role": "Developer"
+                }
+            ],
+            "description": "Lightweight autodiscovering PSR-17 HTTP factories",
+            "homepage": "https://github.com/tuupola/http-factory",
+            "keywords": [
+                "http",
+                "psr-17",
+                "psr-7"
+            ],
+            "support": {
+                "issues": "https://github.com/tuupola/http-factory/issues",
+                "source": "https://github.com/tuupola/http-factory/tree/1.3.0"
             },
-            "time": "2016-08-12T13:12:58+00:00"
+            "funding": [
+                {
+                    "url": "https://github.com/tuupola",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-10-01T07:46:32+00:00"
         }
     ],
     "packages-dev": [
@@ -5480,89 +6270,6 @@
             ],
             "time": "2021-02-19T12:13:01+00:00"
         },
-        {
-            "name": "symfony/polyfill-php80",
-            "version": "v1.23.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/symfony/polyfill-php80.git",
-                "reference": "eca0bf41ed421bed1b57c4958bab16aa86b757d0"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/eca0bf41ed421bed1b57c4958bab16aa86b757d0",
-                "reference": "eca0bf41ed421bed1b57c4958bab16aa86b757d0",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.1"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-main": "1.23-dev"
-                },
-                "thanks": {
-                    "name": "symfony/polyfill",
-                    "url": "https://github.com/symfony/polyfill"
-                }
-            },
-            "autoload": {
-                "psr-4": {
-                    "Symfony\\Polyfill\\Php80\\": ""
-                },
-                "files": [
-                    "bootstrap.php"
-                ],
-                "classmap": [
-                    "Resources/stubs"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Ion Bazan",
-                    "email": "ion.bazan@gmail.com"
-                },
-                {
-                    "name": "Nicolas Grekas",
-                    "email": "p@tchwork.com"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
-                }
-            ],
-            "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
-            "homepage": "https://symfony.com",
-            "keywords": [
-                "compatibility",
-                "polyfill",
-                "portable",
-                "shim"
-            ],
-            "support": {
-                "source": "https://github.com/symfony/polyfill-php80/tree/v1.23.0"
-            },
-            "funding": [
-                {
-                    "url": "https://symfony.com/sponsor",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/fabpot",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2021-02-19T12:13:01+00:00"
-        },
         {
             "name": "symfony/service-contracts",
             "version": "v2.2.0",
@@ -5835,27 +6542,33 @@
         },
         {
             "name": "woohoolabs/yang",
-            "version": "0.9.0",
+            "version": "2.3.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/woohoolabs/yang.git",
-                "reference": "00dc9820d48780364cd214537604b032d751a781"
+                "reference": "da65122971fa6add83751497ec76af1fb6cccf77"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/woohoolabs/yang/zipball/00dc9820d48780364cd214537604b032d751a781",
-                "reference": "00dc9820d48780364cd214537604b032d751a781",
+                "url": "https://api.github.com/repos/woohoolabs/yang/zipball/da65122971fa6add83751497ec76af1fb6cccf77",
+                "reference": "da65122971fa6add83751497ec76af1fb6cccf77",
                 "shasum": ""
             },
             "require": {
-                "php": "^5.6.0||^7.0.0",
-                "php-http/client-implementation": "^1.0.0",
-                "php-http/httplug": "^1.0.0"
+                "php": "^7.2.0||^8.0.0",
+                "php-http/httplug": "^1.0.0|^2.0.0",
+                "psr/http-message-implementation": "^1.0.0"
             },
             "require-dev": {
-                "php-http/guzzle6-adapter": "^1.1.0",
-                "phpunit/phpunit": "^5.4.0",
-                "squizlabs/php_codesniffer": "^2.3.1"
+                "guzzlehttp/psr7": "^1.4.0",
+                "php-http/guzzle6-adapter": "^2.0.0",
+                "phpstan/phpstan": "^0.12.0",
+                "phpstan/phpstan-phpunit": "^0.12.0",
+                "phpstan/phpstan-strict-rules": "^0.12.0",
+                "phpunit/phpunit": "^7.0.0||^8.2.0||^9.0.0",
+                "squizlabs/php_codesniffer": "^3.5.1",
+                "woohoolabs/coding-standard": "^1.0.0",
+                "woohoolabs/releaser": "^1.1.0"
             },
             "suggest": {
                 "php-http/guzzle6-adapter": "Allows to use Guzzle 6 as the HTTP client implementation"
@@ -5877,18 +6590,18 @@
                 }
             ],
             "description": "Woohoo Labs. Yang",
-            "homepage": "http://yang.woohoolabs.com",
             "keywords": [
                 "Woohoo Labs.",
                 "Yang",
                 "json api",
+                "psr-18",
                 "psr-7"
             ],
             "support": {
                 "issues": "https://github.com/woohoolabs/yang/issues",
                 "source": "https://github.com/woohoolabs/yang"
             },
-            "time": "2016-12-21T20:37:46+00:00"
+            "time": "2020-11-15T08:55:55+00:00"
         }
     ],
     "aliases": [],
@@ -5910,5 +6623,5 @@
         "ext-mbstring": "*"
     },
     "platform-dev": [],
-    "plugin-api-version": "2.1.0"
+    "plugin-api-version": "2.0.0"
 }
diff --git a/lib/classes/JsonApi/AppFactory.php b/lib/classes/JsonApi/AppFactory.php
deleted file mode 100644
index ae715aa936c9968586e87f13ebca6cae7e4176e0..0000000000000000000000000000000000000000
--- a/lib/classes/JsonApi/AppFactory.php
+++ /dev/null
@@ -1,54 +0,0 @@
-<?php
-
-namespace JsonApi;
-
-use Slim\App;
-use StudipPlugin;
-use JsonApi\Middlewares\RemoveTrailingSlashes;
-
-/**
- * Diese Klasse erstellt eine neue Slim-Applikation und konfiguriert
- * diese rudimentär vor.
- *
- * Dabei werden im `Dependency Container` der Slim-Applikation unter
- * dem Schlüssel `plugin` das Stud.IP-Plugin vermerkt und außerdem
- * eingestellt, dass Fehler des Slim-Frameworks detailliert angezeigt
- * werden sollen, wenn sich Stud.IP im Modus `development` befindet.
- *
- * Darüber hinaus wird eine Middleware installiert, die alle Requests umleitet,
- * die mit einem Schrägstrich enden (und zwar jeweils auf das Pendant
- * ohne Schrägstrich).
- *
- * @see http://www.slimframework.com/
- * @see \Studip\ENV
- * @see \JsonApi\Middlewares\RemoveTrailingSlashes
- */
-class AppFactory
-{
-    /**
-     * Diese Factory-Methode erstellt die Slim-Applikation und
-     * konfiguriert diese wie oben angegeben.
-     *
-     * @return \Slim\App die erstellte Slim-Applikation
-     */
-    public function makeApp()
-    {
-        $app = new App();
-        $app = $this->configureContainer($app);
-        $app->add(new RemoveTrailingSlashes());
-
-        return $app;
-    }
-
-    // hier wird der Container konfiguriert
-    private function configureContainer($app)
-    {
-        $container = $app->getContainer();
-        $container['settings']['displayErrorDetails'] = defined('\\Studip\\ENV') && \Studip\ENV === 'development';
-
-        $container->register(new Providers\StudipConfig());
-        $container->register(new Providers\StudipServices());
-
-        return $app;
-    }
-}
diff --git a/lib/classes/JsonApi/Contracts/JsonApiPlugin.php b/lib/classes/JsonApi/Contracts/JsonApiPlugin.php
index f7b607a8fac78c14aabb540f8b49bf525b43ebbf..15f1a6549750452d14fdcb81f09a81b8c4d3a375 100644
--- a/lib/classes/JsonApi/Contracts/JsonApiPlugin.php
+++ b/lib/classes/JsonApi/Contracts/JsonApiPlugin.php
@@ -28,6 +28,8 @@ interface JsonApiPlugin
      *
      * @param \Slim\App $app die Slim-Applikation, in der das Plugin
      *                       Routen eintragen möchte
+     *
+     * @return void
      */
     public function registerAuthenticatedRoutes(\Slim\App $app);
 
@@ -51,6 +53,8 @@ interface JsonApiPlugin
      *
      * @param \Slim\App $app die Slim-Applikation, in der das Plugin
      *                       Routen eintragen möchte
+     *
+     * @return void
      */
     public function registerUnauthenticatedRoutes(\Slim\App $app);
 
diff --git a/lib/classes/JsonApi/Errors/AuthorizationFailedException.php b/lib/classes/JsonApi/Errors/AuthorizationFailedException.php
index 104a4b530056ec5a11f2c02b8d4c1a29011b6608..af3d4fb889c50332c6bc163d9f3ff40bc2fe8d2e 100644
--- a/lib/classes/JsonApi/Errors/AuthorizationFailedException.php
+++ b/lib/classes/JsonApi/Errors/AuthorizationFailedException.php
@@ -2,7 +2,7 @@
 
 namespace JsonApi\Errors;
 
-use Neomerx\JsonApi\Document\Error;
+use Neomerx\JsonApi\Schema\Error;
 use Neomerx\JsonApi\Exceptions\JsonApiException;
 
 /**
@@ -15,7 +15,7 @@ class AuthorizationFailedException extends JsonApiException
      */
     public function __construct()
     {
-        $error = new Error('Forbidden', null, 403);
+        $error = new Error(null, null, null, '403', null, 'Forbidden');
         parent::__construct($error, 403);
     }
 }
diff --git a/lib/classes/JsonApi/Errors/BadRequestException.php b/lib/classes/JsonApi/Errors/BadRequestException.php
index 577ac22f01ed91e636dea9b8078ac4e2004b98b0..c09a96ad78d19de31e107ed6e76ec1978d0984df 100644
--- a/lib/classes/JsonApi/Errors/BadRequestException.php
+++ b/lib/classes/JsonApi/Errors/BadRequestException.php
@@ -2,7 +2,7 @@
 
 namespace JsonApi\Errors;
 
-use Neomerx\JsonApi\Document\Error;
+use Neomerx\JsonApi\Schema\Error;
 use Neomerx\JsonApi\Exceptions\JsonApiException;
 
 /**
@@ -15,7 +15,7 @@ class BadRequestException extends JsonApiException
      */
     public function __construct($detail = null, array $source = null)
     {
-        $error = new Error('Bad Request', null, 400, null, null, $detail, $source);
+        $error = new Error(null, null, null, 400, null, 'Bad Request', $detail, $source);
         parent::__construct($error, 400);
     }
 }
diff --git a/lib/classes/JsonApi/Errors/ConflictException.php b/lib/classes/JsonApi/Errors/ConflictException.php
index 69b00c5fa1552999a4e1310dcd18fe2c0f9599ca..c5cf0d735607f409f4e59777e3655da7d7789f94 100644
--- a/lib/classes/JsonApi/Errors/ConflictException.php
+++ b/lib/classes/JsonApi/Errors/ConflictException.php
@@ -2,7 +2,7 @@
 
 namespace JsonApi\Errors;
 
-use Neomerx\JsonApi\Document\Error;
+use Neomerx\JsonApi\Schema\Error;
 use Neomerx\JsonApi\Exceptions\JsonApiException;
 
 /**
@@ -15,7 +15,7 @@ class ConflictException extends JsonApiException
      */
     public function __construct($error = null)
     {
-        $error = new Error($error ?: 'Conflict', null, 409);
-        parent::__construct($error, 409);
+        $errorObject = new Error(null, null, null, 409, null, 'Conflict', $error);
+        parent::__construct($errorObject, 409);
     }
 }
diff --git a/lib/classes/JsonApi/Errors/ErrorHandler.php b/lib/classes/JsonApi/Errors/ErrorHandler.php
new file mode 100644
index 0000000000000000000000000000000000000000..93549b50a169c007021cb554e7ef5749c1ead8e9
--- /dev/null
+++ b/lib/classes/JsonApi/Errors/ErrorHandler.php
@@ -0,0 +1,140 @@
+<?php
+
+namespace JsonApi\Errors;
+
+use Neomerx\JsonApi\Exceptions\JsonApiException;
+use Neomerx\JsonApi\Schema\Error;
+use Neomerx\JsonApi\Schema\ErrorCollection;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Log\LoggerInterface;
+use Slim\App;
+use Slim\Exception\HttpException;
+use Throwable;
+
+class ErrorHandler
+{
+    /** @var \Slim\App */
+    private $app;
+
+    public function __construct(App $app)
+    {
+        $this->app = $app;
+    }
+
+    public function __invoke(
+        ServerRequestInterface $request,
+        Throwable $exception,
+        bool $displayErrorDetails,
+        bool $logErrors,
+        bool $logErrorDetails,
+        ?LoggerInterface $logger = null
+    ): ResponseInterface {
+        if ($logger) {
+            $logger->error($exception->getMessage());
+        }
+
+        $response = $this->app->getResponseFactory()->createResponse();
+        $response->getBody()->write($this->determinePayload($exception, $displayErrorDetails));
+
+        $code = $this->determineStatusCode($exception, $request->getMethod());
+
+        return $response->withStatus($code);
+    }
+
+    protected function determineStatusCode(Throwable $exception, string $method): int
+    {
+        if ('OPTIONS' === $method) {
+            return 200;
+        }
+
+        if ($exception instanceof HttpException) {
+            return $exception->getCode();
+        }
+
+        if ($exception instanceof JsonApiException) {
+            return $exception->getHttpCode();
+        }
+
+        return 500;
+    }
+
+    protected function determinePayload(Throwable $exception, bool $displayErrorDetails): string
+    {
+        $message = '';
+        if ($exception instanceof JsonApiException) {
+            $httpCode = $exception->getHttpCode();
+            $errors = new ErrorCollection();
+            foreach ($exception->getErrors() as $error) {
+                $errors[] = $this->copyError($error, $displayErrorDetails, $exception);
+            }
+        } elseif ($exception instanceof HttpException) {
+            $errors = $this->createErrorCollection(
+                $exception->getCode(),
+                $exception->getMessage(),
+                $exception->getDescription(),
+                $exception,
+                $displayErrorDetails
+            );
+        } else {
+            $errors = $this->createErrorCollection(
+                '500',
+                $exception->getMessage(),
+                null,
+                $exception,
+                $displayErrorDetails
+            );
+        }
+
+        if (sizeof($errors)) {
+            $encoder = $this->app->getContainer()->get('json-api-error-encoder');
+
+            return $encoder->encodeErrors($errors);
+        }
+
+        return '';
+    }
+
+    private function createErrorCollection(
+        string $httpCode,
+        string $message,
+        ?string $details,
+        Throwable $exception,
+        bool $displayErrorDetails
+    ): ErrorCollection {
+        /** @var \Exception $exception */
+        $errors = new ErrorCollection();
+        $errors->add(
+            new Error(
+                null,
+                null,
+                null,
+                $httpCode,
+                null,
+                $message,
+                $details,
+                $displayErrorDetails ? ['backtrace' => explode("\n", $exception->getTraceAsString())] : null
+            )
+        );
+
+        return $errors;
+    }
+
+    private function copyError(Error $error, bool $displayErrorDetails, JsonApiException $exception): Error
+    {
+        $newError = new Error(
+            $error->getId(),
+            $error->getLinks(),
+            $error->getTypeLinks(),
+            $error->getStatus(),
+            $error->getCode(),
+            $error->getTitle(),
+            $error->getDetail(),
+            $displayErrorDetails ? ['backtrace' => explode("\n", $exception->getTraceAsString())] : null,
+            false,
+            $error->getMeta()
+        );
+
+        return $newError;
+    }
+}
diff --git a/lib/classes/JsonApi/Errors/HttpRangeException.php b/lib/classes/JsonApi/Errors/HttpRangeException.php
index 950535d2c814ca44d59282084ad572aac394dd57..077d14cda69d0c508b79fa0b96b3bc51d80d215b 100644
--- a/lib/classes/JsonApi/Errors/HttpRangeException.php
+++ b/lib/classes/JsonApi/Errors/HttpRangeException.php
@@ -2,7 +2,7 @@
 
 namespace JsonApi\Errors;
 
-use Neomerx\JsonApi\Document\Error;
+use Neomerx\JsonApi\Schema\Error;
 use Neomerx\JsonApi\Exceptions\JsonApiException;
 
 /**
@@ -15,7 +15,7 @@ class HttpRangeException extends JsonApiException
      */
     public function __construct($error = null)
     {
-        $error = new Error($error ?: 'Requested Range Not Satisfiable.', null, 416);
-        parent::__construct($error, 416);
+        $errorObject = new Error(null, null, null, 416, null, 'Requested Range Not Satisfiable.', $error);
+        parent::__construct($errorObject, 416);
     }
 }
diff --git a/lib/classes/JsonApi/Errors/InternalServerError.php b/lib/classes/JsonApi/Errors/InternalServerError.php
index 98bae8f1ca9c4182515722a088868eab7fd176d2..ccff364ef4816035593c9480c922b482be637b35 100644
--- a/lib/classes/JsonApi/Errors/InternalServerError.php
+++ b/lib/classes/JsonApi/Errors/InternalServerError.php
@@ -2,7 +2,7 @@
 
 namespace JsonApi\Errors;
 
-use Neomerx\JsonApi\Document\Error;
+use Neomerx\JsonApi\Schema\Error;
 use Neomerx\JsonApi\Exceptions\JsonApiException;
 
 /**
@@ -15,7 +15,7 @@ class InternalServerError extends JsonApiException
      */
     public function __construct($detail = null, array $source = null)
     {
-        $error = new Error('Internal Server Error', null, 500, null, null, $detail, $source);
+        $error = new Error(null, null, null, 500, null, 'Internal Server Error', $detail, $source);
         parent::__construct($error, 500);
     }
 }
diff --git a/lib/classes/JsonApi/Errors/JsonApiExceptionHandler.php b/lib/classes/JsonApi/Errors/JsonApiExceptionHandler.php
deleted file mode 100644
index 25ac09239731e93b24de5843465312882441b294..0000000000000000000000000000000000000000
--- a/lib/classes/JsonApi/Errors/JsonApiExceptionHandler.php
+++ /dev/null
@@ -1,97 +0,0 @@
-<?php
-
-namespace JsonApi\Errors;
-
-use JsonApi\Providers\JsonApiConfig as C;
-use Neomerx\JsonApi\Contracts\Factories\FactoryInterface;
-use Neomerx\JsonApi\Contracts\Http\Headers\MediaTypeInterface;
-use Neomerx\JsonApi\Contracts\Schema\ContainerInterface as SC;
-use Neomerx\JsonApi\Document\Error;
-use Neomerx\JsonApi\Encoder\EncoderOptions;
-use Neomerx\JsonApi\Exceptions\ErrorCollection;
-use Neomerx\JsonApi\Exceptions\JsonApiException;
-use Psr\Container\ContainerInterface;
-use Psr\Http\Message\ResponseInterface as Response;
-use Psr\Http\Message\ServerRequestInterface as Request;
-
-/**
- * Dieser spezielle Exception Handler wird in der Slim-Applikation
- * für alle JSON-API-Routen installiert und sorgt dafür, dass auch
- * evtl. Fehler JSON-API-kompatibel geliefert werden.
- */
-class JsonApiExceptionHandler
-{
-    private $previous;
-
-    private $container;
-
-    /**
-     * Der Konstruktor...
-     *
-     * @param ContainerInterface $container der Dependency Container,
-     *                                      der in der Slim-Applikation verwendet wird
-     * @param callable           $previous  der zuvor installierte `Error
-     *                                      Handler` als Fallback
-     */
-    public function __construct(ContainerInterface $container, $previous = null)
-    {
-        $this->previous = $previous;
-        $this->container = $container;
-    }
-
-    /**
-     * Diese Methode wird aufgerufen, sobald es zu einer Exception
-     * kam, und generiert eine entsprechende JSON-API-spezifische Response.
-     *
-     * @param Request    $request   der eingehende Request
-     * @param Response   $response  die vorbereitete ausgehende Response
-     * @param \Exception $exception die aufgetretene Exception
-     *
-     * @return Response die JSON-API-kompatible Response
-     */
-    public function __invoke(Request $request, Response $response, \Exception $exception)
-    {
-        if ($exception instanceof JsonApiException) {
-            $httpCode = $exception->getHttpCode();
-            $errors = $exception->getErrors();
-        } else {
-            $httpCode = 500;
-            $details = null;
-
-            $debugEnabled = \Studip\ENV === 'development';
-            if ($debugEnabled === true) {
-                $message = $exception->getMessage();
-                $details = (string) $exception;
-            }
-            $errors = new ErrorCollection();
-            $errors->add(new Error(null, null, $httpCode, null, $message, $details));
-        }
-
-        if (sizeof($errors)) {
-            $encoder = $this->createEncoder();
-            $response = $response
-                      ->withHeader(
-                          'Content-Type',
-                          sprintf('%s/%s',
-                                  MediaTypeInterface::JSON_API_TYPE,
-                                  MediaTypeInterface::JSON_API_SUB_TYPE
-                          )
-                      )
-                      ->write($encoder->encodeErrors($errors));
-        }
-
-        return $response->withStatus($httpCode);
-    }
-
-    private function createEncoder()
-    {
-        $factory = $this->container[FactoryInterface::class];
-        $schemaContainer = $this->container[SC::class];
-
-        $urlPrefix = $this->container[C::JSON_URL_PREFIX];
-
-        $encoderOptions = new EncoderOptions(0, $urlPrefix);
-
-        return $factory->createEncoder($schemaContainer, $encoderOptions);
-    }
-}
diff --git a/lib/classes/JsonApi/Errors/NotAcceptableException.php b/lib/classes/JsonApi/Errors/NotAcceptableException.php
new file mode 100644
index 0000000000000000000000000000000000000000..dddb377da3f6c5867731392325b6a85cb85ab329
--- /dev/null
+++ b/lib/classes/JsonApi/Errors/NotAcceptableException.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace JsonApi\Errors;
+
+use Neomerx\JsonApi\Schema\Error;
+use Neomerx\JsonApi\Exceptions\JsonApiException;
+
+class NotAcceptableException extends JsonApiException
+{
+    public function __construct()
+    {
+        $error = new Error(null, null, null, '406', null, 'Not Acceptable Error');
+        parent::__construct($error, 406);
+    }
+}
diff --git a/lib/classes/JsonApi/Errors/NotImplementedException.php b/lib/classes/JsonApi/Errors/NotImplementedException.php
index 4e8541d683d0ff41364b72e828085d1047775b71..d6f35a1f2a60e9079b2f7c867b33190b35738ab7 100644
--- a/lib/classes/JsonApi/Errors/NotImplementedException.php
+++ b/lib/classes/JsonApi/Errors/NotImplementedException.php
@@ -2,7 +2,7 @@
 
 namespace JsonApi\Errors;
 
-use Neomerx\JsonApi\Document\Error;
+use Neomerx\JsonApi\Schema\Error;
 use Neomerx\JsonApi\Exceptions\JsonApiException;
 
 /**
@@ -15,7 +15,7 @@ class NotImplementedException extends JsonApiException
      */
     public function __construct($detail = null, array $source = null)
     {
-        $error = new Error('Not Implemented Error', null, 501, null, null, $detail, $source);
+        $error = new Error(null, null, null, 501, null, 'Not Implemented Error', $detail, $source);
         parent::__construct($error, 501);
     }
 }
diff --git a/lib/classes/JsonApi/Errors/RecordNotFoundException.php b/lib/classes/JsonApi/Errors/RecordNotFoundException.php
index 9ed0c1a2c8b0c10a325b2efd6e28b99f6dfb9a04..4cf0cb952cba5b3058490c1167e324262381a612 100644
--- a/lib/classes/JsonApi/Errors/RecordNotFoundException.php
+++ b/lib/classes/JsonApi/Errors/RecordNotFoundException.php
@@ -2,7 +2,7 @@
 
 namespace JsonApi\Errors;
 
-use Neomerx\JsonApi\Document\Error;
+use Neomerx\JsonApi\Schema\Error;
 use Neomerx\JsonApi\Exceptions\JsonApiException;
 
 /**
@@ -15,7 +15,7 @@ class RecordNotFoundException extends JsonApiException
      */
     public function __construct($error = null)
     {
-        $error = new Error($error ?: 'Not Found', null, 404);
-        parent::__construct($error, 404);
+        $errorObject = new Error(null, null, null, 404, null, 'Not Found', $error);
+        parent::__construct($errorObject, 404);
     }
 }
diff --git a/lib/classes/JsonApi/Errors/UnprocessableEntityException.php b/lib/classes/JsonApi/Errors/UnprocessableEntityException.php
index e8a4794a6192aada8c730dff0733f44c104183ec..2af4d896ef457d543a0c5d02cc19bd6ad012ed26 100644
--- a/lib/classes/JsonApi/Errors/UnprocessableEntityException.php
+++ b/lib/classes/JsonApi/Errors/UnprocessableEntityException.php
@@ -2,7 +2,7 @@
 
 namespace JsonApi\Errors;
 
-use Neomerx\JsonApi\Document\Error;
+use Neomerx\JsonApi\Schema\Error;
 use Neomerx\JsonApi\Exceptions\JsonApiException;
 
 /**
@@ -15,7 +15,7 @@ class UnprocessableEntityException extends JsonApiException
      */
     public function __construct($detail = null, array $source = null)
     {
-        $error = new Error('Unprocesssable Entity', null, 422, null, null, $detail, $source);
+        $error = new Error(null, null, null, 422, null, 'Unprocesssable Entity', $detail, $source);
         parent::__construct($error, 422);
     }
 }
diff --git a/lib/classes/JsonApi/Errors/UnsupportedMediaTypeException.php b/lib/classes/JsonApi/Errors/UnsupportedMediaTypeException.php
new file mode 100644
index 0000000000000000000000000000000000000000..ec835717592036df55152443c4964351a0b5b7eb
--- /dev/null
+++ b/lib/classes/JsonApi/Errors/UnsupportedMediaTypeException.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace JsonApi\Errors;
+
+use Neomerx\JsonApi\Schema\Error;
+use Neomerx\JsonApi\Exceptions\JsonApiException;
+
+class UnsupportedMediaTypeException extends JsonApiException
+{
+    public function __construct()
+    {
+        $error = new Error(null, null, null, '415', null, 'Unsupported Media Type Error');
+        parent::__construct($error, 415);
+    }
+}
diff --git a/lib/classes/JsonApi/Errors/UnsupportedRequestError.php b/lib/classes/JsonApi/Errors/UnsupportedRequestError.php
index 62ac4fbd2e44ad287ddb6caa2711de40af44d995..3b8bcd54bae6a40950ecf2fd2e0d686ae9fd41c2 100644
--- a/lib/classes/JsonApi/Errors/UnsupportedRequestError.php
+++ b/lib/classes/JsonApi/Errors/UnsupportedRequestError.php
@@ -2,7 +2,7 @@
 
 namespace JsonApi\Errors;
 
-use Neomerx\JsonApi\Document\Error;
+use Neomerx\JsonApi\Schema\Error;
 use Neomerx\JsonApi\Exceptions\JsonApiException;
 
 /**
@@ -15,7 +15,7 @@ class UnsupportedRequestError extends JsonApiException
      */
     public function __construct($detail = null, array $source = null)
     {
-        $error = new Error('Unsupported request.', null, 403, null, null, $detail, $source);
+        $error = new Error(null, null, null, 403, null, 'Unsupported request.', $detail, $source);
         parent::__construct($error, 403);
     }
 }
diff --git a/lib/classes/JsonApi/JsonApiController.php b/lib/classes/JsonApi/JsonApiController.php
index 645d4041dc17d1026630f710cbcd0d60f2df4b19..1718c52f86a1903cfb52f88508d8e5274b243ff5 100644
--- a/lib/classes/JsonApi/JsonApiController.php
+++ b/lib/classes/JsonApi/JsonApiController.php
@@ -3,10 +3,20 @@
 namespace JsonApi;
 
 use JsonApi\JsonApiIntegration\JsonApiTrait;
+use JsonApi\JsonApiIntegration\QueryParserInterface;
+use JsonApi\Middlewares\Authentication;
+use Neomerx\JsonApi\Contracts\Encoder\EncoderInterface;
+use Neomerx\JsonApi\Contracts\Factories\FactoryInterface;
+use Neomerx\JsonApi\Contracts\Http\Headers\HeaderParametersParserInterface;
+use Neomerx\JsonApi\Contracts\Http\Headers\MediaTypeInterface;
+use Neomerx\JsonApi\Contracts\Http\ResponsesInterface;
+use Neomerx\JsonApi\Contracts\Schema\SchemaContainerInterface;
+use Neomerx\JsonApi\Contracts\Schema\SchemaInterface;
+use Neomerx\JsonApi\Http\Headers\MediaType;
+use Neomerx\JsonApi\Schema\Link;
 use Psr\Container\ContainerInterface;
-
-use Neomerx\JsonApi\Contracts\Http\Headers\HeadersCheckerInterface;
-use Neomerx\JsonApi\Contracts\Http\Headers\HeaderParametersInterface;
+use Psr\Http\Message\ResponseInterface as Response;
+use Psr\Http\Message\ServerRequestInterface as Request;
 
 /**
  * Ein JsonApiController ist die einfachste Möglichkeit, eine eigene
@@ -28,19 +38,426 @@ use Neomerx\JsonApi\Contracts\Http\Headers\HeaderParametersInterface;
  */
 class JsonApiController
 {
-    use JsonApiTrait;
+    /**
+     * @var \Slim\App
+     */
+    protected $app;
+
+    /**
+     * @var ContainerInterface;
+     */
+    protected $container;
+
+    /**
+     * @var FactoryInterface
+     */
+    protected $factory;
+
+    /**
+     * @var EncoderInterface
+     */
+    protected $encoder;
+
+    /**
+     * @var SchemaContainerInterface
+     */
+    protected $schemaContainer;
+
+    /**
+     * @var QueryParserInterface
+     */
+    protected $queryParser;
 
     /**
      * Der Konstruktor.
+     */
+    public function __construct(
+        \Slim\App $app,
+        ContainerInterface $container,
+        FactoryInterface $factory,
+        EncoderInterface $encoder,
+        SchemaContainerInterface $schemaContainer,
+        QueryParserInterface $queryParser,
+        HeaderParametersParserInterface $headerParametersParser
+    ) {
+        $this->app = $app;
+        $this->container = $container;
+        $this->factory = $factory;
+        $this->encoder = $encoder;
+        $this->schemaContainer = $schemaContainer;
+        $this->queryParser = $queryParser;
+
+        $queryChecker = new JsonApiIntegration\QueryChecker(
+            $this->allowUnrecognizedParams,
+            $this->allowedIncludePaths,
+            $this->allowedFieldSetTypes,
+            $this->allowedSortFields,
+            $this->allowedPagingParameters,
+            $this->allowedFilteringParameters
+        );
+
+        $queryChecker->checkQuery($queryParser);
+
+        $this->checkAcceptHeader($headerParametersParser);
+        $this->checkContentTypeHeader($headerParametersParser);
+    }
+
+    /**
+     * If unrecognized parameters should be allowed in input parameters.
+     *
+     * @var bool
+     */
+    protected $allowUnrecognizedParams = false;
+
+    /**
+     * A list of allowed include paths in input parameters.
+     *
+     * Empty array [] means clients are not allowed to specify include paths and 'null' means all paths are allowed.
+     *
+     * @var string[]|null
+     */
+    protected $allowedIncludePaths = [];
+
+    /**
+     * A list of JSON API types which clients can sent field sets to.
+     *
+     * Possible values
+     *
+     * $allowedFieldSetTypes = null; // <-- for all types all fields are allowed
+     *
+     * $allowedFieldSetTypes = []; // <-- non of the types and fields are allowed
+     *
+     * $allowedFieldSetTypes = [
+     *      'people'   => null,              // <-- all fields for 'people' are allowed
+     *      'comments' => [],                // <-- no fields for 'comments' are allowed (all denied)
+     *      'posts'    => ['title', 'body'], // <-- only 'title' and 'body' fields are allowed for 'posts'
+     * ];
      *
-     * @param ContainerInterface $container der Dependency Container
+     * @var string[]|null
      */
-    public function __construct(ContainerInterface $container)
+    protected $allowedFieldSetTypes = null;
+
+    /**
+     * A list of allowed sort field names in input parameters.
+     *
+     * Empty array [] means clients are not allowed to specify sort fields and 'null' means all fields are allowed.
+     *
+     * @var string[]|null
+     */
+    protected $allowedSortFields = [];
+
+    /**
+     * A list of allowed pagination input parameters (e.g 'number', 'size', 'offset' and etc).
+     *
+     * Empty array [] means clients are not allowed to specify paging and 'null' means all parameters are allowed.
+     *
+     * @var string[]|null
+     */
+    protected $allowedPagingParameters = [];
+
+    /**
+     * A list of allowed filtering input parameters.
+     *
+     * Empty array [] means clients are not allowed to specify filtering and 'null' means all parameters are allowed.
+     *
+     * @var string[]|null
+     */
+    protected $allowedFilteringParameters = [];
+
+    // ***** RESPONSE GENERATORS *****
+
+    /**
+     * Get response with HTTP code only.
+     */
+    protected function getCodeResponse(int $statusCode, array $headers = []): Response
     {
-        $this->container = $container;
-        $this->initJsonApiSupport($container);
+        $responses = $this->getResponses();
+
+        return $responses->getCodeResponse($statusCode, $headers);
+    }
+
+    /**
+     * Get response with meta information only.
+     *
+     * @param array|object $meta       Meta information
+     * @param int          $statusCode
+     */
+    protected function getMetaResponse($meta, $statusCode = ResponsesInterface::HTTP_OK, array $headers = []): Response
+    {
+        $responses = $this->getResponses();
+
+        return $responses->getMetaResponse($meta, $statusCode, $headers);
+    }
+
+    /**
+     * Get response with regular JSON API Document in body.
+     *
+     * @param object|array $data
+     * @param int          $statusCode
+     * @param array|null   $links
+     * @param mixed        $meta
+     */
+    protected function getContentResponse(
+        $data,
+        $statusCode = ResponsesInterface::HTTP_OK,
+        $links = [],
+        $meta = [],
+        array $headers = []
+    ): Response {
+        $responses = $this->getResponses($links, $meta);
+
+        return $responses->getContentResponse($data, $statusCode, $headers);
+    }
+
+    /**
+     * Get response with only resource identifiers.
+     *
+     * @param object|array $data
+     * @param array|null   $links
+     * @param mixed        $meta
+     */
+    protected function getIdentifiersResponse($data, $links = [], $meta = [], array $headers = []): Response
+    {
+        $responses = $this->getResponses($links, $meta);
+        $statusCode = ResponsesInterface::HTTP_OK;
+
+        return $responses->getIdentifiersResponse($data, $statusCode, $headers);
+    }
+
+    /**
+     * Get response with paginated resource identifiers.
+     *
+     * @param object|array $data
+     * @param ?int         $total
+     * @param array|null   $links
+     * @param mixed        $meta
+     */
+    protected function getPaginatedIdentifiersResponse(
+        $data,
+        $total,
+        $links = [],
+        $meta = [],
+        array $headers = []
+    ): Response {
+        list($offset, $limit) = $this->getOffsetAndLimit();
+
+        $meta['page'] = [
+            'offset' => (int) $offset,
+            'limit' => (int) $limit,
+        ];
+
+        if (isset($total)) {
+            $meta['page']['total'] = (int) $total;
+        }
+
+        $paginator = new JsonApiIntegration\Paginator($total, $offset, $limit);
+
+        foreach (words('first last prev next') as $rel) {
+            if (list($off, $lim) = $paginator->{'get'.ucfirst($rel).'PageOffsetAndLimit'}()) {
+                $links[$rel] = $this->createLink($off, $lim);
+            }
+        }
+
+        $responses = $this->getResponses($links, $meta);
+        $statusCode = ResponsesInterface::HTTP_OK;
+
+        return $responses->getIdentifiersResponse($data, $statusCode, $headers);
+    }
+
+    /**
+     * @param object     $resource
+     * @param array|null $links
+     * @param mixed      $meta
+     */
+    protected function getCreatedResponse($resource, $links = [], $meta = [], array $headers = []): Response
+    {
+        $responses = $this->getResponses($links, $meta);
+        $urlPrefix = $this->container->get('json-api-integration-urlPrefix');
+        $url = $this->schemaContainer
+            ->getSchema($resource)
+            ->getSelfLink($resource)
+            ->getStringRepresentation($urlPrefix);
+
+        return $responses->getCreatedResponse($resource, $url, $headers);
+    }
+
+    /**
+     * @param object|array $data
+     * @param ?int         $total
+     * @param int          $statusCode
+     * @param array|null   $links
+     * @param mixed        $meta
+     */
+    protected function getPaginatedContentResponse(
+        $data,
+        $total,
+        $statusCode = ResponsesInterface::HTTP_OK,
+        $links = [],
+        $meta = [],
+        array $headers = []
+    ): Response {
+        list($offset, $limit) = $this->getOffsetAndLimit();
+
+        $meta['page'] = [
+            'offset' => (int) $offset,
+            'limit' => (int) $limit,
+        ];
+
+        if (isset($total)) {
+            $meta['page']['total'] = (int) $total;
+        }
+
+        $paginator = new JsonApiIntegration\Paginator($total, $offset, $limit);
+
+        foreach (words('first last prev next') as $rel) {
+            if (list($off, $lim) = $paginator->{'get'.ucfirst($rel).'PageOffsetAndLimit'}()) {
+                $links[$rel] = $this->createLink($off, $lim);
+            }
+        }
+
+        $responses = $this->getResponses($links, $meta);
+
+        return $responses->getContentResponse($data, $statusCode, $headers);
+    }
+
+    protected function getQueryParameters(): QueryParserInterface
+    {
+        return $this->queryParser;
+    }
+
+    /**
+     * Liefert Offset und Limit aus den Request-Parametern zurück.
+     *
+     * @param int $offsetDefault optional; gibt den Standard-Offset
+     *                           an, falls dieser Wert nicht im Request gesetzt ist
+     * @param int $limitDefault  optional; gibt das Standard-Limit an,
+     *                           falls dieser Wert nicht im Request gesetzt ist
+     *
+     * @return array<int> {
+     *
+     *     @var int $offset der im Request gesetzte Offset oder
+     *     ansonsten der Default-Wert 0
+     *     @var int $limit das im Request gesetzte Limit oder
+     *     ansonsten der Default-Wert 30
+     * }
+     */
+    protected function getOffsetAndLimit($offsetDefault = 0, $limitDefault = 30): array
+    {
+        $params = iterator_to_array($this->queryParser->getPagination());
+
+        return [
+            $params && array_key_exists('offset', $params) ? (int) $params['offset'] : $offsetDefault,
+            $params && array_key_exists('limit', $params) ? (int) $params['limit'] : $limitDefault,
+        ];
+    }
+
+    // Hier wird der aktuelle Link zusätzlich noch mit Paginierung ausgestattet
+    private function createLink(int $offset, int $limit): Link
+    {
+        $request = $this->container->get('request');
+        $queryParams = $request->getQueryParams();
+        $queryParams['page']['offset'] = $offset;
+        $queryParams['page']['limit'] = $limit;
+
+        $uri = $request->getUri()->withQuery(http_build_query($queryParams));
+
+        $path = $uri->getPath();
+        $query = $uri->getQuery();
+        $fragment = $uri->getFragment();
+
+        $uriString = $path.($query ? '?'.$query : '').($fragment ? '#'.$fragment : '');
+
+        return new Link(false, $uriString, false);
+    }
+
+    /**
+     * Gibt null oder das User-Objekt des "eingeloggten" Nutzers zurück.
+     *
+     * @param Request $request Request der eingehende Request
+     *
+     * @return mixed entweder null oder das User-Objekt des
+     *               "eingeloggten" Nutzers
+     */
+    public function getUser(Request $request)
+    {
+        return $request->getAttribute(Authentication::USER_KEY, null);
+    }
+
+    /**
+     * Gibt das Schema zu einer beliebigen Ressource zurück.
+     *
+     * @param mixed $resource die Ressource, zu der das Schema geliefert werden soll
+     *
+     * @return SchemaInterface das Schema zur Ressource
+     */
+    protected function getSchema($resource): SchemaInterface
+    {
+        return $this->schemaContainer->getSchema($resource);
+    }
+
+    protected function getResponses(array $links = [], array $meta = []): ResponsesInterface
+    {
+        $paths = $this->queryParser->getIncludePaths();
+        $fieldSets = iterator_to_array($this->queryParser->getFields());
+
+        $encoder = $this->encoder
+            ->withIncludedPaths($paths)
+            ->withFieldSets($fieldSets)
+            ->withLinks($links);
+        if (count($meta)) {
+            $encoder = $encoder->withMeta($meta);
+        }
+
+        $mediaType = new MediaType(MediaTypeInterface::JSON_API_TYPE, MediaTypeInterface::JSON_API_SUB_TYPE);
+
+        return new JsonApiIntegration\Responses($encoder, $mediaType);
+    }
+
+    private function checkAcceptHeader(HeaderParametersParserInterface $headerParametersParser): void
+    {
+        $request = $this->container->get('request');
+        $accept = $request->getHeader(HeaderParametersParserInterface::HEADER_ACCEPT);
+        if (count($accept)) {
+            $mediaType = $this->factory->createMediaType(
+                MediaTypeInterface::JSON_API_TYPE,
+                MediaTypeInterface::JSON_API_SUB_TYPE
+            );
+
+            foreach ($headerParametersParser->parseAcceptHeader($accept[0]) as $acceptMediaType) {
+                if ($mediaType->matchesTo($acceptMediaType)) {
+                    return;
+                }
+            }
+        }
+        throw new Errors\NotAcceptableException();
+    }
+
+    private function checkContentTypeHeader(HeaderParametersParserInterface $headerParametersParser): void
+    {
+        $request = $this->container->get('request');
+        if ($this->doesRequestHaveBody($request)) {
+            $contentType = $request->getHeader(HeaderParametersParserInterface::HEADER_CONTENT_TYPE);
+            if (count($contentType)) {
+                $mediaType = $this->factory->createMediaType(
+                    MediaTypeInterface::JSON_API_TYPE,
+                    MediaTypeInterface::JSON_API_SUB_TYPE
+                );
+                $parsedContentType = $headerParametersParser->parseContentTypeHeader($contentType[0]);
+                if ($mediaType->matchesTo($parsedContentType)) {
+                    return;
+                }
+            }
+            throw new Errors\UnsupportedMediaTypeException();
+        }
+    }
+
+    private function doesRequestHaveBody(Request $request): bool
+    {
+        if (count($request->getHeader('Transfer-Encoding'))) {
+            return true;
+        }
+        $contentLength = $request->getHeader('Content-Length');
 
-        $headerChecker = $this->container[HeadersCheckerInterface::class];
-        $headerChecker->checkHeaders($this->container[HeaderParametersInterface::class]);
+        return count($contentLength) && $contentLength[0] > 0;
     }
 }
diff --git a/lib/classes/JsonApi/JsonApiIntegration/Container.php b/lib/classes/JsonApi/JsonApiIntegration/Container.php
deleted file mode 100644
index 7f9911ac7e4ed1fc7f380d4d4e240467f7127c85..0000000000000000000000000000000000000000
--- a/lib/classes/JsonApi/JsonApiIntegration/Container.php
+++ /dev/null
@@ -1,98 +0,0 @@
-<?php
-
-namespace JsonApi\JsonApiIntegration;
-
-class Container extends \Neomerx\JsonApi\Schema\Container
-{
-    /**
-     * Überprüft nicht nur, ob es ein mapping zum $type gibt, sondern
-     * sucht sonst auch nach mappings der Oberklassen bzw. Interfaces.
-     *
-     * @param string $type
-     *
-     * @return bool
-     */
-    protected function hasProviderMapping($type)
-    {
-        if (parent::hasProviderMapping($type)) {
-            return true;
-        }
-
-        foreach ($this->getParentClassesAndInterfaces($type) as $class) {
-            if (parent::hasProviderMapping($class)) {
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    /**
-     * Liefert nicht nur direkte Treffer aus den provider mappings
-     * sondern schaut falls nötig auch nach Oberklassen bzw. Interfaces.
-     *
-     * @param string $type
-     *
-     * @return mixed
-     */
-    protected function getProviderMapping($type)
-    {
-        $providerMapping = $this->getProviderMappings();
-
-        if (isset($providerMapping[$type])) {
-            return $providerMapping[$type];
-        }
-
-        foreach ($this->getParentClassesAndInterfaces($type) as $class) {
-            if (isset($providerMapping[$class])) {
-                return $providerMapping[$class];
-            }
-        }
-
-        return null;
-    }
-
-    private function getParentClassesAndInterfaces($type)
-    {
-        return class_exists($type) ? @class_parents($type) + @class_implements($type) : [];
-    }
-
-    /**
-     * @deprecated use `createSchemaFromCallable` method instead
-     *
-     * @param Closure $closure
-     *
-     * @return SchemaProviderInterface
-     */
-    protected function createSchemaFromClosure(\Closure $closure)
-    {
-        $schema = $closure($this->getFactory(), $this);
-
-        return $schema;
-    }
-
-    /**
-     * @param callable $callable
-     *
-     * @return SchemaProviderInterface
-     */
-    protected function createSchemaFromCallable(callable $callable)
-    {
-        $schema = $callable instanceof \Closure ?
-                $this->createSchemaFromClosure($callable) : call_user_func($callable, $this->getFactory(), $this);
-
-        return $schema;
-    }
-
-    /**
-     * @param string $className
-     *
-     * @return SchemaProviderInterface
-     */
-    protected function createSchemaFromClassName($className)
-    {
-        $schema = new $className($this->getFactory(), $this);
-
-        return $schema;
-    }
-}
diff --git a/lib/classes/JsonApi/JsonApiIntegration/EncoderParser.php b/lib/classes/JsonApi/JsonApiIntegration/EncoderParser.php
deleted file mode 100644
index 2e047b78ae6a55a88d60e0e60d848dab0e583dd7..0000000000000000000000000000000000000000
--- a/lib/classes/JsonApi/JsonApiIntegration/EncoderParser.php
+++ /dev/null
@@ -1,49 +0,0 @@
-<?php
-
-namespace JsonApi\JsonApiIntegration;
-
-use SimpleORMap;
-use Neomerx\JsonApi\Encoder\Parser\Parser as NeomerxParser;
-
-/**
- * Eine Instanz von Neomerx\JsonApi\Encoder\Parser\Parser wird
- * benötigt, um Werte, die an den JSON-API-Encoder gehen, zu
- * analysieren und entsprechned weiter zu verarbeiten. Unter anderem
- * wird darin auch die Unterscheidung getroffen, ob Werte, die an den
- * JSON-API-Encoder gehen, Collections sind oder nicht.
- *
- * Bei dieser Analyse werden sinnvollerweise alle Werte, die das
- * PHP-Interface \IteratorAggregate implementieren, als Collections
- * behandelt. Da aber die Stud.IP-Klasse \SimpleORMap
- * ungewöhnlicherweise ebenfalls dieses Interface implementiert, muss
- * hier eine Sonderbehandlung stattfinden.
- *
- * Dazu wird die Methode
- * Neomerx\JsonApi\Encoder\Parser\Parser::analyzeCurrentData so
- * überschrieben, dass Instanzen von \SimpleORMap nicht als
- * Collections gelten.
- *
- * @see Neomerx\JsonApi\Encoder\Parser\Parser
- * @see \SimpleORMap
- */
-class EncoderParser extends NeomerxParser
-{
-    /**
-     * {@inheritdoc}
-     */
-    protected function analyzeCurrentData()
-    {
-        $relationship = $this->stack->end()->getRelationship();
-        $data = $relationship->isShowData() === true ? $relationship->getData() : null;
-
-        if ($data instanceof SimpleORMap) {
-            $isEmpty = false;
-            $isCollection = false;
-            $traversableData = [$data];
-
-            return [$isEmpty, $isCollection, $traversableData];
-        }
-
-        return parent::analyzeCurrentData();
-    }
-}
diff --git a/lib/classes/JsonApi/JsonApiIntegration/Factory.php b/lib/classes/JsonApi/JsonApiIntegration/Factory.php
index 8e99d3b8ebac901b1da7955f12b099ae6d128b08..afdd4317a162ffbca9da37cdd84fad0527d9475a 100644
--- a/lib/classes/JsonApi/JsonApiIntegration/Factory.php
+++ b/lib/classes/JsonApi/JsonApiIntegration/Factory.php
@@ -2,9 +2,12 @@
 
 namespace JsonApi\JsonApiIntegration;
 
+use Neomerx\JsonApi\Contracts\Parser\EditableContextInterface;
+use Neomerx\JsonApi\Contracts\Parser\ParserInterface;
+use Neomerx\JsonApi\Contracts\Schema\LinkInterface;
+use Neomerx\JsonApi\Contracts\Schema\SchemaContainerInterface;
 use Neomerx\JsonApi\Factories\Factory as NeomerxFactory;
-use Neomerx\JsonApi\Contracts\Schema\ContainerInterface;
-use Neomerx\JsonApi\Contracts\Encoder\Parser\ParserManagerInterface;
+use Neomerx\JsonApi\Schema\Link;
 
 /**
  * Die "normale" \Neomerx\JsonApi\Factories\Factory stellt in
@@ -19,38 +22,21 @@ use Neomerx\JsonApi\Contracts\Encoder\Parser\ParserManagerInterface;
  */
 class Factory extends NeomerxFactory
 {
-    private $diContainer;
-
-    public function setDependencyInjectionContainer(\Psr\Container\ContainerInterface $diContainer)
-    {
-        $this->diContainer = $diContainer;
-    }
-
-    public function getDependencyInjectionContainer()
-    {
-        return $this->diContainer;
-    }
-
     /**
-     * {@inheritdoc}
+     * @inheritdoc
      */
-    public function createContainer(array $providers = [])
-    {
-        $container = new Container($this, $providers);
-
-        $container->setLogger($this->logger);
-
-        return $container;
+    public function createParser(
+        SchemaContainerInterface $container,
+        EditableContextInterface $context
+    ): ParserInterface {
+        return new Parser($this, $container, $context);
     }
 
     /**
-     * {@inheritdoc}
+     * @inheritdoc
      */
-    public function createParser(ContainerInterface $container, ParserManagerInterface $manager)
+    public function createSchemaContainer(iterable $schemas): SchemaContainerInterface
     {
-        $parser = new EncoderParser($this, $this, $this, $container, $manager);
-        $parser->setLogger($this->logger);
-
-        return $parser;
+        return new SchemaContainer($this, $schemas);
     }
 }
diff --git a/lib/classes/JsonApi/JsonApiIntegration/JsonApiTrait.php b/lib/classes/JsonApi/JsonApiIntegration/JsonApiTrait.php
deleted file mode 100644
index 3a5e122fe07899c2092c0712383a33d849314daf..0000000000000000000000000000000000000000
--- a/lib/classes/JsonApi/JsonApiIntegration/JsonApiTrait.php
+++ /dev/null
@@ -1,428 +0,0 @@
-<?php
-
-namespace JsonApi\JsonApiIntegration;
-
-use JsonApi\Middlewares\Authentication;
-use Neomerx\JsonApi\Contracts\Factories\FactoryInterface;
-use Neomerx\JsonApi\Contracts\Codec\CodecMatcherInterface;
-use Neomerx\JsonApi\Contracts\Parameters\ParametersInterface;
-use Neomerx\JsonApi\Contracts\Parameters\ParametersCheckerInterface;
-use Neomerx\JsonApi\Contracts\Http\ResponsesInterface;
-use Neomerx\JsonApi\Contracts\Encoder\Parameters\EncodingParametersInterface;
-use Neomerx\JsonApi\Contracts\Http\Headers\HeaderParametersInterface;
-use Neomerx\JsonApi\Contracts\Schema\ContainerInterface;
-use Psr\Http\Message\ServerRequestInterface as Request;
-
-/**
- * Dieser Trait beinhaltet Instanzmethoden und -variablen, die in
- * JSON-API-Routen verwendet werden können, um entsprechende
- * JSON-API-Responses zu generieren.
- *
- * @author info@neomerx.com (www.neomerx.com)
- * @author mlunzena@uos.de
- *
- * @see https://github.com/InactiveProjects/limoncello/blob/v0.5.1/src/Http/JsonApiTrait.php
- */
-trait JsonApiTrait
-{
-    /**
-     * If unrecognized parameters should be allowed in input parameters.
-     *
-     * @var bool
-     */
-    protected $allowUnrecognizedParams = false;
-
-    /**
-     * A list of allowed include paths in input parameters.
-     *
-     * Empty array [] means clients are not allowed to specify include paths and 'null' means all paths are allowed.
-     *
-     * @var string[]|null
-     */
-    protected $allowedIncludePaths = [];
-
-    /**
-     * A list of JSON API types which clients can sent field sets to.
-     *
-     * Possible values
-     *
-     * $allowedFieldSetTypes = null; // <-- for all types all fields are allowed
-     *
-     * $allowedFieldSetTypes = []; // <-- non of the types and fields are allowed
-     *
-     * $allowedFieldSetTypes = [
-     *      'people'   => null,              // <-- all fields for 'people' are allowed
-     *      'comments' => [],                // <-- no fields for 'comments' are allowed (all denied)
-     *      'posts'    => ['title', 'body'], // <-- only 'title' and 'body' fields are allowed for 'posts'
-     * ];
-     *
-     * @var array|null
-     */
-    protected $allowedFieldSetTypes = null;
-
-    /**
-     * A list of allowed sort field names in input parameters.
-     *
-     * Empty array [] means clients are not allowed to specify sort fields and 'null' means all fields are allowed.
-     *
-     * @var string[]|null
-     */
-    protected $allowedSortFields = [];
-
-    /**
-     * A list of allowed pagination input parameters (e.g 'number', 'size', 'offset' and etc).
-     *
-     * Empty array [] means clients are not allowed to specify paging and 'null' means all parameters are allowed.
-     *
-     * @var string[]|null
-     */
-    protected $allowedPagingParameters = [];
-
-    /**
-     * A list of allowed filtering input parameters.
-     *
-     * Empty array [] means clients are not allowed to specify filtering and 'null' means all parameters are allowed.
-     *
-     * @var string[]|null
-     */
-    protected $allowedFilteringParameters = [];
-
-    /**
-     * @var Psr\Container\ContainerInterface;
-     */
-    protected $container;
-
-    /**
-     * @var ParametersCheckerInterface
-     */
-    private $parametersChecker;
-
-    /**
-     * @var bool
-     */
-    private $parametersChecked = false;
-
-    /**
-     * Init integrations with JSON API implementation.
-     *
-     * @param $container
-     */
-    private function initJsonApiSupport($container)
-    {
-        $this->container = $container;
-
-        $factory = $container[FactoryInterface::class];
-
-        $this->parametersChecker = $factory->createQueryChecker(
-            $this->allowUnrecognizedParams,
-            $this->allowedIncludePaths,
-            $this->allowedFieldSetTypes,
-            $this->allowedSortFields,
-            $this->allowedPagingParameters,
-            $this->allowedFilteringParameters
-        );
-    }
-
-    // ***** RESPONSE GENERATORS *****
-
-    /**
-     * Get response with HTTP code only.
-     *
-     * @param $statusCode
-     *
-     * @return Response
-     */
-    protected function getCodeResponse($statusCode, array $headers = [])
-    {
-        $this->checkQueryParameters();
-        $responses = $this->container[ResponsesInterface::class];
-
-        return $responses->getCodeResponse($statusCode, $headers);
-    }
-
-    /**
-     * Get response with meta information only.
-     *
-     * @param array|object $meta       Meta information
-     * @param int          $statusCode
-     *
-     * @return Response
-     */
-    protected function getMetaResponse($meta, $statusCode = ResponsesInterface::HTTP_OK, $headers = [])
-    {
-        $this->checkQueryParameters();
-        $responses = $this->container[ResponsesInterface::class];
-
-        return $responses->getMetaResponse($meta, $statusCode, $headers);
-    }
-
-    /**
-     * Get response with regular JSON API Document in body.
-     *
-     * @param object|array                                                       $data
-     * @param int                                                                $statusCode
-     * @param array<string,\Neomerx\JsonApi\Contracts\Schema\LinkInterface>|null $links
-     * @param mixed                                                              $meta
-     *
-     * @return Response
-     */
-    protected function getContentResponse(
-        $data,
-        $statusCode = ResponsesInterface::HTTP_OK,
-        $links = null,
-        $meta = null,
-        array $headers = []
-    ) {
-        $this->checkQueryParameters();
-        $responses = $this->container[ResponsesInterface::class];
-
-        return $responses->getContentResponse($data, $statusCode, $links, $meta, $headers);
-    }
-
-    /**
-     * Get response with only resource identifiers.
-     *
-     * @param object|array $data
-     * @param int          $statusCode
-     * @param array|null   $links
-     * @param mixed        $meta
-     * @param array        $headers
-     *
-     * @return mixed
-     */
-    protected function getIdentifiersResponse(
-        $data,
-        $links = null,
-        $meta = null,
-        array $headers = []
-    ) {
-        $this->checkQueryParameters();
-        $responses = $this->container[ResponsesInterface::class];
-        $statusCode = ResponsesInterface::HTTP_OK;
-
-        return $responses->getIdentifiersResponse($data, $statusCode, $links, $meta, $headers);
-    }
-
-    protected function getPaginatedIdentifiersResponse(
-        $data,
-        $total,
-        $links = null,
-        $meta = null,
-        array $headers = []
-    ) {
-        $this->checkQueryParameters();
-        $responses = $this->container[ResponsesInterface::class];
-        $statusCode = ResponsesInterface::HTTP_OK;
-
-        list($offset, $limit) = $this->getOffsetAndLimit();
-
-        if (!$meta) {
-            $meta = [];
-        }
-
-        $meta['page'] = [
-            'offset' => (int) $offset,
-            'limit' => (int) $limit,
-        ];
-
-        if (isset($total)) {
-            $meta['page']['total'] = (int) $total;
-        }
-
-        $paginator = new Paginator($total, $offset, $limit);
-
-        if (!$links) {
-            $links = [];
-        }
-
-        foreach (words('first last prev next') as $rel) {
-            if (list($off, $lim) = $paginator->{'get'.ucfirst($rel).'PageOffsetAndLimit'}()) {
-                $links[$rel] = $this->createLink($off, $lim);
-            }
-        }
-
-        return $responses->getIdentifiersResponse($data, $statusCode, $links, $meta, $headers);
-    }
-
-    /**
-     * @param object                                                             $resource
-     * @param array<string,\Neomerx\JsonApi\Contracts\Schema\LinkInterface>|null $links
-     * @param mixed                                                              $meta
-     *
-     * @return Response
-     */
-    protected function getCreatedResponse(
-        $resource,
-        $links = null,
-        $meta = null,
-        array $headers = []
-    ) {
-        $this->checkQueryParameters();
-        $responses = $this->container[ResponsesInterface::class];
-
-        return $responses->getCreatedResponse($resource, $links, $meta, $headers);
-    }
-
-    protected function getPaginatedContentResponse(
-        $data,
-        $total,
-        $statusCode = ResponsesInterface::HTTP_OK,
-        $links = null,
-        $meta = null,
-        array $headers = []
-    ) {
-        $this->checkQueryParameters();
-        $responses = $this->container[ResponsesInterface::class];
-
-        list($offset, $limit) = $this->getOffsetAndLimit();
-
-        if (!$meta) {
-            $meta = [];
-        }
-
-        $meta['page'] = [
-            'offset' => (int) $offset,
-            'limit' => (int) $limit,
-        ];
-
-        if (isset($total)) {
-            $meta['page']['total'] = (int) $total;
-        }
-
-        $paginator = new Paginator($total, $offset, $limit);
-
-        if (!$links) {
-            $links = [];
-        }
-
-        foreach (words('first last prev next') as $rel) {
-            if (list($off, $lim) = $paginator->{'get'.ucfirst($rel).'PageOffsetAndLimit'}()) {
-                $links[$rel] = $this->createLink($off, $lim);
-            }
-        }
-
-        return $responses->getContentResponse($data, $statusCode, $links, $meta, $headers);
-    }
-
-    /**
-     * @return mixed
-     */
-    protected function getDocument()
-    {
-        $request = $this->container['request'];
-        $codecMatcher = $this->container[CodecMatcherInterface::class];
-        if ($codecMatcher->getDecoder() === null) {
-            $parameters = $this->container[HeaderParametersInterface::class];
-            $codecMatcher->matchDecoder($parameters->getContentTypeHeader());
-        }
-        $decoder = $codecMatcher->getDecoder();
-
-        return $decoder->decode($request->getBody()->getContents());
-    }
-
-    protected function checkQueryParameters()
-    {
-        $this->parametersChecker->checkQuery($this->container[EncodingParametersInterface::class]);
-        $this->parametersChecked = true;
-    }
-
-    /**
-     * @return ParametersInterface
-     */
-    protected function getQueryParameters()
-    {
-        if ($this->parametersChecked === false) {
-            $this->checkQueryParameters();
-        }
-
-        return $this->container[EncodingParametersInterface::class];
-    }
-
-    /**
-     * Liefert Offset und Limit aus den Request-Parametern zurück.
-     *
-     * @param int $offsetDefault optional; gibt den Standard-Offset
-     *                           an, falls dieser Wert nicht im Request gesetzt ist
-     * @param int $limitDefault  optional; gibt das Standard-Limit an,
-     *                           falls dieser Wert nicht im Request gesetzt ist
-     *
-     * @return array {
-     *
-     *     @var int $offset der im Request gesetzte Offset oder
-     *     ansonsten der Default-Wert 0
-     *     @var int $limit das im Request gesetzte Limit oder
-     *     ansonsten der Default-Wert 30
-     * }
-     */
-    protected function getOffsetAndLimit($offsetDefault = 0, $limitDefault = 30)
-    {
-        $params = $this->getQueryParameters()->getPaginationParameters();
-
-        return [
-            $params && array_key_exists('offset', $params) ? (int) $params['offset'] : $offsetDefault,
-            $params && array_key_exists('limit', $params) ? (int) $params['limit'] : $limitDefault,
-        ];
-    }
-
-    private function createLink($offset, $limit)
-    {
-        $factory = $this->container[FactoryInterface::class];
-        $request = $this->container['request'];
-
-        $queryParams = $request->getQueryParams();
-        $queryParams['page']['offset'] = $offset;
-        $queryParams['page']['limit'] = $limit;
-
-        $uri = $request->getUri()->withQuery(http_build_query($queryParams));
-
-        $basePath = $uri->getBasePath();
-        $path = $uri->getPath();
-        $query = $uri->getQuery();
-        $fragment = $uri->getFragment();
-
-        $uriString = $basePath.'/'.ltrim($path, '/')
-                   .($query ? '?'.$query : '')
-                   .($fragment ? '#'.$fragment : '');
-
-        return $factory->createLink($uriString, null, true);
-    }
-
-    /**
-     * Gibt null oder das User-Objekt des "eingeloggten" Nutzers zurück.
-     *
-     * @param request Request der eingehende Request
-     *
-     * @return mixed entweder null oder das User-Objekt des
-     *               "eingeloggten" Nutzers
-     */
-    public function getUser(Request $request)
-    {
-        return $request->getAttribute(Authentication::USER_KEY, null);
-    }
-
-    /**
-     * Gibt das Schema zu einer beliebigen Ressource zurück.
-     *
-     * @param resource Object  die Ressource, zu der das Schema geliefert werden soll
-     *
-     * @return SchemaProviderInterface das Schema zur Ressource
-     */
-    protected function getSchema($resource)
-    {
-        return $this->container[ContainerInterface::class]->getSchema($resource);
-    }
-
-    /**
-     * Ermöglicht JSON-Routen das ermitteln der resource location URL
-     * eines Objekts.
-     *
-     * @param resource Object  die Ressource zu der die URL gefunden
-     * werden soll
-     *
-     * @retunr String  die resource location URL
-     */
-    protected function getResourceLocationUrl($resource)
-    {
-        return $this->container[ResponsesInterface::class]->getResourceLocationUrl($resource);
-    }
-}
diff --git a/lib/classes/JsonApi/JsonApiIntegration/Paginator.php b/lib/classes/JsonApi/JsonApiIntegration/Paginator.php
index 09f8fc8f0da2fc7db8ca54440ea91dffefb80363..7e74f8066000a20c0771f7860d412e7d6cfb98b2 100644
--- a/lib/classes/JsonApi/JsonApiIntegration/Paginator.php
+++ b/lib/classes/JsonApi/JsonApiIntegration/Paginator.php
@@ -11,6 +11,20 @@ namespace JsonApi\JsonApiIntegration;
  */
 class Paginator
 {
+    /** @var int|null */
+    private $total;
+
+    /** @var int */
+    private $offset;
+
+    /** @var int */
+    private $limit;
+
+    /**
+     * @param int|null $total
+     * @param int      $offset
+     * @param int      $limit
+     */
     public function __construct($total, $offset, $limit)
     {
         $this->total = $total;
@@ -18,16 +32,16 @@ class Paginator
         $this->limit = $limit;
     }
 
-    public function getFirstPageOffsetAndLimit()
+    public function getFirstPageOffsetAndLimit(): array
     {
-        if ($this->limit === 0) {
+        if (0 === $this->limit) {
             return [0, 0];
         }
 
         return [0, $this->limit];
     }
 
-    public function getLastPageOffsetAndLimit()
+    public function getLastPageOffsetAndLimit(): ?array
     {
         if (!isset($this->total)) {
             return null;
@@ -42,7 +56,7 @@ class Paginator
         return [$last, $this->limit];
     }
 
-    public function getPrevPageOffsetAndLimit()
+    public function getPrevPageOffsetAndLimit(): ?array
     {
         if ($this->limit === 0) {
             return null;
@@ -55,7 +69,7 @@ class Paginator
         return [max(0, $this->offset - $this->limit), $this->limit];
     }
 
-    public function getNextPageOffsetAndLimit()
+    public function getNextPageOffsetAndLimit(): ?array
     {
         if ($this->limit === 0) {
             return null;
diff --git a/lib/classes/JsonApi/JsonApiIntegration/Parser.php b/lib/classes/JsonApi/JsonApiIntegration/Parser.php
new file mode 100644
index 0000000000000000000000000000000000000000..6e460d9a9917117f629f77751a84728d4f22fc8c
--- /dev/null
+++ b/lib/classes/JsonApi/JsonApiIntegration/Parser.php
@@ -0,0 +1,75 @@
+<?php
+
+namespace JsonApi\JsonApiIntegration;
+
+use Neomerx\JsonApi\Contracts\Factories\FactoryInterface;
+use Neomerx\JsonApi\Contracts\Schema\SchemaContainerInterface;
+use Neomerx\JsonApi\Contracts\Parser\EditableContextInterface;
+use Neomerx\JsonApi\Exceptions\InvalidArgumentException;
+use Neomerx\JsonApi\Parser\Parser as NeomerxParser;
+use SimpleORMap;
+use function Neomerx\JsonApi\I18n\format as _;
+
+/**
+ * Eine Instanz von Neomerx\JsonApi\Encoder\Parser\Parser wird
+ * benötigt, um Werte, die an den JSON-API-Encoder gehen, zu
+ * analysieren und entsprechned weiter zu verarbeiten. Unter anderem
+ * wird darin auch die Unterscheidung getroffen, ob Werte, die an den
+ * JSON-API-Encoder gehen, Collections sind oder nicht.
+ *
+ * Bei dieser Analyse werden sinnvollerweise alle Werte, die das
+ * PHP-Interface \IteratorAggregate implementieren, als Collections
+ * behandelt. Da aber die Stud.IP-Klasse \SimpleORMap
+ * ungewöhnlicherweise ebenfalls dieses Interface implementiert, muss
+ * hier eine Sonderbehandlung stattfinden.
+ *
+ * Dazu wird die Methode
+ * Neomerx\JsonApi\Encoder\Parser\Parser::analyzeCurrentData so
+ * überschrieben, dass Instanzen von \SimpleORMap nicht als
+ * Collections gelten.
+ *
+ * @see Neomerx\JsonApi\Parser\Parser
+ * @see \SimpleORMap
+ */
+class Parser extends NeomerxParser
+{
+    /**
+     * @var SchemaContainerInterface
+     */
+    private $schemaContainer;
+
+    /**
+     * As `$schemaContainer` is private in \Neomerx\JsonApi\Parser\Parser it has
+     * to be stored again in this subclass.
+     *
+     * @param FactoryInterface         $factory
+     * @param SchemaContainerInterface $container
+     * @param EditableContextInterface $context
+     */
+    public function __construct(
+        FactoryInterface $factory,
+        SchemaContainerInterface $container,
+        EditableContextInterface $context
+    ) {
+        $this->schemaContainer = $container;
+
+        parent::__construct($factory, $container, $context);
+    }
+
+    /**
+     * Show better error messages using instances of subclasses of \SimpleORMap
+     * without a Schema.
+     *
+     * @inheritdoc
+     */
+    public function parse($data, array $paths = []): iterable
+    {
+        \assert(\is_array($data) === true || \is_object($data) === true || $data === null);
+
+        if ($data instanceof SimpleORMap && $this->schemaContainer->hasSchema($data) !== true) {
+            throw new InvalidArgumentException(_(static::MSG_NO_SCHEMA_FOUND, \get_class($data)));
+        }
+
+        return parent::parse($data, $paths);
+    }
+}
diff --git a/lib/classes/JsonApi/JsonApiIntegration/QueryChecker.php b/lib/classes/JsonApi/JsonApiIntegration/QueryChecker.php
new file mode 100644
index 0000000000000000000000000000000000000000..ff2a03d26deae9f263fbb9f967456a7a7a07b3a9
--- /dev/null
+++ b/lib/classes/JsonApi/JsonApiIntegration/QueryChecker.php
@@ -0,0 +1,186 @@
+<?php
+
+namespace JsonApi\JsonApiIntegration;
+
+use Neomerx\JsonApi\Exceptions\JsonApiException;
+use Neomerx\JsonApi\Schema\ErrorCollection;
+
+class QueryChecker
+{
+    /**
+     * @var bool
+     */
+    private $allowUnrecognized;
+
+    /**
+     * @var array|null
+     */
+    private $includePaths;
+
+    /**
+     * @var array|null
+     */
+    private $fieldSetTypes;
+
+    /**
+     * @var array|null
+     */
+    private $pagingParameters;
+
+    /**
+     * @var array|null
+     */
+    private $sortParameters;
+
+    /**
+     * @var array|null
+     */
+    private $filteringParameters;
+
+    public function __construct(
+        bool $allowUnrecognized = true,
+        array $includePaths = null,
+        array $fieldSetTypes = null,
+        array $sortParameters = null,
+        array $pagingParameters = null,
+        array $filteringParameters = null
+    ) {
+        $this->includePaths = $includePaths;
+        $this->allowUnrecognized = $allowUnrecognized;
+        $this->fieldSetTypes = $fieldSetTypes;
+        $this->sortParameters = $this->flip($sortParameters);
+        $this->pagingParameters = $this->flip($pagingParameters);
+        $this->filteringParameters = $this->flip($filteringParameters);
+    }
+
+    public function checkQuery(QueryParserInterface $queryParser): void
+    {
+        $errors = new ErrorCollection();
+
+        $this->checkIncludePaths($errors, $queryParser);
+        $this->checkFieldSets($errors, $queryParser);
+        $this->checkFiltering($errors, $queryParser);
+        $this->checkSorting($errors, $queryParser);
+        $this->checkPaging($errors, $queryParser);
+        $this->checkUnrecognized($errors, $queryParser);
+
+        if ($errors->count()) {
+            throw new JsonApiException($errors, JsonApiException::HTTP_CODE_BAD_REQUEST);
+        }
+    }
+
+    protected function checkIncludePaths(ErrorCollection $errors, QueryParserInterface $queryParser): void
+    {
+        $withinAllowed = $this->valuesWithinAllowed(
+            iterator_to_array($queryParser->getIncludePaths()),
+            $this->includePaths
+        );
+        if (!$withinAllowed) {
+            $errors->addQueryParameterError(
+                QueryParser::PARAM_INCLUDE,
+                'Include paths should contain only allowed ones.'
+            );
+        }
+    }
+
+    protected function checkFieldSets(ErrorCollection $errors, QueryParserInterface $queryParser): void
+    {
+        $withinAllowed = $this->isFieldsAllowed(iterator_to_array($queryParser->getFields()));
+        if (!$withinAllowed) {
+            $errors->addQueryParameterError(QueryParser::PARAM_FIELDS, 'Field sets should contain only allowed ones.');
+        }
+    }
+
+    protected function checkFiltering(ErrorCollection $errors, QueryParserInterface $queryParser): void
+    {
+        $withinAllowed = $this->keysWithinAllowed(
+            iterator_to_array($queryParser->getFilters()),
+            $this->filteringParameters
+        );
+        if (!$withinAllowed) {
+            $errors->addQueryParameterError(QueryParser::PARAM_FILTER, 'Filter should contain only allowed values.');
+        }
+    }
+
+    protected function checkSorting(ErrorCollection $errors, QueryParserInterface $queryParser): void
+    {
+        if (null !== $queryParser->getSorts() && null !== $this->sortParameters) {
+            foreach ($queryParser->getSorts() as $sortParameter) {
+                if (!array_key_exists($sortParameter->getField(), $this->sortParameters)) {
+                    $errors->addQueryParameterError(
+                        QueryParser::PARAM_SORT,
+                        sprintf('Sort parameter %s is not allowed.', $sortParameter->getField())
+                    );
+                }
+            }
+        }
+    }
+
+    protected function checkPaging(ErrorCollection $errors, QueryParserInterface $queryParser): void
+    {
+        $withinAllowed = $this->keysWithinAllowed(
+            iterator_to_array($queryParser->getPagination()),
+            $this->pagingParameters
+        );
+        if (!$withinAllowed) {
+            $errors->addQueryParameterError(
+                QueryParser::PARAM_PAGE,
+                'Page parameter should contain only allowed values.'
+            );
+        }
+    }
+
+    protected function checkUnrecognized(ErrorCollection $errors, QueryParserInterface $queryParser): void
+    {
+        if (!$this->allowUnrecognized && !empty($queryParser->getUnrecognizedParameters())) {
+            foreach ($queryParser->getUnrecognizedParameters() as $name => $value) {
+                $errors->addQueryParameterError($name, 'Unrecognized Parameter.');
+            }
+        }
+    }
+
+    private function keysWithinAllowed(array $toCheck = null, array $allowed = null): bool
+    {
+        return null === $toCheck || null === $allowed || empty(array_diff_key($toCheck, $allowed));
+    }
+
+    private function valuesWithinAllowed(array $toCheck = null, array $allowed = null): bool
+    {
+        return null === $toCheck || null === $allowed || empty(array_diff($toCheck, $allowed));
+    }
+
+    /**
+     * @return array|null
+     */
+    private function flip(array $array = null)
+    {
+        return $array === null ? null : array_flip($array);
+    }
+
+    /**
+     * Check input fields against allowed.
+     *
+     * @param array|null $fields
+     */
+    private function isFieldsAllowed(array $fields = null): bool
+    {
+        if ($this->fieldSetTypes === null || $fields === null) {
+            return true;
+        }
+
+        foreach ($fields as $type => $requestedFields) {
+            if (array_key_exists($type, $this->fieldSetTypes) === false) {
+                return false;
+            }
+
+            $allowedFields = $this->fieldSetTypes[$type];
+
+            // if not all fields are allowed and requested more fields than allowed
+            if ($allowedFields !== null && empty(array_diff($requestedFields, $allowedFields)) === false) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+}
diff --git a/lib/classes/JsonApi/JsonApiIntegration/QueryParser.php b/lib/classes/JsonApi/JsonApiIntegration/QueryParser.php
new file mode 100644
index 0000000000000000000000000000000000000000..a33979e2a091852e66d008b52a99517333315952
--- /dev/null
+++ b/lib/classes/JsonApi/JsonApiIntegration/QueryParser.php
@@ -0,0 +1,76 @@
+<?php
+
+namespace JsonApi\JsonApiIntegration;
+
+use Neomerx\JsonApi\Contracts\Http\Query\BaseQueryParserInterface as P;
+use Neomerx\JsonApi\Http\Query\BaseQueryParser;
+use Neomerx\JsonApi\Exceptions\JsonApiException;
+use Neomerx\JsonApi\Schema\Error;
+
+/**
+ */
+class QueryParser extends BaseQueryParser implements QueryParserInterface
+{
+    public function getFilteringParameters(): array
+    {
+        return iterator_to_array($this->getFilters());
+    }
+
+    public function getFilters(): iterable
+    {
+        $parameters = $this->getParameters();
+        if (array_key_exists(P::PARAM_FILTER, $parameters)) {
+            $filters = $parameters[P::PARAM_FILTER];
+            if (!is_array($filters) || empty($filters)) {
+                $errorTitle = $this->getMessage(static::MSG_ERR_INVALID_PARAMETER);
+                $error = new Error(null, null, null, null, null, $errorTitle, null, [
+                    Error::SOURCE_PARAMETER => P::PARAM_FILTER,
+                ]);
+                throw new JsonApiException($error);
+            }
+
+            foreach ($filters as $type => $field) {
+                yield $type => $field;
+            }
+        }
+    }
+
+    /**
+     * Get pagination parameters from encoder.
+     *
+     * @return iterable
+     */
+    public function getPagination(): iterable
+    {
+        $parameters = $this->getParameters();
+        if (array_key_exists(P::PARAM_PAGE, $parameters)) {
+            $pagination = $parameters[P::PARAM_PAGE];
+            if (!is_array($pagination) || empty($pagination)) {
+                $errorTitle = $this->getMessage(static::MSG_ERR_INVALID_PARAMETER);
+                $error = new Error(null, null, null, null, null, $errorTitle, null, [
+                    Error::SOURCE_PARAMETER => P::PARAM_PAGE,
+                ]);
+                throw new JsonApiException($error);
+            }
+
+            foreach ($pagination as $type => $field) {
+                yield $type => $field;
+            }
+        }
+    }
+
+    public function getUnrecognizedParameters(): iterable
+    {
+        $parameters = $this->getParameters();
+        $supported = [
+            P::PARAM_INCLUDE => 0,
+            P::PARAM_FIELDS  => 0,
+            P::PARAM_PAGE    => 0,
+            P::PARAM_FILTER  => 0,
+            P::PARAM_SORT    => 0,
+        ];
+        $unrecognized = array_diff_key($parameters, $supported);
+
+        return $unrecognized;
+    }
+}
diff --git a/lib/classes/JsonApi/JsonApiIntegration/QueryParserInterface.php b/lib/classes/JsonApi/JsonApiIntegration/QueryParserInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..ad37cb7a08f951bc810b2b56bfafc95b129d6e11
--- /dev/null
+++ b/lib/classes/JsonApi/JsonApiIntegration/QueryParserInterface.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace JsonApi\JsonApiIntegration;
+
+use Neomerx\JsonApi\Contracts\Http\Query\BaseQueryParserInterface;
+
+interface QueryParserInterface extends BaseQueryParserInterface
+{
+    /** Query parameter */
+    const PARAM_PAGING_LIMIT = 'limit';
+
+    /** Query parameter */
+    const PARAM_PAGING_OFFSET = 'offset';
+
+    public function getIncludePaths(): iterable;
+
+    public function getFilters(): iterable;
+
+    /**
+     * @return iterable
+     */
+    public function getPagination(): iterable;
+
+    public function getUnrecognizedParameters(): iterable;
+}
diff --git a/lib/classes/JsonApi/JsonApiIntegration/Responses.php b/lib/classes/JsonApi/JsonApiIntegration/Responses.php
index e2f6b54bd73f9feeabc95feac0a71e5be5e46832..0b8ccb559eb3ca151949125f83c6408a3fbf3807 100644
--- a/lib/classes/JsonApi/JsonApiIntegration/Responses.php
+++ b/lib/classes/JsonApi/JsonApiIntegration/Responses.php
@@ -2,27 +2,23 @@
 
 namespace JsonApi\JsonApiIntegration;
 
+use Neomerx\JsonApi\Http\BaseResponses;
 use Neomerx\JsonApi\Contracts\Encoder\EncoderInterface;
-use Neomerx\JsonApi\Contracts\Encoder\Parameters\EncodingParametersInterface;
 use Neomerx\JsonApi\Contracts\Http\Headers\MediaTypeInterface;
+use Slim\Psr7\Headers;
+use Slim\Psr7\Response;
+
+use Neomerx\JsonApi\Contracts\Encoder\Parameters\EncodingParametersInterface;
 use Neomerx\JsonApi\Contracts\Http\Headers\SupportedExtensionsInterface;
 use Neomerx\JsonApi\Contracts\Schema\ContainerInterface;
-use Neomerx\JsonApi\Http\Responses as NeomerxResponses;
-use Slim\Http\Headers;
-use Slim\Http\Response;
 
 /**
  * Diese Factory-Klasse verknüpft die "neomerx/json-api"-Bibliothek mit der
  * Slim-Applikation. Hier wird festgelegt, wie Slim-artige Response-Objekte gebildet
  * werden.
  */
-class Responses extends NeomerxResponses
+class Responses extends BaseResponses
 {
-    /**
-     * @var EncodingParametersInterface|null
-     */
-    private $parameters;
-
     /**
      * @var EncoderInterface
      */
@@ -33,48 +29,12 @@ class Responses extends NeomerxResponses
      */
     private $outputMediaType;
 
-    /**
-     * @var SupportedExtensionsInterface
-     */
-    private $extensions;
-
-    /**
-     * @var ContainerInterface
-     */
-    private $schemes;
-
-    /**
-     * @var null|string
-     */
-    private $urlPrefix;
-
-    /**
-     * Dieser Konstruktor wird in \JsonApi\Providers\JsonApiServices
-     * befüllt.
-     *
-     * @param MediaTypeInterface               $outputMediaType
-     * @param SupportedExtensionsInterface     $extensions
-     * @param EncoderInterface                 $encoder
-     * @param ContainerInterface               $schemes
-     * @param EncodingParametersInterface|null $parameters
-     * @param string|null                      $urlPrefix
-     *
-     * @internal
-     */
     public function __construct(
-        MediaTypeInterface $outputMediaType,
-        SupportedExtensionsInterface $extensions,
         EncoderInterface $encoder,
-        ContainerInterface $schemes,
-        EncodingParametersInterface $parameters = null,
-        $urlPrefix = null
+        MediaTypeInterface $outputMediaType
     ) {
-        $this->extensions = $extensions;
         $this->encoder = $encoder;
         $this->outputMediaType = $outputMediaType;
-        $this->schemes = $schemes;
-        $this->urlPrefix = $urlPrefix;
-        $this->parameters = $parameters;
     }
 
     /**
@@ -87,13 +47,13 @@ class Responses extends NeomerxResponses
      *                                zukünftigen Response
      * @param array       $headers    die Header der zukünftigen Response
      *
-     * @return \Slim\Http\Response die fertige Slim-Response
+     * @return mixed die fertige Slim-Response
      */
-    protected function createResponse($content, $statusCode, array $headers)
+    protected function createResponse(?string $content, int $statusCode, array $headers)
     {
         $headers = new Headers($headers);
         $response = new Response($statusCode, $headers);
-        $response->getBody()->write($content);
+        $response->getBody()->write($content ?? '');
 
         return $response->withProtocolVersion('1.1');
     }
@@ -103,7 +63,7 @@ class Responses extends NeomerxResponses
      *
      * @internal
      */
-    protected function getEncoder()
+    protected function getEncoder(): EncoderInterface
     {
         return $this->encoder;
     }
@@ -113,77 +73,8 @@ class Responses extends NeomerxResponses
      *
      * @internal
      */
-    protected function getUrlPrefix()
-    {
-        return $this->urlPrefix;
-    }
-
-    /**
-     * {@inheritdoc}
-     *
-     * @internal
-     */
-    protected function getEncodingParameters()
-    {
-        return $this->parameters;
-    }
-
-    /**
-     * {@inheritdoc}
-     *
-     * @internal
-     */
-    protected function getSchemaContainer()
-    {
-        return $this->schemes;
-    }
-
-    /**
-     * {@inheritdoc}
-     *
-     * @internal
-     */
-    protected function getSupportedExtensions()
-    {
-        return $this->extensions;
-    }
-
-    /**
-     * {@inheritdoc}
-     *
-     * @internal
-     */
-    protected function getMediaType()
+    protected function getMediaType(): MediaTypeInterface
     {
         return $this->outputMediaType;
     }
-
-    /**
-     * {@inheritdoc}
-     */
-    public function getIdentifiersResponse(
-        $data,
-        $statusCode = self::HTTP_OK,
-        $links = null,
-        $meta = null,
-        array $headers = []
-    ) {
-        $encoder = $this->getEncoder();
-
-        $links === null ?: $encoder->withLinks($links);
-        $meta === null ?: $encoder->withMeta($meta);
-        $content = $encoder->encodeIdentifiers($data, $this->getEncodingParameters());
-
-        return $this->createJsonApiResponse($content, $statusCode, $headers);
-    }
-
-    /**
-     * Widen method visibility from protected to public.
-     *
-     * {@inheritdoc}
-     */
-    public function getResourceLocationUrl($resource)
-    {
-        return parent::getResourceLocationUrl($resource);
-    }
 }
diff --git a/lib/classes/JsonApi/JsonApiIntegration/SchemaContainer.php b/lib/classes/JsonApi/JsonApiIntegration/SchemaContainer.php
new file mode 100644
index 0000000000000000000000000000000000000000..a299084563203a0a63b4414ef99bead910a9d2a4
--- /dev/null
+++ b/lib/classes/JsonApi/JsonApiIntegration/SchemaContainer.php
@@ -0,0 +1,104 @@
+<?php
+
+namespace JsonApi\JsonApiIntegration;
+
+use Closure;
+use Neomerx\JsonApi\Contracts\Schema\SchemaInterface;
+use Neomerx\JsonApi\Exceptions\InvalidArgumentException;
+use function Neomerx\JsonApi\I18n\format as _;
+use Neomerx\JsonApi\Schema\SchemaContainer as NeomerxSchemaContainer;
+
+class SchemaContainer extends NeomerxSchemaContainer
+{
+    /**
+     * The original SchemaContainer does not like mappings of interfaces to schemas.
+     * So this method now allows classes *and* interfaces.
+     *
+     * @inheritdoc
+     */
+    public function register(string $type, $schema): void
+    {
+        if (true === empty($type) || (false === \class_exists($type) && false === \interface_exists($type))) {
+            throw new InvalidArgumentException(_(static::MSG_INVALID_MODEL_TYPE));
+        }
+
+        $isOk =
+            (true === \is_string($schema) &&
+                false === empty($schema) &&
+                true === \class_exists($schema) &&
+                true === \in_array(SchemaInterface::class, \class_implements($schema))) ||
+            \is_callable($schema) ||
+            $schema instanceof SchemaInterface;
+        if (false === $isOk) {
+            throw new InvalidArgumentException(_(static::MSG_INVALID_SCHEME, $type));
+        }
+
+        if (true === $this->hasProviderMapping($type)) {
+            throw new InvalidArgumentException(_(static::MSG_TYPE_REUSE_FORBIDDEN, $type));
+        }
+
+        if ($schema instanceof SchemaInterface) {
+            $this->setProviderMapping($type, \get_class($schema));
+            $this->setResourceToJsonTypeMapping($schema->getType(), $type);
+            $this->setCreatedProvider($type, $schema);
+        } else {
+            $this->setProviderMapping($type, $schema);
+        }
+    }
+
+    /**
+     * @param callable $callable
+     *
+     * @return SchemaInterface
+     */
+    protected function createSchemaFromCallable(callable $callable): SchemaInterface
+    {
+        $schema = \call_user_func($callable, $this);
+
+        return $schema;
+    }
+
+    /**
+     * @param string $type
+     *
+     * @return bool
+     */
+    protected function hasProviderMapping(string $type): bool
+    {
+        if (parent::hasProviderMapping($type)) {
+            return true;
+        }
+
+        foreach ($this->getParentClassesAndInterfaces($type) as $class) {
+            if (parent::hasProviderMapping($class)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * @param string $type
+     *
+     * @return mixed
+     */
+    protected function getProviderMapping(string $type)
+    {
+        if (parent::hasProviderMapping($type)) {
+            return parent::getProviderMapping($type);
+        }
+
+        foreach ($this->getParentClassesAndInterfaces($type) as $class) {
+            if (parent::hasProviderMapping($class)) {
+                return parent::getProviderMapping($class);
+            }
+        }
+        throw new InvalidArgumentException(_('Cannot find schema for type `%s`', $type));
+    }
+
+    private function getParentClassesAndInterfaces(string $type): array
+    {
+        return class_exists($type) ? @class_parents($type) + @class_implements($type) : [];
+    }
+}
diff --git a/lib/classes/JsonApi/Middlewares/Auth/HttpBasicAuthStrategy.php b/lib/classes/JsonApi/Middlewares/Auth/HttpBasicAuthStrategy.php
index 57facebb0253ddae58556b61e7ed7ae182a842fd..42b18ae2ac57cc62bb8a38c5c3ea4c671bdc4610 100644
--- a/lib/classes/JsonApi/Middlewares/Auth/HttpBasicAuthStrategy.php
+++ b/lib/classes/JsonApi/Middlewares/Auth/HttpBasicAuthStrategy.php
@@ -2,15 +2,24 @@
 
 namespace JsonApi\Middlewares\Auth;
 
-use Psr\Http\Message\ServerRequestInterface as Request;
 use Psr\Http\Message\ResponseInterface as Response;
+use Psr\Http\Message\ServerRequestInterface as Request;
 
 class HttpBasicAuthStrategy implements Strategy
 {
-    protected $user;
+    /** @var callable */
+    protected $authenticator;
 
+    /** @var Request */
     protected $request;
 
+    /** @var ?\User */
+    protected $user;
+
+    /**
+     * @param Request $request
+     * @param callable $authenticator
+     */
     public function __construct(Request $request, $authenticator)
     {
         $this->request = $request;
diff --git a/lib/classes/JsonApi/Middlewares/Auth/OAuth1Strategy.php b/lib/classes/JsonApi/Middlewares/Auth/OAuth1Strategy.php
index 9d465ec3dca492163d6c20fea9309758854526dc..113ee09afe62ae429a4ccbe1fedeec75d550e325 100644
--- a/lib/classes/JsonApi/Middlewares/Auth/OAuth1Strategy.php
+++ b/lib/classes/JsonApi/Middlewares/Auth/OAuth1Strategy.php
@@ -2,15 +2,23 @@
 
 namespace JsonApi\Middlewares\Auth;
 
-use Psr\Http\Message\ServerRequestInterface as Request;
 use Psr\Http\Message\ResponseInterface as Response;
+use Psr\Http\Message\ServerRequestInterface as Request;
 
 class OAuth1Strategy implements Strategy
 {
-    protected $user;
+    /** @var callable */
+    protected $authenticator;
 
+    /** @var Request */
     protected $request;
 
+    /** @var ?\User */
+    protected $user;
+
+    /**
+     * @param callable $authenticator
+     */
     public function __construct(Request $request, $authenticator)
     {
         $this->request = $request;
@@ -40,7 +48,7 @@ class OAuth1Strategy implements Strategy
         return $response; //->withHeader('WWW-Authenticate', sprintf('Basic realm="%s"', 'Stud.IP JSON-API'));
     }
 
-    private function detect()
+    private function detect(): ?\User
     {
         if (!\OAuthRequestVerifier::requestIsSigned()) {
             return null;
@@ -75,10 +83,11 @@ class OAuth1Strategy implements Strategy
             return null;
         }
 
+        /** @var \User */
         return \User::find($userId);
     }
 
-    private function getParamsFromAuthorizationHeader(Request $request, array $params)
+    private function getParamsFromAuthorizationHeader(Request $request, array $params): array
     {
         if ($request->hasHeader('Authorization')) {
             $auth = $request->getHeaderLine('Authorization');
diff --git a/lib/classes/JsonApi/Middlewares/Auth/SessionStrategy.php b/lib/classes/JsonApi/Middlewares/Auth/SessionStrategy.php
index 101b172922923e586d78d928d854efcfd96aee77..26c0bc22fe621d33a3c596f6cfa43be284a04359 100644
--- a/lib/classes/JsonApi/Middlewares/Auth/SessionStrategy.php
+++ b/lib/classes/JsonApi/Middlewares/Auth/SessionStrategy.php
@@ -6,6 +6,7 @@ use Psr\Http\Message\ResponseInterface as Response;
 
 class SessionStrategy implements Strategy
 {
+    /** @var ?\User */
     protected $user;
 
     public function check()
@@ -22,7 +23,8 @@ class SessionStrategy implements Strategy
             return $this->user;
         }
 
-        $isAuthenticated = isset($GLOBALS['auth']) && $GLOBALS['auth']->is_authenticated() && 'nobody' !== $GLOBALS['user']->id;
+        $isAuthenticated =
+            isset($GLOBALS['auth']) && $GLOBALS['auth']->is_authenticated() && 'nobody' !== $GLOBALS['user']->id;
 
         if ($isAuthenticated) {
             $this->user = $GLOBALS['user']->getAuthenticatedUser();
diff --git a/lib/classes/JsonApi/Middlewares/Auth/Strategy.php b/lib/classes/JsonApi/Middlewares/Auth/Strategy.php
index 69c92e56a3d44f45e3955b0bde8e739cc9ce8d00..6b26cedceb6cfc6e1f9a27576d5756a6e1cf4355 100644
--- a/lib/classes/JsonApi/Middlewares/Auth/Strategy.php
+++ b/lib/classes/JsonApi/Middlewares/Auth/Strategy.php
@@ -16,16 +16,16 @@ interface Strategy
     /**
      * Get the currently authenticated user.
      *
-     * @return \User|null
+     * @return ?\User
      */
     public function user();
 
     /**
      * Validate a user's credentials.
      *
-     * @param array $credentials
+     * @param Response $response the current response
      *
-     * @return bool
+     * @return Response the new response containing the challenge
      */
     public function addChallenge(Response $response);
 }
diff --git a/lib/classes/JsonApi/Middlewares/Authentication.php b/lib/classes/JsonApi/Middlewares/Authentication.php
index 1608524a1b5387898ff58ba946ae5f1016211708..3e4ee176a78a4998c1f5cb18f71788f8676ecc37 100644
--- a/lib/classes/JsonApi/Middlewares/Authentication.php
+++ b/lib/classes/JsonApi/Middlewares/Authentication.php
@@ -2,18 +2,11 @@
 
 namespace JsonApi\Middlewares;
 
+use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
+use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
+use Slim\Psr7\Response;
 
-/**
- * Diese Klasse ist eine "leere" Middleware, die noch implementiert
- * werden muss.
- *
- * Allerdings wird sie jetzt schon in \JsonApi\RouteMap
- * verwendet, um dort die autorisierten Routen abzusichern.
- *
- * @todo muss zu einem späteren Zeitpunk implementiert werden
- */
 class Authentication
 {
     // der Schlüssel des Request-Attributs, in dem der Stud.IP-Nutzer
@@ -24,14 +17,16 @@ class Authentication
 
     // a callable accepting two arguments username and password and
     // returning either null or a Stud.IP user object
+    /** @var callable */
     private $authenticator;
 
     /**
      * Der Konstruktor.
      *
-     * @param callable $authenticator ein Callable, das den
-     *                                Nutzernamen und das Passwort als Argumente erhält und damit
-     *                                entweder einen Stud.IP-User-Objekt oder null zurückgibt
+     * @param callable $authenticator ein Callable, das den Nutzernamen und
+     *                                das Passwort als Argumente erhält und
+     *                                damit entweder einen Stud.IP-User-Objekt
+     *                                oder null zurückgibt
      */
     public function __construct($authenticator)
     {
@@ -41,17 +36,14 @@ class Authentication
     /**
      * Hier muss die Autorisierung implementiert werden.
      *
-     * @param \Psr\Http\Message\ServerRequestInterface $request  das
-     *                                                           PSR-7 Request-Objekt
-     * @param \Psr\Http\Message\ResponseInterface      $response das PSR-7
-     *                                                           Response-Objekt
-     * @param callable                                 $next     das nächste Middleware-Callable
+     * @param Request        $request das Request-Objekt
+     * @param RequestHandler $handler der PSR-15 Request Handler
      *
-     * @return \Psr\Http\Message\ResponseInterface das neue Response-Objekt
+     * @return ResponseInterface das neue Response-Objekt
      *
      * @SuppressWarnings(PHPMD.Superglobals)
      */
-    public function __invoke(Request $request, Response $response, $next)
+    public function __invoke(Request $request, RequestHandler $handler)
     {
         $guards = [
             new Auth\SessionStrategy(),
@@ -63,17 +55,17 @@ class Authentication
             if ($guard->check()) {
                 $request = $this->provideUser($request, $guard->user());
 
-                return $next($request, $response);
+                return $handler->handle($request);
             }
         }
 
-        return $this->generateChallenges($response, $guards);
+        return $this->generateChallenges($guards);
     }
 
     // according to RFC 2616
-    private function generateChallenges(Response $response, array $guards)
+    private function generateChallenges(array $guards): Response
     {
-        $response = $response->withStatus(401);
+        $response = new Response(401);
 
         foreach ($guards as $guard) {
             $response = $guard->addChallenge($response);
@@ -85,10 +77,10 @@ class Authentication
     /**
      * @SuppressWarnings(PHPMD.Superglobals)
      */
-    private function provideUser(Request $request, \User $user)
+    private function provideUser(Request $request, \User $user): Request
     {
         if ('nobody' === $GLOBALS['user']->id) {
-            $GLOBALS['user'] = new \Seminar_User($user->id);
+            $GLOBALS['user'] = new \Seminar_User($user);
             $GLOBALS['auth'] = new \Seminar_Auth();
             $GLOBALS['auth']->auth = [
                 'uid' => $user->id,
diff --git a/lib/classes/JsonApi/Middlewares/DangerousRouteHandler.php b/lib/classes/JsonApi/Middlewares/DangerousRouteHandler.php
index 4695242087d8da02eb5879b857fe8051e183a08b..8ff78803a07a7db6fae3c230c8a2e7990dae2278 100644
--- a/lib/classes/JsonApi/Middlewares/DangerousRouteHandler.php
+++ b/lib/classes/JsonApi/Middlewares/DangerousRouteHandler.php
@@ -2,31 +2,30 @@
 
 namespace JsonApi\Middlewares;
 
+use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
+use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
+use Slim\Psr7\Response;
 
 /**
  * TODO.
  */
 class DangerousRouteHandler
 {
-
     /**
      * TODO.
      *
-     * @param \Psr\Http\Message\ServerRequestInterface $request  das
-     *                                                           PSR-7 Request-Objekt
-     * @param \Psr\Http\Message\ResponseInterface      $response das PSR-7
-     *                                                           Response-Objekt
-     * @param callable                                 $next     das nächste Middleware-Callable
+     * @param Request        $request das Request-Objekt
+     * @param RequestHandler $handler der PSR-15 Request Handler
      *
-     * @return \Psr\Http\Message\ResponseInterface die neue Response
+     * @return ResponseInterface das neue Response-Objekt
      */
-    public function __invoke(Request $request, Response $response, $next)
+    public function __invoke(Request $request, RequestHandler $handler)
     {
         if (\Config::get()->getValue('JSONAPI_DANGEROUS_ROUTES_ALLOWED')) {
-            return $next($request, $response);
+            return $handler->handle($request);
         }
+        $response = new Response();
 
         return $response->withStatus(503)->write('Service Unavailable.');
     }
diff --git a/lib/classes/JsonApi/Middlewares/JsonApi.php b/lib/classes/JsonApi/Middlewares/JsonApi.php
deleted file mode 100644
index cfc9024311f6ca1bffa121a9a1f67e5ef3c17e18..0000000000000000000000000000000000000000
--- a/lib/classes/JsonApi/Middlewares/JsonApi.php
+++ /dev/null
@@ -1,80 +0,0 @@
-<?php
-
-namespace JsonApi\Middlewares;
-
-use JsonApi\Errors\JsonApiExceptionHandler;
-use JsonApi\Providers\JsonApiConfig as JsonApiConfigProvider;
-use JsonApi\Providers\JsonApiServices as JsonApiServiceProvider;
-use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
-
-/**
- * Diese Middleware sorgt dafür, dass alle von ihr versorgten
- * (JSON-API-)Routen auf die entsprechend benötigten JSON-API-Services
- * zugreifen können. Außerdem sorgt sie dafür, dass ein
- * JSON-API-spezifischer Exception-Handler registriert wird.
- */
-class JsonApi
-{
-    /**
-     * Der Konstruktor.
-     *
-     * @param \Slim\App     $app    die Slim-Applikation
-     */
-    public function __construct($app)
-    {
-        $this->app = $app;
-    }
-
-    /**
-     * Hier wird der Dependency Container mit JSON-API-spezifischen
-     * Services befüllt und ein JSON-API-spezifischer
-     * Exception-Handler registriert.
-     *
-     * @param \Psr\Http\Message\ServerRequestInterface $request  das
-     *                                                           PSR-7 Request-Objekt
-     * @param \Psr\Http\Message\ResponseInterface      $response das PSR-7
-     *                                                           Response-Objekt
-     * @param callable                                 $next     das nächste Middleware-Callable
-     *
-     * @return \Psr\Http\Message\ResponseInterface die neue Response
-     */
-    public function __invoke(Request $request, Response $response, $next)
-    {
-        $container = $this->app->getContainer();
-
-        $container->register(new JsonApiConfigProvider());
-        $container->register(new JsonApiServiceProvider());
-
-        $this->registerExceptionHandler($container);
-
-        // TODO: wie kriegt man das hier korrekt hin?
-        $request->registerMediaTypeParser(
-            'application/vnd.api+json',
-            function ($input) {
-                return json_decode($input, true);
-            }
-        );
-
-        $response = $next($request, $response);
-
-        return $response;
-    }
-
-    /**
-     * Register exception handler.
-     */
-    private function registerExceptionHandler($container)
-    {
-        $previousHandler = null;
-        if ($container['errorHandler']) {
-            $previousHandler = $container['errorHandler'];
-        }
-
-        unset($container['errorHandler']);
-
-        $container['errorHandler'] = function ($container) use ($previousHandler) {
-            return new JsonApiExceptionHandler($container, $previousHandler);
-        };
-    }
-}
diff --git a/lib/classes/JsonApi/Middlewares/RemoveTrailingSlashes.php b/lib/classes/JsonApi/Middlewares/RemoveTrailingSlashes.php
index fb3d24ab008cfc0b60fd56aa45ef14bc3bd38d84..3a16702f2ab26e2ac534de80ccada198b184eb06 100644
--- a/lib/classes/JsonApi/Middlewares/RemoveTrailingSlashes.php
+++ b/lib/classes/JsonApi/Middlewares/RemoveTrailingSlashes.php
@@ -2,8 +2,10 @@
 
 namespace JsonApi\Middlewares;
 
+use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
+use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
+use Slim\Psr7\Response;
 
 /**
  * Diese Klasse definiert eine Middleware, die Requests  umleitet,
@@ -18,26 +20,35 @@ class RemoveTrailingSlashes
      * delegiert, sondern eine Response mit `Location`-Header also
      * einem Redirect zurückgegeben.
      *
-     * @param \Psr\Http\Message\ServerRequestInterface $request  das
-     *                                                           PSR-7 Request-Objekt
-     * @param \Psr\Http\Message\ResponseInterface      $response das PSR-7
-     *                                                           Response-Objekt
-     * @param callable                                 $next     das nächste Middleware-Callable
+     * @param Request        $request das Request-Objekt
+     * @param RequestHandler $handler der PSR-15 Request Handler
      *
-     * @return \Psr\Http\Message\ResponseInterface die neue Response
+     * @return ResponseInterface das neue Response-Objekt
      */
-    public function __invoke(Request $request, Response $response, $next)
+    public function __invoke(Request $request, RequestHandler $handler)
     {
         $uri = $request->getUri();
         $path = $uri->getPath();
-        if ($path != '/' && substr($path, -1) == '/') {
+
+        if ('/' != $path && '/' == substr($path, -1)) {
+            // recursively remove slashes when its more than 1 slash
+            $path = rtrim($path, '/');
+
             // permanently redirect paths with a trailing slash
             // to their non-trailing counterpart
-            $uri = $uri->withPath(substr($path, 0, -1));
+            $uri = $uri->withPath($path);
+
+            if ('GET' == $request->getMethod()) {
+                $response = new Response();
 
-            return $response->withRedirect((string) $uri, 301);
+                return $response
+                    ->withHeader('Location', (string) $uri)
+                    ->withStatus(301);
+            } else {
+                $request = $request->withUri($uri);
+            }
         }
 
-        return $next($request, $response);
+        return $handler->handle($request);
     }
 }
diff --git a/lib/classes/JsonApi/Middlewares/StudipMockNavigation.php b/lib/classes/JsonApi/Middlewares/StudipMockNavigation.php
index 72911ea90041b7c2e1818c9a9f24edbea2d8daa5..0bce8a41fd8226a5111f7da99b6a6e78c057e683 100644
--- a/lib/classes/JsonApi/Middlewares/StudipMockNavigation.php
+++ b/lib/classes/JsonApi/Middlewares/StudipMockNavigation.php
@@ -2,8 +2,9 @@
 
 namespace JsonApi\Middlewares;
 
+use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface as Request;
-use Psr\Http\Message\ResponseInterface as Response;
+use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
 
 class DummyNavigation extends \Navigation implements \ArrayAccess
 {
@@ -56,10 +57,16 @@ class DummyNavigation extends \Navigation implements \ArrayAccess
 
 class StudipMockNavigation
 {
-    public function __invoke(Request $request, Response $response, $next)
+    /**
+     * @param Request                 $request das PSR-7 Request-Objekt
+     * @param RequestHandlerInterface $handler das PSR-7 Response-Objekt
+     *
+     * @return ResponseInterface die neue Response
+     */
+    public function __invoke(Request $request, RequestHandler $handler)
     {
         \Navigation::setRootNavigation(new DummyNavigation('stuff'));
 
-        return $next($request, $response);
+        return $handler->handle($request);
     }
 }
diff --git a/lib/classes/JsonApi/Models/Studip.php b/lib/classes/JsonApi/Models/Studip.php
index 7e28263d06d9c656b6297cecd5ac05b56a5c737e..ce5fa1cfb9f44f0a17037fcf5e8b8b4ed9612296 100644
--- a/lib/classes/JsonApi/Models/Studip.php
+++ b/lib/classes/JsonApi/Models/Studip.php
@@ -4,13 +4,6 @@ namespace JsonApi\Models;
 
 class Studip
 {
-    private $container;
-
-    public function __construct($container)
-    {
-        $this->container = $container;
-    }
-
     public function getId()
     {
         return 'studip';
diff --git a/lib/classes/JsonApi/NonJsonApiController.php b/lib/classes/JsonApi/NonJsonApiController.php
index cf91ec5a4bfdf98c16eaf6d86f71b918e560b758..8384b54fdd10e41df5a5be93b67fe86096d092e4 100644
--- a/lib/classes/JsonApi/NonJsonApiController.php
+++ b/lib/classes/JsonApi/NonJsonApiController.php
@@ -4,7 +4,8 @@ namespace JsonApi;
 
 use JsonApi\Errors\InternalServerError;
 use JsonApi\Middlewares\Authentication;
-use Neomerx\JsonApi\Contracts\Schema\ContainerInterface as SchemaContainerInterface;
+use Neomerx\JsonApi\Contracts\Schema\SchemaContainerInterface;
+use Neomerx\JsonApi\Contracts\Schema\SchemaInterface;
 use Neomerx\JsonApi\Exceptions\JsonApiException;
 use Psr\Container\ContainerInterface;
 use Psr\Http\Message\ResponseInterface as Response;
@@ -15,38 +16,55 @@ use Psr\Http\Message\ServerRequestInterface as Request;
  */
 class NonJsonApiController
 {
+    /** @var \Psr\Container\ContainerInterface */
+    protected $container;
+
     public function __construct(ContainerInterface $container)
     {
         $this->container = $container;
     }
 
-    public function __invoke(Request $request, Response $response, $args)
+    /**
+     * @return \Psr\Http\Message\ResponseInterface
+     */
+    public function __invoke(Request $request, Response $response, array $args)
     {
         try {
             $response = $this->invoke($request, $response, $args);
         } catch (JsonApiException $exception) {
             $httpCode = $exception->getHttpCode();
             $errors = $exception->getErrors();
-            $reason = count($errors) ? $errors[0]->getId() : '';
+            $reason = count($errors) ? $errors[0]->getTitle() ?? '' : '';
 
-            $response = $response->withStatus($httpCode)->write($reason);
+            $response->getBody()->write($reason);
+            $response = $response->withStatus($httpCode);
         }
 
         return $response;
     }
 
-    public function invoke(Request $request, Response $response, $args)
+    public function invoke(Request $request, Response $response, array $args): Response
     {
         throw new InternalServerError();
     }
 
-    protected function getUser($request)
+    /**
+     * @return mixed
+     */
+    protected function getUser(Request $request)
     {
         return $request->getAttribute(Authentication::USER_KEY, null);
     }
 
-    protected function getSchema($resource)
+    /**
+     * Gibt das Schema zu einer beliebigen Ressource zurück.
+     *
+     * @param mixed $resource die Ressource, zu der das Schema geliefert werden soll
+     *
+     * @return SchemaInterface das Schema zur Ressource
+     */
+    protected function getSchema($resource): SchemaInterface
     {
-        return $this->container[SchemaContainerInterface::class]->getSchema($resource);
+        return $this->container->get(SchemaContainerInterface::class)->getSchema($resource);
     }
 }
diff --git a/lib/classes/JsonApi/Providers/JsonApiConfig.php b/lib/classes/JsonApi/Providers/JsonApiConfig.php
deleted file mode 100644
index 3a8e93a45fe573c65b655aa32a0d5eaf7d1a9c12..0000000000000000000000000000000000000000
--- a/lib/classes/JsonApi/Providers/JsonApiConfig.php
+++ /dev/null
@@ -1,31 +0,0 @@
-<?php
-
-namespace JsonApi\Providers;
-
-/**
- * Diese Klasse konfiguriert die JSON-API in der Slim-Applikation um
- * die Zuordnung von Schemata zu Stud.IP-Model-Klassen und setzt das
- * URL-Prefix für die interne Generierung von URIs.
- */
-class JsonApiConfig implements \Pimple\ServiceProviderInterface
-{
-    /** Config key for schema list */
-    const SCHEMAS = 'json-api-integration-schemas';
-
-    /** Config key for URL prefix that will be added to all document links which have $treatAsHref flag set to false */
-    const JSON_URL_PREFIX = 'json-api-integration-urlPrefix';
-
-    /**
-     * Diese Methode wird automatisch aufgerufen, wenn diese Klasse dem
-     * Dependency Container der Slim-Applikation hinzugefügt wird.
-     *
-     * Hier werden die Schema-Abbildungen und das JSON-API-URL-Präfix gesetzt.
-     *
-     * @param \Pimple\Container $container der Dependency Container
-     */
-    public function register(\Pimple\Container $container)
-    {
-        $container[self::SCHEMAS] = new \JsonApi\SchemaMap();
-        $container[self::JSON_URL_PREFIX] = rtrim(\URLHelper::getUrl('jsonapi.php/v1'), '/');
-    }
-}
diff --git a/lib/classes/JsonApi/Providers/JsonApiServices.php b/lib/classes/JsonApi/Providers/JsonApiServices.php
deleted file mode 100644
index 15b9eedb924af4c819424076a1e61d3a44e96728..0000000000000000000000000000000000000000
--- a/lib/classes/JsonApi/Providers/JsonApiServices.php
+++ /dev/null
@@ -1,169 +0,0 @@
-<?php
-
-namespace JsonApi\Providers;
-
-use JsonApi\Contracts\JsonApiPlugin;
-use JsonApi\JsonApiIntegration\Factory;
-use JsonApi\JsonApiIntegration\Responses;
-use JsonApi\Providers\JsonApiConfig as C;
-use Neomerx\JsonApi\Contracts\Codec\CodecMatcherInterface;
-use Neomerx\JsonApi\Contracts\Encoder\Parameters\EncodingParametersInterface;
-use Neomerx\JsonApi\Contracts\Factories\FactoryInterface;
-use Neomerx\JsonApi\Contracts\Http\Headers\HeaderParametersInterface;
-use Neomerx\JsonApi\Contracts\Http\Headers\HeadersCheckerInterface;
-use Neomerx\JsonApi\Contracts\Http\Headers\MediaTypeInterface;
-use Neomerx\JsonApi\Contracts\Http\ResponsesInterface;
-use Neomerx\JsonApi\Contracts\Schema\ContainerInterface;
-use Neomerx\JsonApi\Encoder\EncoderOptions;
-use Neomerx\JsonApi\Http\Headers\MediaType;
-use Neomerx\JsonApi\Http\Headers\SupportedExtensions;
-use Neomerx\JsonApi\Decoders\ArrayDecoder;
-
-class JsonApiServices implements \Pimple\ServiceProviderInterface
-{
-    /**
-     * @SuppressWarnings(PHPMD.ShortVariable)
-     */
-    public function register(\Pimple\Container $container)
-    {
-        // register factory
-        $container[FactoryInterface::class] = function ($c) {
-            $factory = new Factory();
-
-            $factory->setDependencyInjectionContainer(new \Pimple\Psr11\Container($c));
-
-            if ($c->has('logger')) {
-                $factory->setLogger($c['logger']);
-            }
-
-            return $factory;
-        };
-
-        // register schemas
-        $container[ContainerInterface::class] = function ($c) {
-            $schemas = isset($c[C::SCHEMAS]) ? $c[C::SCHEMAS] : [];
-            $schemaContainer = $c[FactoryInterface::class]->createContainer($schemas);
-
-            $pluginSchemas = \PluginEngine::sendMessage(JsonApiPlugin::class, 'registerSchemas', $schemaContainer);
-            if (is_array($pluginSchemas) && count($pluginSchemas)) {
-                foreach ($pluginSchemas as $arrayOfSchemas) {
-                    $schemaContainer->registerArray($arrayOfSchemas);
-                }
-            }
-
-            return $schemaContainer;
-        };
-
-        // register codec matcher
-        $container[CodecMatcherInterface::class] = function ($c) {
-            return $this->createCodecMatcher($c);
-        };
-
-        // TODO wo wird das gebraucht
-        $container[HeadersCheckerInterface::class] = function ($c) {
-            return $c[FactoryInterface::class]->createHeadersChecker($c[CodecMatcherInterface::class]);
-        };
-
-        // register query params
-        $container[EncodingParametersInterface::class] = function ($c) {
-            return $c[FactoryInterface::class]->createQueryParametersParser()->parse($c['request']);
-        };
-
-        $container[HeaderParametersInterface::class] = function ($c) {
-            return $c[FactoryInterface::class]->createHeaderParametersParser()->parse($c['request']);
-        };
-
-        // register responses
-        $container[ResponsesInterface::class] = function ($c) {
-            return $this->createResponses($c);
-        };
-    }
-
-    /**
-     * @param ContainerInterface $container
-     *
-     * @return ResponsesInterface
-     */
-    protected function createResponses($container)
-    {
-        $codecMatcher = $container[CodecMatcherInterface::class];
-        $parameters = $container[EncodingParametersInterface::class];
-        $params = $container[HeaderParametersInterface::class];
-
-        $codecMatcher->matchEncoder($params->getAcceptHeader());
-        $encoder = $codecMatcher->getEncoder();
-
-        $schemaContainer = $container[ContainerInterface::class];
-        $urlPrefix = $container[C::JSON_URL_PREFIX];
-
-        $responses = new Responses(
-            new MediaType(MediaTypeInterface::JSON_API_TYPE, MediaTypeInterface::JSON_API_SUB_TYPE),
-            new SupportedExtensions(),
-            $encoder,
-            $schemaContainer,
-            $parameters,
-            $urlPrefix
-        );
-
-        return $responses;
-    }
-
-    /**
-     * @param ContainerInterface $schemaContainer
-     *
-     * @return CodecMatcherInterface
-     */
-    protected function createCodecMatcher($container)
-    {
-        $factory = $container[FactoryInterface::class];
-        $schemaContainer = $container[ContainerInterface::class];
-
-        $encoderOptions = new EncoderOptions(0, $container[C::JSON_URL_PREFIX]);
-
-        $decoderClosure = $this->getDecoderClosure();
-        $encoderClosure = $this->getEncoderClosure($factory, $schemaContainer, $encoderOptions);
-        $codecMatcher = $factory->createCodecMatcher();
-        $jsonApiType = $factory->createMediaType(
-            MediaTypeInterface::JSON_API_TYPE,
-            MediaTypeInterface::JSON_API_SUB_TYPE
-        );
-        $jsonApiTypeUtf8 = $factory->createMediaType(
-            MediaTypeInterface::JSON_API_TYPE,
-            MediaTypeInterface::JSON_API_SUB_TYPE,
-            ['charset' => 'UTF-8']
-        );
-        $codecMatcher->registerEncoder($jsonApiType, $encoderClosure);
-        $codecMatcher->registerDecoder($jsonApiType, $decoderClosure);
-        $codecMatcher->registerEncoder($jsonApiTypeUtf8, $encoderClosure);
-        $codecMatcher->registerDecoder($jsonApiTypeUtf8, $decoderClosure);
-
-        return $codecMatcher;
-    }
-
-    /**
-     * @return Closure
-     */
-    protected function getDecoderClosure()
-    {
-        return function () {
-            return new ArrayDecoder();
-        };
-    }
-
-    /**
-     * @param FactoryInterface   $factory
-     * @param ContainerInterface $container
-     * @param EncoderOptions     $encoderOptions
-     *
-     * @return Closure
-     */
-    private function getEncoderClosure(
-        FactoryInterface $factory,
-        ContainerInterface $container,
-        EncoderOptions $encoderOptions
-    ) {
-        return function () use ($factory, $container, $encoderOptions) {
-            return $factory->createEncoder($container, $encoderOptions);
-        };
-    }
-}
diff --git a/lib/classes/JsonApi/Providers/StudipConfig.php b/lib/classes/JsonApi/Providers/StudipConfig.php
deleted file mode 100644
index 99b3f7ece77a4d8a13c101fc1957b1932089fdf8..0000000000000000000000000000000000000000
--- a/lib/classes/JsonApi/Providers/StudipConfig.php
+++ /dev/null
@@ -1,36 +0,0 @@
-<?php
-
-namespace JsonApi\Providers;
-
-/**
- * Diese Klasse konfiguriert die JSON-API in der Slim-Applikation um
- * die Zuordnung von Schemata zu Stud.IP-Model-Klassen und setzt das
- * URL-Prefix für die interne Generierung von URIs.
- */
-class StudipConfig implements \Pimple\ServiceProviderInterface
-{
-    /**
-     * Diese Methode wird automatisch aufgerufen, wenn diese Klasse dem
-     * Dependency Container der Slim-Applikation hinzugefügt wird.
-     *
-     * Hier werden die Schema-Abbildungen und das JSON-API-URL-Präfix gesetzt.
-     *
-     * @param \Pimple\Container $container der Dependency Container
-     *
-     * @SuppressWarnings(PHPMD.Superglobals)
-     */
-    public function register(\Pimple\Container $container)
-    {
-        $container['studip-system-object'] = function () {
-            return new \JsonApi\Models\Studip($container);
-        };
-
-        $container['studip-current-user'] = function () {
-            if ($user = $GLOBALS['user']) {
-                return $user->getAuthenticatedUser();
-            }
-
-            return null;
-        };
-    }
-}
diff --git a/lib/classes/JsonApi/Providers/StudipServices.php b/lib/classes/JsonApi/Providers/StudipServices.php
deleted file mode 100644
index 57def54b17bb02900263481b98e96247d19a7dad..0000000000000000000000000000000000000000
--- a/lib/classes/JsonApi/Providers/StudipServices.php
+++ /dev/null
@@ -1,41 +0,0 @@
-<?php
-
-namespace JsonApi\Providers;
-
-use StudipAuthAbstract;
-use User;
-
-/**
- * Diese Klasse stellt Stud.IP-Spezifika zum Beispiel für die
- * Authentifizierung zur Verfügung.
- */
-class StudipServices implements \Pimple\ServiceProviderInterface
-{
-    /**
-     * Schlüssel für den Stud.IP-Authentifizierungservice.
-     */
-    const AUTHENTICATOR = 'studip-authenticator';
-
-    /**
-     * Diese Methode wird automatisch aufgerufen, wenn diese Klasse dem
-     * Dependency Container der Slim-Applikation hinzugefügt wird.
-     *
-     * Hier werden die Stud.IP-Spezifika gesetzt
-     *
-     * @param \Pimple\Container $container der Dependency Container
-     */
-    public function register(\Pimple\Container $container)
-    {
-        $container[self::AUTHENTICATOR] = function ($c) {
-            return function ($username, $password) {
-                $check = StudipAuthAbstract::CheckAuthentication($username, $password);
-
-                if ($check['uid'] && $check['uid'] != 'nobody') {
-                    return User::find($check['uid']);
-                }
-
-                return null;
-            };
-        };
-    }
-}
diff --git a/lib/classes/JsonApi/RouteMap.php b/lib/classes/JsonApi/RouteMap.php
index b91c6983bb3aa6d2bed0e5df35baf3a9fa7d3257..a258b10457d3377a891dbbbdad3f77179ab555af 100644
--- a/lib/classes/JsonApi/RouteMap.php
+++ b/lib/classes/JsonApi/RouteMap.php
@@ -7,7 +7,7 @@ use JsonApi\Middlewares\Authentication;
 use JsonApi\Middlewares\DangerousRouteHandler;
 use JsonApi\Middlewares\JsonApi as JsonApiMiddleware;
 use JsonApi\Middlewares\StudipMockNavigation;
-use JsonApi\Providers\StudipServices;
+use Slim\Routing\RouteCollectorProxy;
 
 /**
  * Diese Klasse ist die JSON-API-Routemap, in der alle Routen
@@ -48,7 +48,6 @@ use JsonApi\Providers\StudipServices;
  *
  *   $this->app->post('/article/{id}/comments', MeineRoute::class);
  *
- *
  * @see \JsonApi\Middlewares\JsonApi
  * @see \JsonApi\Middlewares\Authentication
  * @see \JsonApi\Contracts\JsonApiPlugin
@@ -58,6 +57,9 @@ use JsonApi\Providers\StudipServices;
  */
 class RouteMap
 {
+    /** @var \Slim\App */
+    private $app;
+
     /**
      * Der Konstruktor.
      *
@@ -76,40 +78,12 @@ class RouteMap
      * RouteMap::authenticatedRoutes eingetragen. Routen ohne
      * Autorisierung werden in RouteMap::unauthenticatedRoutes vermerkt.
      */
-    public function __invoke()
+    public function __invoke(RouteCollectorProxy $group): void
     {
-        $corsOrigin = \Config::get()->getValue('JSONAPI_CORS_ORIGIN');
-        if (is_array($corsOrigin) && count($corsOrigin)) {
-            $this->app->add(
-                new \Tuupola\Middleware\Cors(
-                    [
-                        'origin' => $corsOrigin,
-                        'methods' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
-                        'headers.allow' => [
-                            'Accept',
-                            'Accept-Encoding',
-                            'Accept-Language',
-                            'Authorization',
-                            'Content-Type',
-                            'Origin',
-                        ],
-                        'headers.expose' => ['Etag'],
-                        'credentials' => true,
-                        'cache' => 86400,
-                    ]
-                )
-            );
-        }
-
-        $this->app->add(new JsonApiMiddleware($this->app));
-
-        $this->app->add(new StudipMockNavigation());
-
-        $this->app->group('', [$this, 'authenticatedRoutes'])
-            ->add(new Authentication($this->getAuthenticator()));
-        $this->app->group('', [$this, 'unauthenticatedRoutes']);
-
-        $this->app->get('/discovery', Routes\DiscoveryIndex::class);
+        $group->group('', [$this, 'authenticatedRoutes'])->add(new Authentication($this->getAuthenticator()));
+        $group->group('', [$this, 'unauthenticatedRoutes']);
+
+        $group->get('/discovery', Routes\DiscoveryIndex::class);
     }
 
     /**
@@ -117,44 +91,43 @@ class RouteMap
      * Außerdem wird über die \PluginEngine allen JsonApiPlugins die
      * Möglichkeit gegeben, sich hier einzutragen.
      */
-    public function authenticatedRoutes()
+    public function authenticatedRoutes(RouteCollectorProxy $group): void
     {
         \PluginEngine::sendMessage(JsonApiPlugin::class, 'registerAuthenticatedRoutes', $this->app);
 
-        $this->app->get('/users', Routes\Users\UsersIndex::class);
-        $this->app->get('/users/me', Routes\Users\UsersShow::class);
-        $this->app->get('/users/{id}', Routes\Users\UsersShow::class);
-        $this->app->delete('/users/{id}', Routes\Users\UsersDelete::class)->add(DangerousRouteHandler::class);
-
-        $this->app->get('/users/{id}/activitystream', Routes\ActivityStreamShow::class);
-        $this->app->get('/users/{id}/institute-memberships', Routes\InstituteMemberships\ByUserIndex::class);
-        $this->app->get('/users/{id}/course-memberships', Routes\CourseMemberships\ByUserIndex::class);
-        $this->app->get('/course-memberships/{id}', Routes\CourseMemberships\CourseMembershipsShow::class);
-        $this->app->patch('/course-memberships/{id}', Routes\CourseMemberships\CourseMembershipsUpdate::class);
-
-        $this->app->get('/users/{id}/schedule', Routes\Schedule\UserScheduleShow::class)->setName('get-schedule');
-        $this->app->get('/schedule-entries/{id}', Routes\Schedule\ScheduleEntriesShow::class);
-        $this->app->get('/seminar-cycle-dates/{id}', Routes\Schedule\SeminarCycleDatesShow::class);
-
-        $this->app->get('/users/{id}/config-values', Routes\ConfigValues\ByUserIndex::class);
-        $this->app->get('/config-values/{id}', Routes\ConfigValues\ConfigValuesShow::class);
-        $this->app->patch('/config-values/{id}', Routes\ConfigValues\ConfigValuesUpdate::class);
-
-
-        $this->addAuthenticatedBlubberRoutes();
-//        $this->addAuthenticatedConsultationRoutes();
-        $this->addAuthenticatedContactsRoutes();
-        $this->addAuthenticatedCoursesRoutes();
-        $this->addAuthenticatedCoursewareRoutes();
-        $this->addAuthenticatedEventsRoutes();
-        $this->addAuthenticatedFeedbackRoutes();
-        $this->addAuthenticatedFilesRoutes();
-        $this->addAuthenticatedForumRoutes();
-        $this->addAuthenticatedInstitutesRoutes();
-        $this->addAuthenticatedMessagesRoutes();
-        $this->addAuthenticatedNewsRoutes();
-        $this->addAuthenticatedStudyAreasRoutes();
-        $this->addAuthenticatedWikiRoutes();
+        $group->get('/users', Routes\Users\UsersIndex::class);
+        $group->get('/users/me', Routes\Users\UsersShow::class)->setName('get-myself');
+        $group->get('/users/{id}', Routes\Users\UsersShow::class);
+        $group->delete('/users/{id}', Routes\Users\UsersDelete::class)->add(DangerousRouteHandler::class);
+
+        $group->get('/users/{id}/activitystream', Routes\ActivityStreamShow::class);
+        $group->get('/users/{id}/institute-memberships', Routes\InstituteMemberships\ByUserIndex::class);
+        $group->get('/users/{id}/course-memberships', Routes\CourseMemberships\ByUserIndex::class);
+        $group->get('/course-memberships/{id}', Routes\CourseMemberships\CourseMembershipsShow::class);
+        $group->patch('/course-memberships/{id}', Routes\CourseMemberships\CourseMembershipsUpdate::class);
+
+        $group->get('/users/{id}/schedule', Routes\Schedule\UserScheduleShow::class)->setName('get-schedule');
+        $group->get('/schedule-entries/{id}', Routes\Schedule\ScheduleEntriesShow::class);
+        $group->get('/seminar-cycle-dates/{id}', Routes\Schedule\SeminarCycleDatesShow::class);
+
+        $group->get('/users/{id}/config-values', Routes\ConfigValues\ByUserIndex::class);
+        $group->get('/config-values/{id}', Routes\ConfigValues\ConfigValuesShow::class);
+        $group->patch('/config-values/{id}', Routes\ConfigValues\ConfigValuesUpdate::class);
+
+        $this->addAuthenticatedBlubberRoutes($group);
+        //        $this->addAuthenticatedConsultationRoutes($group);
+        $this->addAuthenticatedContactsRoutes($group);
+        $this->addAuthenticatedCoursesRoutes($group);
+        $this->addAuthenticatedCoursewareRoutes($group);
+        $this->addAuthenticatedEventsRoutes($group);
+        $this->addAuthenticatedFeedbackRoutes($group);
+        $this->addAuthenticatedFilesRoutes($group);
+        $this->addAuthenticatedForumRoutes($group);
+        $this->addAuthenticatedInstitutesRoutes($group);
+        $this->addAuthenticatedMessagesRoutes($group);
+        $this->addAuthenticatedNewsRoutes($group);
+        $this->addAuthenticatedStudyAreasRoutes($group);
+        $this->addAuthenticatedWikiRoutes($group);
     }
 
     /**
@@ -162,341 +135,357 @@ class RouteMap
      * Außerdem wird über die \PluginEngine allen JsonApiPlugins die
      * Möglichkeit gegeben, sich hier einzutragen.
      */
-    public function unauthenticatedRoutes()
+    public function unauthenticatedRoutes(RouteCollectorProxy $group): void
     {
-        \PluginEngine::sendMessage(JsonApiPlugin::class, 'registerUnauthenticatedRoutes', $this->app);
+        \PluginEngine::sendMessage(JsonApiPlugin::class, 'registerUnauthenticatedRoutes', $group);
 
-        $this->app->get('/semesters', Routes\SemestersIndex::class);
-        $this->app->get('/semesters/{id}', Routes\SemestersShow::class);
+        $group->get('/semesters', Routes\SemestersIndex::class);
+        $group->get('/semesters/{id}', Routes\SemestersShow::class)->setName('get-semester');
 
-        $this->app->get('/studip/properties', Routes\Studip\PropertiesIndex::class);
+        $group->get('/studip/properties', Routes\Studip\PropertiesIndex::class);
     }
 
-    private function getAuthenticator()
+    private function getAuthenticator(): callable
     {
-        $container = $this->app->getContainer();
-
-        return $container[StudipServices::AUTHENTICATOR];
+        return $this->app->getContainer()->get('studip-authenticator');
     }
 
-    private function addAuthenticatedBlubberRoutes()
+    private function addAuthenticatedBlubberRoutes(RouteCollectorProxy $group): void
     {
         // find BlubberThreads
-        $this->app->get('/courses/{id}/blubber-threads', Routes\Blubber\ThreadsIndex::class)
-                  ->setArgument('type', 'course');
-        $this->app->get('/institutes/{id}/blubber-threads', Routes\Blubber\ThreadsIndex::class)
-                  ->setArgument('type', 'institute');
-        $this->app->get('/studip/blubber-threads', Routes\Blubber\ThreadsIndex::class)
-                  ->setArgument('type', 'public');
-        $this->app->get('/users/{id}/blubber-threads', Routes\Blubber\ThreadsIndex::class)
-                  ->setArgument('type', 'private');
-        $this->app->get('/blubber-threads', Routes\Blubber\ThreadsIndex::class)
-                  ->setArgument('type', 'all');
-        $this->app->get('/blubber-threads/{id}', Routes\Blubber\ThreadsShow::class);
+        $group->get('/courses/{id}/blubber-threads', Routes\Blubber\ThreadsIndex::class)->setArgument('type', 'course');
+        $group
+            ->get('/institutes/{id}/blubber-threads', Routes\Blubber\ThreadsIndex::class)
+            ->setArgument('type', 'institute');
+        $group->get('/studip/blubber-threads', Routes\Blubber\ThreadsIndex::class)->setArgument('type', 'public');
+        $group->get('/users/{id}/blubber-threads', Routes\Blubber\ThreadsIndex::class)->setArgument('type', 'private');
+        $group->get('/blubber-threads', Routes\Blubber\ThreadsIndex::class)->setArgument('type', 'all');
+        $group->get('/blubber-threads/{id}', Routes\Blubber\ThreadsShow::class);
 
         // create, read, update and delete BlubberComments
-        $this->app->get('/blubber-threads/{id}/comments', Routes\Blubber\CommentsByThreadIndex::class);
-        $this->app->post('/blubber-threads/{id}/comments', Routes\Blubber\CommentsCreate::class);
-        $this->app->get('/blubber-comments', Routes\Blubber\CommentsIndex::class);
-        $this->app->get('/blubber-comments/{id}', Routes\Blubber\CommentsShow::class);
-        $this->app->patch('/blubber-comments/{id}', Routes\Blubber\CommentsUpdate::class);
-        $this->app->delete('/blubber-comments/{id}', Routes\Blubber\CommentsDelete::class);
+        $group->get('/blubber-threads/{id}/comments', Routes\Blubber\CommentsByThreadIndex::class);
+        $group->post('/blubber-threads/{id}/comments', Routes\Blubber\CommentsCreate::class);
+        $group->get('/blubber-comments', Routes\Blubber\CommentsIndex::class);
+        $group->get('/blubber-comments/{id}', Routes\Blubber\CommentsShow::class);
+        $group->patch('/blubber-comments/{id}', Routes\Blubber\CommentsUpdate::class);
+        $group->delete('/blubber-comments/{id}', Routes\Blubber\CommentsDelete::class);
 
         // REL mentions
-        $this->addRelationship('/blubber-threads/{id}/relationships/mentions', Routes\Blubber\Rel\Mentions::class);
+        $this->addRelationship(
+            $group,
+            '/blubber-threads/{id}/relationships/mentions',
+            Routes\Blubber\Rel\Mentions::class
+        );
     }
 
-    private function addAuthenticatedConsultationRoutes()
+    private function addAuthenticatedConsultationRoutes(RouteCollectorProxy $group): void
     {
-        $this->app->get('/users/{id}/consultations', Routes\Consultations\BlocksByUserIndex::class);
-
-        $this->app->get('/consultation-blocks/{id}', Routes\Consultations\BlockShow::class);
-        $this->app->get('/consultation-blocks/{id}/slots', Routes\Consultations\SlotsByBlockIndex::class);
+        $group->get('/users/{id}/consultations', Routes\Consultations\BlocksByUserIndex::class);
 
-        $this->app->get('/consultation-slots/{id}', Routes\Consultations\SlotShow::class);
-        $this->app->get('/consultation-slots/{id}/bookings', Routes\Consultations\BookingsBySlotIndex::class);
-        $this->app->post('/consultation-slots/{id}/bookings', Routes\Consultations\BookingsCreate::class);
+        $group->get('/consultation-blocks/{id}', Routes\Consultations\BlockShow::class);
+        $group->get('/consultation-blocks/{id}/slots', Routes\Consultations\SlotsByBlockIndex::class);
 
-        $this->app->get('/consultation-bookings/{id}', Routes\Consultations\BookingsShow::class);
-        $this->app->delete('/consultation-bookings/{id}', Routes\Consultations\BookingsDelete::class);
+        $group->get('/consultation-slots/{id}', Routes\Consultations\SlotShow::class);
+        $group->get('/consultation-slots/{id}/bookings', Routes\Consultations\BookingsBySlotIndex::class);
+        $group->post('/consultation-slots/{id}/bookings', Routes\Consultations\BookingsCreate::class);
 
-//        $this->addRelationship('/users/{id}/relationships/contacts', Routes\Users\Rel\Contacts::class);
+        $group->get('/consultation-bookings/{id}', Routes\Consultations\BookingsShow::class);
+        $group->delete('/consultation-bookings/{id}', Routes\Consultations\BookingsDelete::class);
     }
 
-    private function addAuthenticatedContactsRoutes()
+    private function addAuthenticatedContactsRoutes(RouteCollectorProxy $group): void
     {
-        $this->app->get('/users/{id}/contacts', Routes\Users\ContactsIndex::class);
-        $this->addRelationship('/users/{id}/relationships/contacts', Routes\Users\Rel\Contacts::class);
+        $group->get('/users/{id}/contacts', Routes\Users\ContactsIndex::class);
+        $this->addRelationship($group, '/users/{id}/relationships/contacts', Routes\Users\Rel\Contacts::class);
     }
 
-    private function addAuthenticatedEventsRoutes()
+    private function addAuthenticatedEventsRoutes(RouteCollectorProxy $group): void
     {
-        $this->app->get('/courses/{id}/events', Routes\Events\CourseEventsIndex::class);
-        $this->app->get('/users/{id}/events', Routes\Events\UserEventsIndex::class);
+        $group->get('/courses/{id}/events', Routes\Events\CourseEventsIndex::class);
+        $group->get('/users/{id}/events', Routes\Events\UserEventsIndex::class);
 
         // not a JSON:API route
-        $this->app->get('/users/{id}/events.ics', Routes\Events\UserEventsIcal::class);
+        $group->get('/users/{id}/events.ics', Routes\Events\UserEventsIcal::class);
     }
 
-    private function addAuthenticatedFeedbackRoutes()
+    private function addAuthenticatedFeedbackRoutes(RouteCollectorProxy $group): void
     {
-        $this->app->get('/feedback-elements/{id}', Routes\Feedback\FeedbackElementsShow::class);
-        $this->app->get('/feedback-elements/{id}/entries', Routes\Feedback\FeedbackEntriesIndex::class);
-        $this->app->get('/courses/{id}/feedback-elements', Routes\Feedback\FeedbackElementsByCourseIndex::class);
-        $this->app->get('/file-refs/{id}/feedback-elements', Routes\Feedback\FeedbackElementsByFileRefIndex::class);
-        $this->app->get('/folders/{id}/feedback-elements', Routes\Feedback\FeedbackElementsByFolderIndex::class);
+        $group->get('/feedback-elements/{id}', Routes\Feedback\FeedbackElementsShow::class);
+        $group->get('/feedback-elements/{id}/entries', Routes\Feedback\FeedbackEntriesIndex::class);
+        $group->get('/courses/{id}/feedback-elements', Routes\Feedback\FeedbackElementsByCourseIndex::class);
+        $group->get('/file-refs/{id}/feedback-elements', Routes\Feedback\FeedbackElementsByFileRefIndex::class);
+        $group->get('/folders/{id}/feedback-elements', Routes\Feedback\FeedbackElementsByFolderIndex::class);
 
-        $this->app->get('/feedback-entries/{id}', Routes\Feedback\FeedbackEntriesShow::class);
+        $group->get('/feedback-entries/{id}', Routes\Feedback\FeedbackEntriesShow::class);
     }
 
-    private function addAuthenticatedInstitutesRoutes()
+    private function addAuthenticatedInstitutesRoutes(RouteCollectorProxy $group): void
     {
-        $this->app->get('/institute-memberships/{id}', Routes\InstituteMemberships\InstituteMembershipsShow::class);
-        $this->app->get('/institutes/{id}', Routes\Institutes\InstitutesShow::class);
-        $this->app->get('/institutes', Routes\Institutes\InstitutesIndex::class);
+        $group->get('/institute-memberships/{id}', Routes\InstituteMemberships\InstituteMembershipsShow::class);
+        $group->get('/institutes/{id}', Routes\Institutes\InstitutesShow::class);
+        $group->get('/institutes', Routes\Institutes\InstitutesIndex::class);
 
-        $this->app->get('/institutes/{id}/status-groups', Routes\Institutes\StatusGroupsOfInstitutes::class);
+        $group->get('/institutes/{id}/status-groups', Routes\Institutes\StatusGroupsOfInstitutes::class);
     }
 
-    private function addAuthenticatedNewsRoutes()
+    private function addAuthenticatedNewsRoutes(RouteCollectorProxy $group): void
     {
-        $this->app->post('/courses/{id}/news', Routes\News\CourseNewsCreate::class);
-        $this->app->post('/users/{id}/news', Routes\News\UserNewsCreate::class);
-        $this->app->post('/news', Routes\News\StudipNewsCreate::class);
-        $this->app->post('/news/{id}/comments', Routes\News\CommentCreate::class);
-        $this->app->patch('/news/{id}', Routes\News\NewsUpdate::class);
-        $this->app->get('/news/{id}', Routes\News\NewsShow::class);
-        $this->app->get('/courses/{id}/news', Routes\News\ByCourseIndex::class);
-        $this->app->get('/users/{id}/news', Routes\News\ByUserIndex::class);
-        $this->app->get('/news/{id}/comments', Routes\News\CommentsIndex::class);
-        $this->app->get('/news', Routes\News\ByCurrentUser::class);
-        $this->app->get('/studip/news', Routes\News\GlobalNewsShow::class);
-        $this->app->delete('/news/{id}', Routes\News\NewsDelete::class);
-        $this->app->delete('/comments/{id}', Routes\News\CommentsDelete::class);
+        $group->post('/courses/{id}/news', Routes\News\CourseNewsCreate::class);
+        $group->post('/users/{id}/news', Routes\News\UserNewsCreate::class);
+        $group->post('/news', Routes\News\StudipNewsCreate::class);
+        $group->post('/news/{id}/comments', Routes\News\CommentCreate::class);
+        $group->patch('/news/{id}', Routes\News\NewsUpdate::class);
+        $group->get('/news/{id}', Routes\News\NewsShow::class);
+        $group->get('/courses/{id}/news', Routes\News\ByCourseIndex::class);
+        $group->get('/users/{id}/news', Routes\News\ByUserIndex::class);
+        $group->get('/news/{id}/comments', Routes\News\CommentsIndex::class);
+        $group->get('/news', Routes\News\ByCurrentUser::class);
+        $group->get('/studip/news', Routes\News\GlobalNewsShow::class);
+        $group->delete('/news/{id}', Routes\News\NewsDelete::class);
+        $group->delete('/comments/{id}', Routes\News\CommentsDelete::class);
 
         // RELATIONSHIP: 'ranges'
-        $this->addRelationship('/news/{id}/relationships/ranges', Routes\News\Rel\Ranges::class);
+        $this->addRelationship($group, '/news/{id}/relationships/ranges', Routes\News\Rel\Ranges::class);
     }
 
-    private function addAuthenticatedStudyAreasRoutes()
+    private function addAuthenticatedStudyAreasRoutes(RouteCollectorProxy $group): void
     {
-        $this->app->get('/study-areas', Routes\StudyAreas\StudyAreasIndex::class);
-        $this->app->get('/study-areas/{id}', Routes\StudyAreas\StudyAreasShow::class);
+        $group->get('/study-areas', Routes\StudyAreas\StudyAreasIndex::class);
+        $group->get('/study-areas/{id}', Routes\StudyAreas\StudyAreasShow::class);
 
-        $this->app->get('/study-areas/{id}/children', Routes\StudyAreas\ChildrenOfStudyAreas::class);
-        $this->app->get('/study-areas/{id}/courses', Routes\StudyAreas\CoursesOfStudyAreas::class);
-        $this->app->get('/study-areas/{id}/institute', Routes\StudyAreas\InstituteOfStudyAreas::class);
-        $this->app->get('/study-areas/{id}/parent', Routes\StudyAreas\ParentOfStudyAreas::class);
+        $group->get('/study-areas/{id}/children', Routes\StudyAreas\ChildrenOfStudyAreas::class);
+        $group->get('/study-areas/{id}/courses', Routes\StudyAreas\CoursesOfStudyAreas::class);
+        $group->get('/study-areas/{id}/institute', Routes\StudyAreas\InstituteOfStudyAreas::class);
+        $group->get('/study-areas/{id}/parent', Routes\StudyAreas\ParentOfStudyAreas::class);
     }
 
-    private function addAuthenticatedWikiRoutes()
+    private function addAuthenticatedWikiRoutes(RouteCollectorProxy $group): void
     {
-        $this->addRelationship('/wiki-pages/{id:.+}/relationships/parent', Routes\Wiki\Rel\ParentPage::class);
-        $this->app->get('/wiki-pages/{id:.+}/children', Routes\Wiki\ChildrenIndex::class);
-        $this->app->get('/wiki-pages/{id:.+}/descendants', Routes\Wiki\DescendantsIndex::class);
+        $this->addRelationship($group, '/wiki-pages/{id:.+}/relationships/parent', Routes\Wiki\Rel\ParentPage::class);
+        $group->get('/wiki-pages/{id:.+}/children', Routes\Wiki\ChildrenIndex::class);
+        $group->get('/wiki-pages/{id:.+}/descendants', Routes\Wiki\DescendantsIndex::class);
 
-        $this->app->get('/courses/{id}/wiki-pages', Routes\Wiki\WikiIndex::class);
-        $this->app->get('/wiki-pages/{id:.+}', Routes\Wiki\WikiShow::class)->setName('get-wiki-page');
+        $group->get('/courses/{id}/wiki-pages', Routes\Wiki\WikiIndex::class);
+        $group->get('/wiki-pages/{id:.+}', Routes\Wiki\WikiShow::class)->setName('get-wiki-page');
 
-        $this->app->post('/courses/{id}/wiki-pages', Routes\Wiki\WikiCreate::class);
-        $this->app->patch('/wiki-pages/{id:.+}', Routes\Wiki\WikiUpdate::class);
-        $this->app->delete('/wiki-pages/{id:.+}', Routes\Wiki\WikiDelete::class);
+        $group->post('/courses/{id}/wiki-pages', Routes\Wiki\WikiCreate::class);
+        $group->patch('/wiki-pages/{id:.+}', Routes\Wiki\WikiUpdate::class);
+        $group->delete('/wiki-pages/{id:.+}', Routes\Wiki\WikiDelete::class);
     }
 
-    private function addAuthenticatedCoursesRoutes()
+    private function addAuthenticatedCoursesRoutes(RouteCollectorProxy $group): void
     {
-        $this->app->get('/courses', Routes\Courses\CoursesIndex::class);
-        $this->app->get('/courses/{id}', Routes\Courses\CoursesShow::class);
+        $group->get('/courses', Routes\Courses\CoursesIndex::class);
+        $group->get('/courses/{id}', Routes\Courses\CoursesShow::class);
 
-        $this->app->get('/users/{id}/courses', Routes\Courses\CoursesByUserIndex::class);
+        $group->get('/users/{id}/courses', Routes\Courses\CoursesByUserIndex::class);
 
-        $this->app->get('/courses/{id}/memberships', Routes\Courses\CoursesMembershipsIndex::class);
-        $this->addRelationship('/courses/{id}/relationships/memberships', Routes\Courses\Rel\Memberships::class);
+        $group->get('/courses/{id}/memberships', Routes\Courses\CoursesMembershipsIndex::class);
+        $this->addRelationship(
+            $group,
+            '/courses/{id}/relationships/memberships',
+            Routes\Courses\Rel\Memberships::class
+        );
 
-        $this->app->get('/courses/{id}/status-groups', Routes\Courses\StatusGroupsOfCourses::class);
+        $group->get('/courses/{id}/status-groups', Routes\Courses\StatusGroupsOfCourses::class);
 
-        $this->app->get('/sem-classes', Routes\Courses\SemClassesIndex::class);
-        $this->app->get('/sem-classes/{id}', Routes\Courses\SemClassesShow::class);
-        $this->app->get('/sem-classes/{id}/sem-types', Routes\Courses\SemTypesBySemClassIndex::class);
-        $this->app->get('/sem-types', Routes\Courses\SemTypesIndex::class);
-        $this->app->get('/sem-types/{id}', Routes\Courses\SemTypesShow::class);
+        $group->get('/sem-classes', Routes\Courses\SemClassesIndex::class);
+        $group->get('/sem-classes/{id}', Routes\Courses\SemClassesShow::class);
+        $group->get('/sem-classes/{id}/sem-types', Routes\Courses\SemTypesBySemClassIndex::class);
+        $group->get('/sem-types', Routes\Courses\SemTypesIndex::class);
+        $group->get('/sem-types/{id}', Routes\Courses\SemTypesShow::class);
     }
 
-    private function addAuthenticatedCoursewareRoutes()
+    private function addAuthenticatedCoursewareRoutes(RouteCollectorProxy $group): void
     {
-        $this->app->get('/{type:courses|users}/{id}/courseware', Routes\Courseware\CoursewareInstancesShow::class);
-        $this->app->patch('/courseware-instances/{id}', Routes\Courseware\CoursewareInstancesUpdate::class);
+        $group->get('/{type:courses|users}/{id}/courseware', Routes\Courseware\CoursewareInstancesShow::class);
+        $group->patch('/courseware-instances/{id}', Routes\Courseware\CoursewareInstancesUpdate::class);
         $this->addRelationship(
+            $group,
             '/courseware-instances/{id}/relationships/bookmarks',
             Routes\Courseware\Rel\BookmarkedStructuralElements::class
         );
-        $this->app->get(
-            '/courseware-instances/{id}/bookmarks',
-            Routes\Courseware\BookmarkedStructuralElementsIndex::class
-        );
+        $group->get('/courseware-instances/{id}/bookmarks', Routes\Courseware\BookmarkedStructuralElementsIndex::class);
 
-        $this->app->get('/courseware-blocks/{id}', Routes\Courseware\BlocksShow::class);
-        $this->app->post('/courseware-blocks', Routes\Courseware\BlocksCreate::class);
-        $this->app->patch('/courseware-blocks/{id}', Routes\Courseware\BlocksUpdate::class);
-        $this->app->delete('/courseware-blocks/{id}', Routes\Courseware\BlocksDelete::class);
+        $group->get('/courseware-blocks/{id}', Routes\Courseware\BlocksShow::class);
+        $group->post('/courseware-blocks', Routes\Courseware\BlocksCreate::class);
+        $group->patch('/courseware-blocks/{id}', Routes\Courseware\BlocksUpdate::class);
+        $group->delete('/courseware-blocks/{id}', Routes\Courseware\BlocksDelete::class);
 
         $this->addRelationship(
+            $group,
             '/courseware-blocks/{id}/relationships/edit-blocker',
             Routes\Courseware\Rel\BlocksEditBlocker::class
         );
 
         $this->addRelationship(
+            $group,
             '/courseware-blocks/{id}/relationships/file-refs',
             Routes\Courseware\Rel\BlocksFilerefs::class
         );
-        $this->app->get('/courseware-blocks/{id}/file-refs', Routes\Courseware\BlocksListFiles::class);
+        $group->get('/courseware-blocks/{id}/file-refs', Routes\Courseware\BlocksListFiles::class);
 
         // not a JSON route
-        $this->app->post('/courseware-blocks/{id}/copy', Routes\Courseware\BlocksCopy::class);
+        $group->post('/courseware-blocks/{id}/copy', Routes\Courseware\BlocksCopy::class);
 
-        $this->app->get('/courseware-containers/{id}', Routes\Courseware\ContainersShow::class);
-        $this->app->post('/courseware-containers', Routes\Courseware\ContainersCreate::class);
-        $this->app->patch('/courseware-containers/{id}', Routes\Courseware\ContainersUpdate::class);
-        $this->app->delete('/courseware-containers/{id}', Routes\Courseware\ContainersDelete::class);
-        $this->app->get('/courseware-containers/{id}/blocks', Routes\Courseware\BlocksIndex::class);
+        $group->get('/courseware-containers/{id}', Routes\Courseware\ContainersShow::class);
+        $group->post('/courseware-containers', Routes\Courseware\ContainersCreate::class);
+        $group->patch('/courseware-containers/{id}', Routes\Courseware\ContainersUpdate::class);
+        $group->delete('/courseware-containers/{id}', Routes\Courseware\ContainersDelete::class);
+        $group->get('/courseware-containers/{id}/blocks', Routes\Courseware\BlocksIndex::class);
         $this->addRelationship(
+            $group,
             '/courseware-containers/{id}/relationships/blocks',
             Routes\Courseware\Rel\ContainersBlocks::class
         );
         $this->addRelationship(
+            $group,
             '/courseware-containers/{id}/relationships/edit-blocker',
             Routes\Courseware\Rel\ContainersEditBlocker::class
         );
 
         // not a JSON route
-        $this->app->post('/courseware-containers/{id}/copy', Routes\Courseware\ContainersCopy::class);
+        $group->post('/courseware-containers/{id}/copy', Routes\Courseware\ContainersCopy::class);
 
-        $this->app->get('/courseware-structural-elements/{id}', Routes\Courseware\StructuralElementsShow::class);
-        $this->app->get('/courseware-structural-elements', Routes\Courseware\StructuralElementsIndex::class);
-        $this->app->post('/courseware-structural-elements', Routes\Courseware\StructuralElementsCreate::class);
-        $this->app->patch('/courseware-structural-elements/{id}', Routes\Courseware\StructuralElementsUpdate::class);
-        $this->app->delete('/courseware-structural-elements/{id}', Routes\Courseware\StructuralElementsDelete::class);
+        $group->get('/courseware-structural-elements/{id}', Routes\Courseware\StructuralElementsShow::class);
+        $group->get('/courseware-structural-elements', Routes\Courseware\StructuralElementsIndex::class);
+        $group->post('/courseware-structural-elements', Routes\Courseware\StructuralElementsCreate::class);
+        $group->patch('/courseware-structural-elements/{id}', Routes\Courseware\StructuralElementsUpdate::class);
+        $group->delete('/courseware-structural-elements/{id}', Routes\Courseware\StructuralElementsDelete::class);
 
-        $this->app->get(
+        $group->get(
             '/courseware-structural-elements/{id}/children',
             Routes\Courseware\ChildrenOfStructuralElementsIndex::class
         );
-        $this->app->get('/courseware-structural-elements/{id}/containers', Routes\Courseware\ContainersIndex::class);
+        $group->get('/courseware-structural-elements/{id}/containers', Routes\Courseware\ContainersIndex::class);
         $this->addRelationship(
+            $group,
             '/courseware-structural-elements/{id}/relationships/containers',
             Routes\Courseware\Rel\StructuralElementsContainers::class
         );
         $this->addRelationship(
+            $group,
             '/courseware-structural-elements/{id}/relationships/children',
             Routes\Courseware\Rel\StructuralElementsChildren::class
         );
-        $this->app->get(
+        $group->get(
             '/courseware-structural-elements/{id}/descendants',
             Routes\Courseware\DescendantsOfStructuralElementsIndex::class
         );
         $this->addRelationship(
+            $group,
             '/courseware-structural-elements/{id}/relationships/edit-blocker',
             Routes\Courseware\Rel\StructuralElementsEditBlocker::class
         );
 
-        $this->app->post('/courseware-structural-elements/{id}/image', Routes\Courseware\StructuralElementsImageUpload::class);
-        $this->app->delete('/courseware-structural-elements/{id}/image', Routes\Courseware\StructuralElementsImageDelete::class);
+        $group->post(
+            '/courseware-structural-elements/{id}/image',
+            Routes\Courseware\StructuralElementsImageUpload::class
+        );
+        $group->delete(
+            '/courseware-structural-elements/{id}/image',
+            Routes\Courseware\StructuralElementsImageDelete::class
+        );
 
         // not a JSON route
-        $this->app->post('/courseware-structural-elements/{id}/copy', Routes\Courseware\StructuralElementsCopy::class);
+        $group->post('/courseware-structural-elements/{id}/copy', Routes\Courseware\StructuralElementsCopy::class);
 
-        $this->app->get('/courseware-blocks/{id}/user-data-field', Routes\Courseware\UserDataFieldOfBlocksShow::class);
-        $this->app->get('/courseware-user-data-fields/{id}', Routes\Courseware\UserDataFieldsShow::class);
-        $this->app->patch('/courseware-user-data-fields/{id}', Routes\Courseware\UserDataFieldsUpdate::class);
+        $group->get('/courseware-blocks/{id}/user-data-field', Routes\Courseware\UserDataFieldOfBlocksShow::class);
+        $group->get('/courseware-user-data-fields/{id}', Routes\Courseware\UserDataFieldsShow::class);
+        $group->patch('/courseware-user-data-fields/{id}', Routes\Courseware\UserDataFieldsUpdate::class);
 
-        $this->app->get('/courseware-blocks/{id}/user-progress', Routes\Courseware\UserProgressOfBlocksShow::class);
-        $this->app->get('/courseware-user-progresses/{id}', Routes\Courseware\UserProgressesShow::class);
-        $this->app->patch('/courseware-user-progresses/{id}', Routes\Courseware\UserProgressesUpdate::class);
+        $group->get('/courseware-blocks/{id}/user-progress', Routes\Courseware\UserProgressOfBlocksShow::class);
+        $group->get('/courseware-user-progresses/{id}', Routes\Courseware\UserProgressesShow::class);
+        $group->patch('/courseware-user-progresses/{id}', Routes\Courseware\UserProgressesUpdate::class);
 
-        $this->app->get('/courseware-blocks/{id}/comments', Routes\Courseware\BlockCommentsOfBlocksIndex::class);
-        $this->app->post('/courseware-block-comments', Routes\Courseware\BlockCommentsCreate::class);
-        $this->app->get('/courseware-block-comments/{id}', Routes\Courseware\BlockCommentsShow::class);
-        $this->app->patch('/courseware-block-comments/{id}', Routes\Courseware\BlockCommentsUpdate::class);
-        $this->app->delete('/courseware-block-comments/{id}', Routes\Courseware\BlockCommentsDelete::class);
+        $group->get('/courseware-blocks/{id}/comments', Routes\Courseware\BlockCommentsOfBlocksIndex::class);
+        $group->post('/courseware-block-comments', Routes\Courseware\BlockCommentsCreate::class);
+        $group->get('/courseware-block-comments/{id}', Routes\Courseware\BlockCommentsShow::class);
+        $group->patch('/courseware-block-comments/{id}', Routes\Courseware\BlockCommentsUpdate::class);
+        $group->delete('/courseware-block-comments/{id}', Routes\Courseware\BlockCommentsDelete::class);
 
-        $this->app->get('/courseware-blocks/{id}/feedback', Routes\Courseware\BlockFeedbacksOfBlocksIndex::class);
-        $this->app->post('/courseware-block-feedback', Routes\Courseware\BlockFeedbacksCreate::class);
-        $this->app->get('/courseware-block-feedback/{id}', Routes\Courseware\BlockFeedbacksShow::class);
+        $group->get('/courseware-blocks/{id}/feedback', Routes\Courseware\BlockFeedbacksOfBlocksIndex::class);
+        $group->post('/courseware-block-feedback', Routes\Courseware\BlockFeedbacksCreate::class);
+        $group->get('/courseware-block-feedback/{id}', Routes\Courseware\BlockFeedbacksShow::class);
     }
 
-    private function addAuthenticatedFilesRoutes()
+    private function addAuthenticatedFilesRoutes(RouteCollectorProxy $group): void
     {
-        $this->app->get('/terms-of-use', Routes\Files\TermsOfUseIndex::class);
-        $this->app->get('/terms-of-use/{id}', Routes\Files\TermsOfUseShow::class);
+        $group->get('/terms-of-use', Routes\Files\TermsOfUseIndex::class);
+        $group->get('/terms-of-use/{id}', Routes\Files\TermsOfUseShow::class);
 
-        $this->app->get('/{type:courses|institutes|users}/{id}/file-refs', Routes\Files\RangeFileRefsIndex::class);
-        $this->app->get('/{type:courses|institutes|users}/{id}/folders', Routes\Files\RangeFoldersIndex::class);
+        $group->get('/{type:courses|institutes|users}/{id}/file-refs', Routes\Files\RangeFileRefsIndex::class);
+        $group->get('/{type:courses|institutes|users}/{id}/folders', Routes\Files\RangeFoldersIndex::class);
 
-        $this->app->post('/{type:courses|institutes|users}/{id}/folders', Routes\Files\RangeFoldersCreate::class);
+        $group->post('/{type:courses|institutes|users}/{id}/folders', Routes\Files\RangeFoldersCreate::class);
 
-        $this->app->get('/file-refs/{id}', Routes\Files\FileRefsShow::class);
-        $this->app->patch('/file-refs/{id}', Routes\Files\FileRefsUpdate::class);
-        $this->app->delete('/file-refs/{id}', Routes\Files\FileRefsDelete::class);
-        $this->addRelationship('/file-refs/{id}/relationships/terms-of-use', Routes\Files\Rel\TermsOfFileRef::class);
+        $group->get('/file-refs/{id}', Routes\Files\FileRefsShow::class);
+        $group->patch('/file-refs/{id}', Routes\Files\FileRefsUpdate::class);
+        $group->delete('/file-refs/{id}', Routes\Files\FileRefsDelete::class);
+        $this->addRelationship(
+            $group,
+            '/file-refs/{id}/relationships/terms-of-use',
+            Routes\Files\Rel\TermsOfFileRef::class
+        );
 
-        $this->app->map(['HEAD'], '/file-refs/{id}/content', Routes\Files\FileRefsContentHead::class);
-        $this->app->get('/file-refs/{id}/content', Routes\Files\FileRefsContentShow::class);
-        $this->app->post('/file-refs/{id}/content', Routes\Files\FileRefsContentUpdate::class);
+        $group->map(['HEAD'], '/file-refs/{id}/content', Routes\Files\FileRefsContentHead::class);
+        $group->get('/file-refs/{id}/content', Routes\Files\FileRefsContentShow::class);
+        $group->post('/file-refs/{id}/content', Routes\Files\FileRefsContentUpdate::class);
 
-        $this->app->get('/folders/{id}', Routes\Files\FoldersShow::class);
-        $this->app->patch('/folders/{id}', Routes\Files\FoldersUpdate::class);
-        $this->app->delete('/folders/{id}', Routes\Files\FoldersDelete::class);
+        $group->get('/folders/{id}', Routes\Files\FoldersShow::class);
+        $group->patch('/folders/{id}', Routes\Files\FoldersUpdate::class);
+        $group->delete('/folders/{id}', Routes\Files\FoldersDelete::class);
 
         // not a JSON route
-        $this->app->post('/folders/{id}/copy', Routes\Files\FoldersCopy::class);
+        $group->post('/folders/{id}/copy', Routes\Files\FoldersCopy::class);
 
-        $this->app->get('/folders/{id}/file-refs', Routes\Files\SubfilerefsIndex::class);
-        $this->app->get('/folders/{id}/folders', Routes\Files\SubfoldersIndex::class);
+        $group->get('/folders/{id}/file-refs', Routes\Files\SubfilerefsIndex::class);
+        $group->get('/folders/{id}/folders', Routes\Files\SubfoldersIndex::class);
 
-        $this->app->post('/folders/{id}/file-refs', Routes\Files\NegotiateFileRefsCreate::class);
-        $this->app->post('/folders/{id}/folders', Routes\Files\SubfoldersCreate::class);
+        $group->post('/folders/{id}/file-refs', Routes\Files\NegotiateFileRefsCreate::class);
+        $group->post('/folders/{id}/folders', Routes\Files\SubfoldersCreate::class);
 
-        $this->app->get('/files/{id}', Routes\Files\FilesShow::class);
-        $this->app->get('/files/{id}/file-refs', Routes\Files\FileRefsOfFilesShow::class);
-        $this->addRelationship('/files/{id}/relationships/file-refs', Routes\Files\Rel\FileRefsOfFile::class);
+        $group->get('/files/{id}', Routes\Files\FilesShow::class);
+        $group->get('/files/{id}/file-refs', Routes\Files\FileRefsOfFilesShow::class);
+        $this->addRelationship($group, '/files/{id}/relationships/file-refs', Routes\Files\Rel\FileRefsOfFile::class);
     }
 
-    private function addAuthenticatedMessagesRoutes()
+    private function addAuthenticatedMessagesRoutes(RouteCollectorProxy $group): void
     {
-        $this->app->get('/users/{id}/inbox', Routes\Messages\InboxShow::class);
+        $group->get('/users/{id}/inbox', Routes\Messages\InboxShow::class);
 
-        $this->app->get('/users/{id}/outbox', Routes\Messages\OutboxShow::class);
+        $group->get('/users/{id}/outbox', Routes\Messages\OutboxShow::class);
 
-        $this->app->post('/messages', Routes\Messages\MessageCreate::class);
-        $this->app->get('/messages/{id}', Routes\Messages\MessageShow::class);
-        $this->app->patch('/messages/{id}', Routes\Messages\MessageUpdate::class);
-        $this->app->delete('/messages/{id}', Routes\Messages\MessageDelete::class);
+        $group->post('/messages', Routes\Messages\MessageCreate::class);
+        $group->get('/messages/{id}', Routes\Messages\MessageShow::class);
+        $group->patch('/messages/{id}', Routes\Messages\MessageUpdate::class);
+        $group->delete('/messages/{id}', Routes\Messages\MessageDelete::class);
     }
 
-    private function addAuthenticatedForumRoutes()
+    private function addAuthenticatedForumRoutes(RouteCollectorProxy $group): void
     {
-        $this->app->get('/courses/{id}/forum-categories', Routes\Forum\ForumCategoriesIndex::class);
+        $group->get('/courses/{id}/forum-categories', Routes\Forum\ForumCategoriesIndex::class);
 
-        $this->app->get('/forum-entries/{id}', Routes\Forum\ForumEntriesShow::class);
-        $this->app->get('/forum-entries/{id}/entries', Routes\Forum\ForumEntryEntriesIndex::class);
+        $group->get('/forum-entries/{id}', Routes\Forum\ForumEntriesShow::class);
+        $group->get('/forum-entries/{id}/entries', Routes\Forum\ForumEntryEntriesIndex::class);
 
-        $this->app->get('/forum-categories/{id}', Routes\Forum\ForumCategoriesShow::class);
+        $group->get('/forum-categories/{id}', Routes\Forum\ForumCategoriesShow::class);
 
-        $this->app->get('/forum-categories/{id}/entries', Routes\Forum\ForumCategoryEntriesIndex::class);
+        $group->get('/forum-categories/{id}/entries', Routes\Forum\ForumCategoryEntriesIndex::class);
 
-        $this->app->post('/forum-entries/{id}/entries', Routes\Forum\ForumEntryEntriesCreate::class);
-        $this->app->post('/forum-categories/{id}/entries', Routes\Forum\ForumCategoryEntriesCreate::class);
-        $this->app->post('/courses/{id}/forum-categories', Routes\Forum\ForumCategoriesCreate::class);
+        $group->post('/forum-entries/{id}/entries', Routes\Forum\ForumEntryEntriesCreate::class);
+        $group->post('/forum-categories/{id}/entries', Routes\Forum\ForumCategoryEntriesCreate::class);
+        $group->post('/courses/{id}/forum-categories', Routes\Forum\ForumCategoriesCreate::class);
 
-        $this->app->patch('/forum-categories/{id}', Routes\Forum\ForumCategoriesUpdate::class);
-        $this->app->patch('/forum-entries/{id}', Routes\Forum\ForumEntriesUpdate::class);
+        $group->patch('/forum-categories/{id}', Routes\Forum\ForumCategoriesUpdate::class);
+        $group->patch('/forum-entries/{id}', Routes\Forum\ForumEntriesUpdate::class);
 
-        $this->app->delete('/forum-categories/{id}', Routes\Forum\ForumCategoriesDelete::class);
-        $this->app->delete('/forum-entries/{id}', Routes\Forum\ForumEntriesDelete::class);
+        $group->delete('/forum-categories/{id}', Routes\Forum\ForumCategoriesDelete::class);
+        $group->delete('/forum-entries/{id}', Routes\Forum\ForumEntriesDelete::class);
     }
 
-    private function addRelationship($url, $handler)
+    private function addRelationship(RouteCollectorProxy $group, string $url, string $handler): void
     {
-        $this->app->map(['GET', 'PATCH', 'POST', 'DELETE'], $url, $handler);
+        $group->map(['GET', 'PATCH', 'POST', 'DELETE'], $url, $handler);
     }
 }
diff --git a/lib/classes/JsonApi/Routes/ActivityStreamShow.php b/lib/classes/JsonApi/Routes/ActivityStreamShow.php
index 1d889cc14aa5287dc0a58d9f561431d97c5725ed..2a1a5574d30bea53153a1f56a18585e7b7fdedcf 100644
--- a/lib/classes/JsonApi/Routes/ActivityStreamShow.php
+++ b/lib/classes/JsonApi/Routes/ActivityStreamShow.php
@@ -14,7 +14,7 @@ use Studip\Activity\InstituteContext;
 use Studip\Activity\Stream;
 use Studip\Activity\UserContext;
 
-function canShowActivityStream(\User $observer, $userId)
+function canShowActivityStream(\User $observer, string $userId): bool
 {
     if (!$GLOBALS['perm']->have_perm('root', $observer->id)) {
         return true;
@@ -31,7 +31,7 @@ class ActivityStreamShow extends JsonApiController
 
     protected $allowedPagingParameters = ['offset', 'limit'];
 
-    public function __invoke(Request $request, Response $response, $args)
+    public function __invoke(Request $request, Response $response, array $args): Response
     {
         if (!canShowActivityStream($this->getUser($request), $userId = $args['id'])) {
             throw new AuthorizationFailedException();
@@ -41,28 +41,18 @@ class ActivityStreamShow extends JsonApiController
             throw new RecordNotFoundException();
         }
 
-        $urlFilter = $this->getUrlFilter($request);
+        $urlFilter = $this->getUrlFilter();
+        /** @var \User $user */
         $contexts = $this->createContexts($user);
         $filter = $this->createFilter($urlFilter);
 
         try {
-            if (!$stream = $this->createStream($contexts, $filter)) {
-                $data = [];
-                $total = 0;
-            } else {
-                list($offset, $limit) = $this->getOffsetAndLimit();
-                $total = count($stream);
-                $data = array_slice($stream->getIterator()->getArrayCopy(), $offset, $limit);
-            }
+            $stream = $this->createStream($contexts, $filter);
+            list($offset, $limit) = $this->getOffsetAndLimit();
+            $total = count($stream);
+            $data = array_slice($stream->getIterator()->getArrayCopy(), $offset, $limit);
         } catch (\Exception $exception) {
-            $error = new \Neomerx\JsonApi\Document\Error(
-                'internal-server-error',
-                null,
-                500,
-                'internal-server-error',
-                $exception->getMessage()
-            );
-            throw new \Neomerx\JsonApi\Exceptions\JsonApiException($error, 500);
+            throw new \JsonApi\Errors\InternalServerError($exception->getMessage());
         }
 
         $meta = ['filter' => $urlFilter];
@@ -70,7 +60,7 @@ class ActivityStreamShow extends JsonApiController
         return $this->getPaginatedContentResponse($data, $total, 200, null, $meta);
     }
 
-    private function getUrlFilter()
+    private function getUrlFilter(): array
     {
         $params = $this->getQueryParameters();
         $filtering = $params->getFilteringParameters();
@@ -100,7 +90,7 @@ class ActivityStreamShow extends JsonApiController
         return $filter;
     }
 
-    private function createContexts(\User $user)
+    private function createContexts(\User $user): array
     {
         $contexts = [
             new SystemContext($user),
@@ -125,7 +115,7 @@ class ActivityStreamShow extends JsonApiController
         return $contexts;
     }
 
-    private function createFilter($urlFilter)
+    private function createFilter(array $urlFilter): Filter
     {
         $filter = new Filter();
 
@@ -137,8 +127,6 @@ class ActivityStreamShow extends JsonApiController
                         $word,
                         [
                             'activity',
-                            // TODO: Polishing
-                            // 'blubber',
                             'documents',
                             'forum',
                             'message',
@@ -162,7 +150,7 @@ class ActivityStreamShow extends JsonApiController
         return $filter;
     }
 
-    private function createStream($contexts, $filter)
+    private function createStream(array $contexts, Filter $filter): Stream
     {
         return new Stream($contexts, $filter);
     }
diff --git a/lib/classes/JsonApi/Routes/ArrayHelperTrait.php b/lib/classes/JsonApi/Routes/ArrayHelperTrait.php
index 46ec41b29dbfe4f90c343595757cb137b5ca0db0..f39dabf35eb4d44a6b9db348278f57bd72ad5742 100644
--- a/lib/classes/JsonApi/Routes/ArrayHelperTrait.php
+++ b/lib/classes/JsonApi/Routes/ArrayHelperTrait.php
@@ -1,7 +1,5 @@
 <?php
 
-// TODO: das gehört bestimmt nicht in Routes
-
 namespace JsonApi\Routes;
 
 trait ArrayHelperTrait
diff --git a/lib/classes/JsonApi/Routes/Blubber/FilterTrait.php b/lib/classes/JsonApi/Routes/Blubber/FilterTrait.php
index 5e12b832d1a7a8f7e434eb5bb7ddf948b90ccafb..1f043388f68733707f07e0c94fcd911bbd553e90 100644
--- a/lib/classes/JsonApi/Routes/Blubber/FilterTrait.php
+++ b/lib/classes/JsonApi/Routes/Blubber/FilterTrait.php
@@ -2,6 +2,8 @@
 
 namespace JsonApi\Routes\Blubber;
 
+use Psr\Http\Message\ServerRequestInterface as Request;
+
 trait FilterTrait
 {
     private function validateFilters()
@@ -21,7 +23,7 @@ trait FilterTrait
         }
     }
 
-    private function getFilters()
+    private function getFilters(): array
     {
         $filtering = $this->getQueryParameters()->getFilteringParameters() ?? [];
 
diff --git a/lib/classes/JsonApi/Routes/Blubber/ThreadsIndex.php b/lib/classes/JsonApi/Routes/Blubber/ThreadsIndex.php
index 8c12031ebe3ae1f11d9993ab2bd1446cda378f45..74f406962ef874fe83c16fa45be3e6924e131b77 100644
--- a/lib/classes/JsonApi/Routes/Blubber/ThreadsIndex.php
+++ b/lib/classes/JsonApi/Routes/Blubber/ThreadsIndex.php
@@ -25,8 +25,6 @@ class ThreadsIndex extends JsonApiController
      */
     public function __invoke(Request $request, Response $response, $args)
     {
-        $this->validateFilters();
-
         $contextType = $args['type'];
         if (!in_array($contextType, ['all', 'public', 'private', 'course', 'institute'])) {
             throw new BadRequestException('Wrong context type.');
@@ -34,7 +32,9 @@ class ThreadsIndex extends JsonApiController
 
         switch ($contextType) {
             case 'all':
-                list($threads, $total) = $this->getAllThreads($this->getUser($request));
+                $this->validateFilters();
+                $filters = $this->getFilters();
+                list($threads, $total) = $this->getAllThreads($filters, $this->getUser($request));
                 break;
 
             case 'public':
@@ -57,9 +57,8 @@ class ThreadsIndex extends JsonApiController
         return $this->getPaginatedContentResponse($threads, $total);
     }
 
-    private function getAllThreads(\User $observer)
+    private function getAllThreads(array $filters, \User $observer)
     {
-        $filters = $this->getFilters();
         list($offset, $limit) = $this->getOffsetAndLimit();
 
         $threads = \BlubberThread::findMyGlobalThreads(
diff --git a/lib/classes/JsonApi/Routes/Courses/CoursesByUserIndex.php b/lib/classes/JsonApi/Routes/Courses/CoursesByUserIndex.php
index 2be3e1d01260a3461df3e70bcc49846c0fa7f7ac..2adf6d7ccb2af3edd26d638fbd90146f9736483f 100644
--- a/lib/classes/JsonApi/Routes/Courses/CoursesByUserIndex.php
+++ b/lib/classes/JsonApi/Routes/Courses/CoursesByUserIndex.php
@@ -34,7 +34,7 @@ class CoursesByUserIndex extends JsonApiController
     /**
      * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
-    public function __invoke(Request $request, Response $response, $args)
+    public function __invoke(Request $request, Response $response, array $args): Response
     {
         if (!$user = \User::find($args['id'])) {
             throw new RecordNotFoundException();
diff --git a/lib/classes/JsonApi/Routes/DiscoveryIndex.php b/lib/classes/JsonApi/Routes/DiscoveryIndex.php
index 5323a2211122641dbccafba7b563ad37f5d9e33b..e5c74b1ba93415f6e81c34f3cfe69b5306365223 100644
--- a/lib/classes/JsonApi/Routes/DiscoveryIndex.php
+++ b/lib/classes/JsonApi/Routes/DiscoveryIndex.php
@@ -8,9 +8,9 @@ use JsonApi\JsonApiController;
 
 class DiscoveryIndex extends JsonApiController
 {
-    public function __invoke(Request $request, Response $response, $args)
+    public function __invoke(Request $request, Response $response)
     {
-        $routes = $this->container->get('router')->getRoutes();
+        $routes = $this->container->get(\Slim\App::class)->getRouteCollector()->getRoutes();
 
         return $this->getContentResponse($routes);
     }
diff --git a/lib/classes/JsonApi/Routes/Events/UserEventsIcal.php b/lib/classes/JsonApi/Routes/Events/UserEventsIcal.php
index a4e6da79c18286d8ccf50a86d0f0a5f443625519..2dbc4c2e33d2fd74872ea397ee51880509ce7848 100644
--- a/lib/classes/JsonApi/Routes/Events/UserEventsIcal.php
+++ b/lib/classes/JsonApi/Routes/Events/UserEventsIcal.php
@@ -31,9 +31,9 @@ class UserEventsIcal extends NonJsonApiController
         }
 
         $content = implode($export->getExport());
+        $response->getBody()->write($content);
 
         return $response->withHeader('Content-Type', 'text/calendar')
-            ->withHeader('Content-Disposition', 'attachment; '.encode_header_parameter('filename', 'studip.ics'))
-            ->write($content);
+            ->withHeader('Content-Disposition', 'attachment; ' . encode_header_parameter('filename', 'studip.ics'));
     }
 }
diff --git a/lib/classes/JsonApi/Routes/Files/FileRefsContentHead.php b/lib/classes/JsonApi/Routes/Files/FileRefsContentHead.php
index a0343f5bdcc5a336ed85d2291ba14c23ff9c5d8c..93aaa95fd4db4fb50faa6f71c20afb7f4549df42 100644
--- a/lib/classes/JsonApi/Routes/Files/FileRefsContentHead.php
+++ b/lib/classes/JsonApi/Routes/Files/FileRefsContentHead.php
@@ -12,7 +12,7 @@ class FileRefsContentHead extends NonJsonApiController
 {
     use EtagHelperTrait;
 
-    public function invoke(Request $request, Response $response, $args)
+    public function invoke(Request $request, Response $response, array $args): Response
     {
         if (!$fileRef = \FileRef::find($args['id'])) {
             throw new RecordNotFoundException();
@@ -21,7 +21,6 @@ class FileRefsContentHead extends NonJsonApiController
         if (!Authority::canDownloadFileRef($this->getUser($request), $fileRef)) {
             throw new AuthorizationFailedException();
         }
-
         list(, $response) = $this->handleEtag($request, $response, $fileRef, true);
 
         return $response;
diff --git a/lib/classes/JsonApi/Routes/Files/FileRefsCreateByUpload.php b/lib/classes/JsonApi/Routes/Files/FileRefsCreateByUpload.php
index 880354c6a4395759005b0f6ffe70bb31925b0872..3bb00d1b9ed0b50a7576565e71a811424ae03821 100644
--- a/lib/classes/JsonApi/Routes/Files/FileRefsCreateByUpload.php
+++ b/lib/classes/JsonApi/Routes/Files/FileRefsCreateByUpload.php
@@ -12,7 +12,7 @@ class FileRefsCreateByUpload extends NonJsonApiController
 {
     use RoutesHelperTrait;
 
-    public function invoke(Request $request, Response $response, $args)
+    public function invoke(Request $request, Response $response, array $args): Response
     {
         if (!$folder = \FileManager::getTypedFolder($args['id'])) {
             throw new RecordNotFoundException();
diff --git a/lib/classes/JsonApi/Routes/Files/FoldersCopy.php b/lib/classes/JsonApi/Routes/Files/FoldersCopy.php
index 60d0f79997610bb25f61c2e03d3e606c01e7b5a1..046fac118bea30d1dce2d4d31f8ea5c831b5c5f6 100644
--- a/lib/classes/JsonApi/Routes/Files/FoldersCopy.php
+++ b/lib/classes/JsonApi/Routes/Files/FoldersCopy.php
@@ -8,7 +8,6 @@ use JsonApi\Errors\AuthorizationFailedException;
 use JsonApi\Errors\BadRequestException;
 use JsonApi\Errors\RecordNotFoundException;
 use JsonApi\NonJsonApiController;
-use JsonApi\Providers\JsonApiConfig as C;
 
 class FoldersCopy extends NonJsonApiController
 {
@@ -45,7 +44,7 @@ class FoldersCopy extends NonJsonApiController
     {
         $pathinfo = $this->getSchema($folder)->getSelfSubLink($folder)->getSubHref();
         $old = \URLHelper::setBaseURL($GLOBALS['ABSOLUTE_URI_STUDIP']);
-        $url = \URLHelper::getURL($this->container->get(C::JSON_URL_PREFIX).$pathinfo, [], true);
+        $url = \URLHelper::getURL($this->container->get('json-api-integration-urlPrefix').$pathinfo, [], true);
         \URLHelper::setBaseURL($old);
 
         return $response->withRedirect($url, 201);
diff --git a/lib/classes/JsonApi/Routes/Files/NegotiateFileRefsCreate.php b/lib/classes/JsonApi/Routes/Files/NegotiateFileRefsCreate.php
index 22add9e6ea6e77c01202ef8151d726e1509031f3..89e00e111a5a1a0a1510f00de353166170d75257 100644
--- a/lib/classes/JsonApi/Routes/Files/NegotiateFileRefsCreate.php
+++ b/lib/classes/JsonApi/Routes/Files/NegotiateFileRefsCreate.php
@@ -3,11 +3,14 @@
 namespace JsonApi\Routes\Files;
 
 use Psr\Container\ContainerInterface;
-use Psr\Http\Message\ServerRequestInterface as Request;
 use Psr\Http\Message\ResponseInterface as Response;
+use Psr\Http\Message\ServerRequestInterface as Request;
 
 class NegotiateFileRefsCreate
 {
+    /** @var ContainerInterface */
+    private $container;
+
     /**
      * Der Konstruktor.
      *
@@ -18,13 +21,13 @@ class NegotiateFileRefsCreate
         $this->container = $container;
     }
 
-    public function __invoke(Request $request, Response $response, $args)
+    public function __invoke(Request $request, Response $response, array $args): Response
     {
         $contentType = $request->getHeaderLine('Content-Type');
         if ('multipart/form-data' === substr($contentType, 0, strlen('multipart/form-data'))) {
-            $route = new FileRefsCreateByUpload($this->container);
+            $route = $this->container->get(FileRefsCreateByUpload::class);
         } else {
-            $route = new FileRefsCreate($this->container);
+            $route = $this->container->get(FileRefsCreate::class);
         }
 
         return $route($request, $response, $args);
diff --git a/lib/classes/JsonApi/Routes/Files/RoutesHelperTrait.php b/lib/classes/JsonApi/Routes/Files/RoutesHelperTrait.php
index 3590e1419aefcead52a47769e5c4b239fa09f232..8122ec59893752417cd4ff675c589b4c62861ae8 100644
--- a/lib/classes/JsonApi/Routes/Files/RoutesHelperTrait.php
+++ b/lib/classes/JsonApi/Routes/Files/RoutesHelperTrait.php
@@ -4,13 +4,12 @@ namespace JsonApi\Routes\Files;
 
 use JsonApi\Errors\BadRequestException;
 use JsonApi\Errors\InternalServerError;
-use JsonApi\Providers\JsonApiConfig as C;
 use JsonApi\Schemas\FileRef as FileRefSchema;
 use JsonApi\Schemas\Folder as FolderSchema;
 use JsonApi\Schemas\ContentTermsOfUse as ContentTermsOfUseSchema;
 use Psr\Http\Message\ServerRequestInterface as Request;
 use Psr\Http\Message\ResponseInterface as Response;
-use Slim\Http\UploadedFile;
+use Slim\Psr7\UploadedFile;
 
 trait RoutesHelperTrait
 {
@@ -292,11 +291,11 @@ trait RoutesHelperTrait
      */
     private function redirectToFileRef(Response $response, \FileRef $fileRef)
     {
-        $pathinfo = $this->getSchema($fileRef)->getSelfSubLink($fileRef)->getSubHref();
+        $pathinfo = $this->getSchema($fileRef)->getSelfLink($fileRef)->getStringRepresentation($this->container->get('json-api-integration-urlPrefix'));
         $old = \URLHelper::setBaseURL($GLOBALS['ABSOLUTE_URI_STUDIP']);
-        $url = \URLHelper::getURL($this->container->get(C::JSON_URL_PREFIX).$pathinfo, [], true);
+        $url = \URLHelper::getURL($pathinfo, [], true);
         \URLHelper::setBaseURL($old);
 
-        return $response->withRedirect($url, 201);
+        return $response->withHeader('Location', $url)->withStatus(201);
     }
 }
diff --git a/lib/classes/JsonApi/Routes/News/Rel/Ranges.php b/lib/classes/JsonApi/Routes/News/Rel/Ranges.php
index 1ab24a478e105e2d9a78d5dfa5e5703b4c9312b3..3f90dcdcf4d25568f625c22813b18ef377c8cfde 100644
--- a/lib/classes/JsonApi/Routes/News/Rel/Ranges.php
+++ b/lib/classes/JsonApi/Routes/News/Rel/Ranges.php
@@ -105,7 +105,7 @@ class Ranges extends RelationshipsController
         return array_filter(
             $news->news_ranges->map(function ($range) use ($types) {
                 if ('global' === $range->type) {
-                    return $this->container['studip-system-object'];
+                    return $this->getGlobalRange();
                 } elseif (isset($types[$range->type])) {
                     $klass = $types[$range->type];
 
@@ -172,7 +172,7 @@ class Ranges extends RelationshipsController
     {
         switch ($type) {
             case \JsonApi\Schemas\Studip::TYPE:
-                return $this->container['studip-system-object'];
+                return $this->getGlobalRange();
 
             case \JsonApi\Schemas\Course::TYPE:
                 return \Course::find($rangeId);
@@ -187,6 +187,11 @@ class Ranges extends RelationshipsController
         return null;
     }
 
+    private function getGlobalRange()
+    {
+        return new \JsonApi\Model\Studip();
+    }
+
     private function addRanges(\StudipNews $news, array $ranges)
     {
         foreach ($ranges as $range) {
diff --git a/lib/classes/JsonApi/Routes/RelationshipsController.php b/lib/classes/JsonApi/Routes/RelationshipsController.php
index 1a42113a26ebb2448faa687f4fa3c9c1e394bcd1..47b546c782b26040d03ad98edd1c8f0678d99302 100644
--- a/lib/classes/JsonApi/Routes/RelationshipsController.php
+++ b/lib/classes/JsonApi/Routes/RelationshipsController.php
@@ -5,7 +5,7 @@ namespace JsonApi\Routes;
 use JsonApi\Errors\AuthorizationFailedException;
 use JsonApi\Errors\UnsupportedRequestError;
 use JsonApi\JsonApiController;
-use Neomerx\JsonApi\Contracts\Document\DocumentInterface;
+use Neomerx\JsonApi\Contracts\Schema\DocumentInterface;
 use Psr\Http\Message\ResponseInterface as Response;
 use Psr\Http\Message\ServerRequestInterface as Request;
 
diff --git a/lib/classes/JsonApi/Routes/Schedule/UserScheduleShow.php b/lib/classes/JsonApi/Routes/Schedule/UserScheduleShow.php
index 48477c625dd99e22334324d77295bb022cbc81c5..85cf9113e08fa9947ec72832678e3577ca231bf9 100644
--- a/lib/classes/JsonApi/Routes/Schedule/UserScheduleShow.php
+++ b/lib/classes/JsonApi/Routes/Schedule/UserScheduleShow.php
@@ -7,8 +7,7 @@ use JsonApi\Errors\RecordNotFoundException;
 use JsonApi\JsonApiController;
 use JsonApi\Models\ScheduleEntry;
 use JsonApi\Routes\Users\Authority;
-use Neomerx\JsonApi\Contracts\Http\ResponsesInterface;
-use Neomerx\JsonApi\Document\Link;
+use Neomerx\JsonApi\Schema\Link;
 use Psr\Http\Message\ResponseInterface as Response;
 use Psr\Http\Message\ServerRequestInterface as Request;
 
@@ -48,13 +47,13 @@ class UserScheduleShow extends JsonApiController
                 ScheduleEntry::findByUser_id($otherUser->id),
                 $this->getCycles($otherUser, $semester)
             ),
-            ResponsesInterface::HTTP_OK,
+            200,
             [Link::SELF => $this->getSelfLink($otherUser, $semester)],
             $this->getMeta($semester)
         );
     }
 
-    private function getCycles(\User $user, \Semester $semester)
+    private function getCycles(\User $user, \Semester $semester): array
     {
         // get all virtually added seminars
         $stmt = \DBManager::get()->prepare(
@@ -76,42 +75,41 @@ class UserScheduleShow extends JsonApiController
                 AND (semester_courses.semester_id IS NULL OR semester_courses.semester_id = :semester_id)
         ');
         $stmt->execute([
-            'userid'      => $user->id,
-            'begin'       => $semester->beginn,
+            'userid' => $user->id,
+            'begin' => $semester->beginn,
             'semester_id' => $semester->id,
         ]);
 
         return array_reduce(
             array_unique(array_merge($ids, $stmt->fetchFirst())),
             function ($cycles, $seminarId) {
-                return array_merge($cycles,
-                                   array_filter(
-                                       \Course::find($seminarId)->cycles->getArrayCopy(),
-                                       function ($cycle) {
-                                           return $cycle['is_visible'];
-                                       }
-                                   )
+                return array_merge(
+                    $cycles,
+                    array_filter(
+                        \Course::find($seminarId)->cycles->getArrayCopy(),
+                        function ($cycle) {
+                            return $cycle['is_visible'];
+                        }
+                    )
                 );
             },
             []
         );
     }
 
-    private function getSelfLink($user, $semester)
+    private function getSelfLink(\User $user, \Semester $semester): Link
     {
-        $url = $this->container['router']->pathFor(
-            'get-schedule',
-            ['id' => $user->id],
-            ['filter[timestamp]' => $semester->beginn]
-        );
+        $routeParser = $this->app->getRouteCollector()->getRouteParser();
+        $url = $routeParser->urlFor('get-schedule', ['id' => $user->id], ['filter[timestamp]' => $semester->beginn]);
 
-        return new Link($url);
+        return new Link(false, $url, false);
     }
 
-    private function getMeta($semester)
+    private function getMeta(\Semester $semester): array
     {
-        return [
-            'semester' => $this->getResourceLocationUrl($semester),
-        ];
+        $routeParser = $this->app->getRouteCollector()->getRouteParser();
+        $url = $routeParser->urlFor('get-semester', ['id' => $semester->id]);
+
+        return [ 'semester' => $url ];
     }
 }
diff --git a/lib/classes/JsonApi/Routes/Studip/PropertiesIndex.php b/lib/classes/JsonApi/Routes/Studip/PropertiesIndex.php
index 9ca30dc5968b080a2cdca062a25e9ac7ad79aa83..8758446c6c2a6cafb226f12383f70213ed276e82 100644
--- a/lib/classes/JsonApi/Routes/Studip/PropertiesIndex.php
+++ b/lib/classes/JsonApi/Routes/Studip/PropertiesIndex.php
@@ -16,7 +16,7 @@ class PropertiesIndex extends JsonApiController
      */
     public function __invoke(Request $request, Response $response, $args)
     {
-        $studip = $this->container['studip-system-object'];
+        $studip = new \JsonApi\Models\Studip();
 
         return $this->getContentResponse($studip->getProperties());
     }
diff --git a/lib/classes/JsonApi/Routes/Users/UsersIndex.php b/lib/classes/JsonApi/Routes/Users/UsersIndex.php
index f430eead8f74f7f60c4bc2773be298f75adb662d..1418354bd085e205e6a99250e3f2a996bd7ee306 100644
--- a/lib/classes/JsonApi/Routes/Users/UsersIndex.php
+++ b/lib/classes/JsonApi/Routes/Users/UsersIndex.php
@@ -26,7 +26,7 @@ class UsersIndex extends JsonApiController
     /**
      * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
-    public function __invoke(Request $request, Response $response, $args)
+    public function __invoke(Request $request, Response $response, $args): Response
     {
         if (!Authority::canIndexUsers($this->getUser($request))) {
             throw new AuthorizationFailedException();
@@ -37,13 +37,17 @@ class UsersIndex extends JsonApiController
 
         list($offset, $limit) = $this->getOffsetAndLimit();
         $partSQL = \GlobalSearchUsers::getSQL($filters['search'], [], $limit + $offset);
-        $users = \User::findMany(array_map(function ($array) {  return $array['user_id']; }, \DBManager::get()->fetchAll($partSQL)));
+        $users = \User::findMany(
+            array_map(function ($array) {
+                return $array['user_id'];
+            }, \DBManager::get()->fetchAll($partSQL))
+        );
         $total = (int) \DBManager::get()->fetchColumn('SELECT FOUND_ROWS() as found_rows');
 
         return $this->getPaginatedContentResponse($users, $total);
     }
 
-    private function validateFilters()
+    private function validateFilters(): void
     {
         $filtering = $this->getQueryParameters()->getFilteringParameters() ?? [];
 
diff --git a/lib/classes/JsonApi/Routes/Users/UsersShow.php b/lib/classes/JsonApi/Routes/Users/UsersShow.php
index be9f1afeb0a5c0672629de51576f1e34317b08b6..f22aaf33a06cc9e7dae64ee401b5000e977b3b87 100644
--- a/lib/classes/JsonApi/Routes/Users/UsersShow.php
+++ b/lib/classes/JsonApi/Routes/Users/UsersShow.php
@@ -2,12 +2,13 @@
 
 namespace JsonApi\Routes\Users;
 
-use Psr\Http\Message\ResponseInterface as Response;
-use Psr\Http\Message\ServerRequestInterface as Request;
-use JsonApi\JsonApiController;
 use JsonApi\Errors\AuthorizationFailedException;
 use JsonApi\Errors\RecordNotFoundException;
+use JsonApi\JsonApiController;
 use JsonApi\Schemas\User as UserSchema;
+use Psr\Http\Message\ResponseInterface as Response;
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Slim\Routing\RouteContext;
 
 class UsersShow extends JsonApiController
 {
@@ -21,12 +22,18 @@ class UsersShow extends JsonApiController
         UserSchema::REL_SCHEDULE,
     ];
 
-    public function __invoke(Request $request, Response $response, $args)
+    /**
+     * @return \Psr\Http\Message\ResponseInterface
+     */
+    public function __invoke(Request $request, Response $response, array $args)
     {
-        if (isset($args['id'])) {
-            $observedUser = \User::find($args['id']);
-        } else {
+        $routeName = RouteContext::fromRequest($request)
+            ->getRoute()
+            ->getName();
+        if ($routeName === 'get-myself') {
             $observedUser = $this->getUser($request);
+        } else {
+            $observedUser = \User::find($args['id']);
         }
         if (!$observedUser) {
             throw new RecordNotFoundException();
diff --git a/lib/classes/JsonApi/Routes/Wiki/WikiShow.php b/lib/classes/JsonApi/Routes/Wiki/WikiShow.php
index 4c2a5c1fcab81cfe5bb1540fc395dfa67935b626..a01c06fab06c4d168768e23689bf1423020ae1c9 100644
--- a/lib/classes/JsonApi/Routes/Wiki/WikiShow.php
+++ b/lib/classes/JsonApi/Routes/Wiki/WikiShow.php
@@ -3,10 +3,7 @@
 namespace JsonApi\Routes\Wiki;
 
 use JsonApi\Errors\AuthorizationFailedException;
-use JsonApi\Errors\BadRequestException;
 use JsonApi\JsonApiController;
-use Neomerx\JsonApi\Contracts\Http\ResponsesInterface;
-use Neomerx\JsonApi\Document\Link;
 use Psr\Http\Message\ResponseInterface as Response;
 use Psr\Http\Message\ServerRequestInterface as Request;
 
diff --git a/lib/classes/JsonApi/SchemaMap.php b/lib/classes/JsonApi/SchemaMap.php
index 2354a0ec2c800d74ea857ea0ccf32db49fb5568c..042ee58eab29f992cda21959fe112f8349c16ee7 100644
--- a/lib/classes/JsonApi/SchemaMap.php
+++ b/lib/classes/JsonApi/SchemaMap.php
@@ -7,58 +7,55 @@ namespace JsonApi;
  */
 class SchemaMap
 {
-    /**
-     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
-     */
-    public function __invoke(\Slim\Container $container)
+    public function __invoke(): array
     {
         return [
-            \Slim\Route::class => \JsonApi\Schemas\SlimRoute::class,
+            \Slim\Routing\Route::class => Schemas\SlimRoute::class,
 
-            \JsonApi\Models\ScheduleEntry::class => \JsonApi\Schemas\ScheduleEntry::class,
+            \JsonApi\Models\ScheduleEntry::class => Schemas\ScheduleEntry::class,
 
-            \BlubberComment::class => \JsonApi\Schemas\BlubberComment::class,
-            \BlubberStatusgruppeThread::class => \JsonApi\Schemas\BlubberStatusgruppeThread::class,
-            \BlubberThread::class => \JsonApi\Schemas\BlubberThread::class,
+            \BlubberComment::class => Schemas\BlubberComment::class,
+            \BlubberStatusgruppeThread::class => Schemas\BlubberStatusgruppeThread::class,
+            \BlubberThread::class => Schemas\BlubberThread::class,
 
-            \CalendarEvent::class => \JsonApi\Schemas\CalendarEvent::class,
-            \ConfigValue::class => \JsonApi\Schemas\ConfigValue::class,
-            \CourseEvent::class => \JsonApi\Schemas\CourseEvent::class,
-            \ContentTermsOfUse::class => \JsonApi\Schemas\ContentTermsOfUse::class,
-            \Course::class => \JsonApi\Schemas\Course::class,
-            \CourseMember::class => \JsonApi\Schemas\CourseMember::class,
-            \FeedbackElement::class => \JsonApi\Schemas\FeedbackElement::class,
-            \FeedbackEntry::class => \JsonApi\Schemas\FeedbackEntry::class,
-            \JsonApi\Models\ForumCat::class => \JsonApi\Schemas\ForumCategory::class,
-            \JsonApi\Models\ForumEntry::class => \JsonApi\Schemas\ForumEntry::class,
-            \Institute::class => \JsonApi\Schemas\Institute::class,
-            \InstituteMember::class => \JsonApi\Schemas\InstituteMember::class,
-            \Message::class => \JsonApi\Schemas\Message::class,
-            \SemClass::class => \JsonApi\Schemas\SemClass::class,
-            \Semester::class => \JsonApi\Schemas\Semester::class,
-            \SemType::class => \JsonApi\Schemas\SemType::class,
-            \SeminarCycleDate::class => \JsonApi\Schemas\SeminarCycleDate::class,
-            \Statusgruppen::class => \JsonApi\Schemas\StatusGroup::class,
-            \JsonApi\Models\Studip::class => \JsonApi\Schemas\Studip::class,
-            \JsonApi\Models\StudipProperty::class => \JsonApi\Schemas\StudipProperty::class,
-            \StudipComment::class => \JsonApi\Schemas\StudipComment::class,
-            \StudipNews::class => \JsonApi\Schemas\StudipNews::class,
-            \StudipStudyArea::class => \JsonApi\Schemas\StudyArea::class,
-            \WikiPage::class => \JsonApi\Schemas\WikiPage::class,
-            \Studip\Activity\Activity::class => \JsonApi\Schemas\Activity::class,
-            \User::class => \JsonApi\Schemas\User::class,
-            \File::class => \JsonApi\Schemas\File::class,
-            \FileRef::class => \JsonApi\Schemas\FileRef::class,
-            \FolderType::class => \JsonApi\Schemas\Folder::class,
+            \CalendarEvent::class => Schemas\CalendarEvent::class,
+            \ConfigValue::class => Schemas\ConfigValue::class,
+            \CourseEvent::class => Schemas\CourseEvent::class,
+            \ContentTermsOfUse::class => Schemas\ContentTermsOfUse::class,
+            \Course::class => Schemas\Course::class,
+            \CourseMember::class => Schemas\CourseMember::class,
+            \FeedbackElement::class => Schemas\FeedbackElement::class,
+            \FeedbackEntry::class => Schemas\FeedbackEntry::class,
+            \JsonApi\Models\ForumCat::class => Schemas\ForumCategory::class,
+            \JsonApi\Models\ForumEntry::class => Schemas\ForumEntry::class,
+            \Institute::class => Schemas\Institute::class,
+            \InstituteMember::class => Schemas\InstituteMember::class,
+            \Message::class => Schemas\Message::class,
+            \SemClass::class => Schemas\SemClass::class,
+            \Semester::class => Schemas\Semester::class,
+            \SemType::class => Schemas\SemType::class,
+            \SeminarCycleDate::class => Schemas\SeminarCycleDate::class,
+            \Statusgruppen::class => Schemas\StatusGroup::class,
+            \JsonApi\Models\Studip::class => Schemas\Studip::class,
+            \JsonApi\Models\StudipProperty::class => Schemas\StudipProperty::class,
+            \StudipComment::class => Schemas\StudipComment::class,
+            \StudipNews::class => Schemas\StudipNews::class,
+            \StudipStudyArea::class => Schemas\StudyArea::class,
+            \WikiPage::class => Schemas\WikiPage::class,
+            \Studip\Activity\Activity::class => Schemas\Activity::class,
+            \User::class => Schemas\User::class,
+            \File::class => Schemas\File::class,
+            \FileRef::class => Schemas\FileRef::class,
+            \FolderType::class => Schemas\Folder::class,
 
-            \Courseware\Block::class => \JsonApi\Schemas\Courseware\Block::class,
-            \Courseware\BlockComment::class => \JsonApi\Schemas\Courseware\BlockComment::class,
-            \Courseware\BlockFeedback::class => \JsonApi\Schemas\Courseware\BlockFeedback::class,
-            \Courseware\Container::class => \JsonApi\Schemas\Courseware\Container::class,
-            \Courseware\Instance::class => \JsonApi\Schemas\Courseware\Instance::class,
-            \Courseware\StructuralElement::class => \JsonApi\Schemas\Courseware\StructuralElement::class,
-            \Courseware\UserDataField::class => \JsonApi\Schemas\Courseware\UserDataField::class,
-            \Courseware\UserProgress::class => \JsonApi\Schemas\Courseware\UserProgress::class,
+            \Courseware\Block::class => Schemas\Courseware\Block::class,
+            \Courseware\BlockComment::class => Schemas\Courseware\BlockComment::class,
+            \Courseware\BlockFeedback::class => Schemas\Courseware\BlockFeedback::class,
+            \Courseware\Container::class => Schemas\Courseware\Container::class,
+            \Courseware\Instance::class => Schemas\Courseware\Instance::class,
+            \Courseware\StructuralElement::class => Schemas\Courseware\StructuralElement::class,
+            \Courseware\UserDataField::class => Schemas\Courseware\UserDataField::class,
+            \Courseware\UserProgress::class => Schemas\Courseware\UserProgress::class,
         ];
     }
 }
diff --git a/lib/classes/JsonApi/Schemas/Activity.php b/lib/classes/JsonApi/Schemas/Activity.php
index 300dc15bf252f4595c32d94c591ea0acacc4d1c3..ae95e0c0ea5e671882e96a4470e0401ffcdc24e2 100644
--- a/lib/classes/JsonApi/Schemas/Activity.php
+++ b/lib/classes/JsonApi/Schemas/Activity.php
@@ -2,7 +2,8 @@
 
 namespace JsonApi\Schemas;
 
-use Neomerx\JsonApi\Document\Link;
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
+use Neomerx\JsonApi\Schema\Link;
 use Studip\Activity\Activity as StudipActivity;
 
 class Activity extends SchemaProvider
@@ -16,14 +17,14 @@ class Activity extends SchemaProvider
      * Hier wird der Typ des Schemas festgelegt.
      * {@inheritdoc}
      */
-    protected $resourceType = self::TYPE;
+
 
     /**
      * Diese Method entscheidet über die JSON-API-spezifische ID von
      * Activity-Objekten.
      * {@inheritdoc}
      */
-    public function getId($activity)
+    public function getId($activity): ?string
     {
         return $activity->id;
     }
@@ -33,7 +34,7 @@ class Activity extends SchemaProvider
      * für die Ausgabe vorbereitet werden.
      * {@inheritdoc}
      */
-    public function getAttributes($activity)
+    public function getAttributes($activity, ContextInterface $context): iterable
     {
         if (preg_match('/\\\\([^\\\\]+)Provider$/', $activity->provider, $matches)) {
             $activityType = strtolower($matches[1]);
@@ -57,8 +58,11 @@ class Activity extends SchemaProvider
      * spezifiziert werden.
      * {@inheritdoc}
      */
-    public function getRelationships($activity, $isPrimary, array $includeList)
+    public function getRelationships($activity, ContextInterface $context): iterable
     {
+        $isPrimary = $context->getPosition()->getLevel() === 0;
+        $includeList = $context->getIncludePaths();
+
         $shouldInclude = function ($key) use ($isPrimary, $includeList) {
             return $isPrimary && in_array($key, $includeList);
         };
@@ -78,9 +82,10 @@ class Activity extends SchemaProvider
         $actorId = $activity->actor_id;
 
         if ($actorType === 'user') {
+            $actor = $include ? \User::findFull($actorId) : \User::build(['id' => $actorId], false);
             $relationships[self::REL_ACTOR] = [
-                self::LINKS => [Link::RELATED => new Link('/users/'.$actorId)],
-                self::DATA => $include ? \User::findFull($actorId) : \User::build(['id' => $actorId], false),
+                self::RELATIONSHIP_LINKS => [Link::RELATED => $this->createLinkToResource($actor)],
+                self::RELATIONSHIP_DATA => $actor
             ];
         }
 
@@ -90,8 +95,6 @@ class Activity extends SchemaProvider
     private function getObjectRelationship(array $relationships, StudipActivity $activity, $include)
     {
         $mapping = [
-            // TODO: Polishing
-            // 'blubber' => \BlubberPosting::class,
             'documents' => \FileRef::class,
             'forum' => \JsonApi\Models\ForumEntry::class,
             'message' => \Message::class,
@@ -114,17 +117,17 @@ class Activity extends SchemaProvider
             }
 
             if ($data) {
-                $link = $this->getSchemaContainer()->getSchema($data)->getSelfSubLink($data);
+                $link = $this->createLinkToResource($data);
                 $relationships[self::REL_OBJECT] = [
-                    self::LINKS => [
+                    self::RELATIONSHIP_LINKS => [
                         Link::RELATED => $link
                     ],
-                    self::DATA => $data,
+                    self::RELATIONSHIP_DATA => $data,
                 ];
             }
         } else {
             $relationships[self::REL_OBJECT] = [
-                self::META => [
+                self::RELATIONSHIP_META => [
                     'object-type' => $activity->object_type,
                     'object-id' => $activity->object_id,
                 ],
@@ -137,12 +140,12 @@ class Activity extends SchemaProvider
     private function getContextRelationship(array $relationships, StudipActivity $activity, $include)
     {
         if ($data = $this->getContext($activity, $include)) {
-            $link = $this->getSchemaContainer()->getSchema($data)->getSelfSubLink($data);
+            $link = $this->createLinkToResource($data);
             $relationships[self::REL_CONTEXT] = [
-                self::LINKS => [
+                self::RELATIONSHIP_LINKS => [
                     Link::RELATED => $link
                 ],
-                self::DATA => $data,
+                self::RELATIONSHIP_DATA => $data,
             ];
         }
 
diff --git a/lib/classes/JsonApi/Schemas/BlubberComment.php b/lib/classes/JsonApi/Schemas/BlubberComment.php
index 7d6e994ae60e315b5f3f79f4301b2070cb708175..2aabd5e19ea9e80322f9d5a32054d2c2727b6246 100644
--- a/lib/classes/JsonApi/Schemas/BlubberComment.php
+++ b/lib/classes/JsonApi/Schemas/BlubberComment.php
@@ -2,7 +2,8 @@
 
 namespace JsonApi\Schemas;
 
-use Neomerx\JsonApi\Document\Link;
+use Neomerx\JsonApi\Schema\Link;
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
 
 class BlubberComment extends SchemaProvider
 {
@@ -11,14 +12,12 @@ class BlubberComment extends SchemaProvider
     const REL_MENTIONS = 'mentions';
     const REL_THREAD = 'thread';
 
-    protected $resourceType = self::TYPE;
-
-    public function getId($resource)
+    public function getId($resource): ?string
     {
         return $resource->id;
     }
 
-    public function getAttributes($resource)
+    public function getAttributes($resource, ContextInterface $context): iterable
     {
         $attributes = [
             # `network` VARCHAR(64) COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL,
@@ -37,8 +36,11 @@ class BlubberComment extends SchemaProvider
      * spezifiziert werden.
      * {@inheritdoc}
      */
-    public function getRelationships($resource, $isPrimary, array $includeList)
+    public function getRelationships($resource, ContextInterface $context): iterable
     {
+        $isPrimary = $context->getPosition()->getLevel() === 0;
+        $includeList = $context->getIncludePaths();
+
         $shouldInclude = function ($key) use ($isPrimary, $includeList) {
             return $isPrimary && in_array($key, $includeList);
         };
@@ -62,12 +64,12 @@ class BlubberComment extends SchemaProvider
     {
         if (!$resource['external_contact']) {
             $userId = $resource['user_id'];
-
+            $data = $includeData ? \User::find($userId) : \User::build(['id' => $userId], false);
             $relationships[self::REL_AUTHOR] = [
-                self::LINKS => [
-                    Link::RELATED => new Link('/users/'.$userId),
+                self::RELATIONSHIP_LINKS => [
+                    Link::RELATED => $this->createLinkToResource($data),
                 ],
-                self::DATA => $includeData ? \User::find($userId) : \User::build(['id' => $userId], false),
+                self::RELATIONSHIP_DATA => $data,
             ];
         }
 
@@ -85,10 +87,10 @@ class BlubberComment extends SchemaProvider
         }
 
         $relationships[self::REL_MENTIONS] = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_MENTIONS),
             ],
-            self::DATA => $relatedUsers,
+            self::RELATIONSHIP_DATA => $relatedUsers,
         ];
 
         return $relationships;
@@ -103,11 +105,10 @@ class BlubberComment extends SchemaProvider
         }
 
         $relationships[self::REL_THREAD] = [
-            self::LINKS => [
-                Link::RELATED => $this->getSchemaContainer()
-                                      ->getSchema($related)->getSelfSubLink($related),
+            self::RELATIONSHIP_LINKS => [
+                Link::RELATED => $this->createLinkToResource($related),
             ],
-            self::DATA => $related,
+            self::RELATIONSHIP_DATA => $related,
         ];
 
         return $relationships;
diff --git a/lib/classes/JsonApi/Schemas/BlubberStatusgruppeThread.php b/lib/classes/JsonApi/Schemas/BlubberStatusgruppeThread.php
index 88cd38ebe2373e5fb47628f06f05dce889b47062..484cb6929c90c944dac2543a3f812d9f8d0ec02a 100644
--- a/lib/classes/JsonApi/Schemas/BlubberStatusgruppeThread.php
+++ b/lib/classes/JsonApi/Schemas/BlubberStatusgruppeThread.php
@@ -3,7 +3,8 @@
 namespace JsonApi\Schemas;
 
 use JsonApi\Errors\InternalServerError;
-use Neomerx\JsonApi\Document\Link;
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
+use Neomerx\JsonApi\Schema\Link;
 
 class BlubberStatusgruppeThread extends BlubberThread
 {
@@ -14,12 +15,15 @@ class BlubberStatusgruppeThread extends BlubberThread
      * spezifiziert werden.
      * {@inheritdoc}
      */
-    public function getRelationships($resource, $isPrimary, array $includeList)
+    public function getRelationships($resource, ContextInterface $context): iterable
     {
-        $relationships = parent::getRelationships($resource, $isPrimary, $includeList);
+        $isPrimary = $context->getPosition()->getLevel() === 0;
+        $includeList = $context->getIncludePaths();
+
+        $relationships = parent::getRelationships($resource, $context);
 
         $relationships[self::REL_STATUSGRUPPE] = [
-            self::DATA => \Statusgruppen::build(
+            self::RELATIONSHIP_DATA => \Statusgruppen::build(
                 [
                     'statusgruppe_id' => $resource['metadata']['statusgruppe_id']
                 ],
diff --git a/lib/classes/JsonApi/Schemas/BlubberThread.php b/lib/classes/JsonApi/Schemas/BlubberThread.php
index 575da930775bb13bac464e0103ce8a2d147c7a3d..386b51e1cd1ba406502d6b447cf153cbd99f181c 100644
--- a/lib/classes/JsonApi/Schemas/BlubberThread.php
+++ b/lib/classes/JsonApi/Schemas/BlubberThread.php
@@ -3,7 +3,8 @@
 namespace JsonApi\Schemas;
 
 use JsonApi\Errors\InternalServerError;
-use Neomerx\JsonApi\Document\Link;
+use Neomerx\JsonApi\Schema\Link;
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
 
 class BlubberThread extends SchemaProvider
 {
@@ -13,25 +14,25 @@ class BlubberThread extends SchemaProvider
     const REL_CONTEXT = 'context';
     const REL_MENTIONS = 'mentions';
 
-    protected $resourceType = self::TYPE;
 
-    public function getId($resource)
+
+    public function getId($resource): ?string
     {
         return $resource->id;
     }
 
-    public function getAttributes($resource)
+    public function getAttributes($resource, ContextInterface $context): iterable
     {
-        $user = $this->getDiContainer()->get('studip-current-user');
+        $userId = $this->currentUser->id;
 
         $attributes = [
             'context-type' => $resource['context_type'],
             'content' => $resource['content'],
             'content-html' => formatReady($resource['content']),
 
-            'is-commentable' => (bool) $resource->isCommentable($user->id),
-            'is-readable' => (bool) $resource->isReadable($user->id),
-            'is-writable' => (bool) $resource->isWritable($user->id),
+            'is-commentable' => (bool) $resource->isCommentable($userId),
+            'is-readable' => (bool) $resource->isReadable($userId),
+            'is-writable' => (bool) $resource->isWritable($userId),
 
             'is-visible-in-stream' => (bool) $resource->isVisibleInStream(),
 
@@ -47,8 +48,11 @@ class BlubberThread extends SchemaProvider
      * spezifiziert werden.
      * {@inheritdoc}
      */
-    public function getRelationships($resource, $isPrimary, array $includeList)
+    public function getRelationships($resource, ContextInterface $context): iterable
     {
+        $isPrimary = $context->getPosition()->getLevel() === 0;
+        $includeList = $context->getIncludePaths();
+
         $shouldInclude = function ($key) use ($isPrimary, $includeList) {
             return $isPrimary && in_array($key, $includeList);
         };
@@ -75,11 +79,10 @@ class BlubberThread extends SchemaProvider
             $userId = $resource['user_id'];
             $related = $includeData ? \User::find($userId) : \User::build(['id' => $userId], false);
             $relationships[self::REL_AUTHOR] = [
-                self::LINKS => [
-                    Link::RELATED => $this->getSchemaContainer()
-                                          ->getSchema($related)->getSelfSubLink($related),
+                self::RELATIONSHIP_LINKS => [
+                    Link::RELATED => $this->createLinkToResource($related),
                 ],
-                self::DATA => $related,
+                self::RELATIONSHIP_DATA => $related,
             ];
         }
 
@@ -98,9 +101,9 @@ class BlubberThread extends SchemaProvider
         }
 
         $relationships[self::REL_MENTIONS] = [
-            self::SHOW_SELF => true,
-            self::LINKS => [],
-            self::DATA => $relatedUsers,
+            self::RELATIONSHIP_LINKS_SELF => true,
+            self::RELATIONSHIP_LINKS => [],
+            self::RELATIONSHIP_DATA => $relatedUsers,
         ];
 
         return $relationships;
@@ -109,13 +112,13 @@ class BlubberThread extends SchemaProvider
     private function getCommentsRelationship(array $relationships, \BlubberThread $resource, $includeData)
     {
         $relationship = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_COMMENTS),
             ],
         ];
 
         if ($includeData) {
-            $relationship[self::DATA] = $resource->comments;
+            $relationship[self::RELATIONSHIP_DATA] = $resource->comments;
         }
 
         $relationships[self::REL_COMMENTS] = $relationship;
@@ -135,7 +138,7 @@ class BlubberThread extends SchemaProvider
                 throw new InternalServerError('Inconsistent data in BlubberThread.');
             }
 
-            $related = new Link('/courses/'.$course->id);
+            $related = $this->createLinkToResource($course);
             $data = $course;
         }
 
@@ -144,17 +147,17 @@ class BlubberThread extends SchemaProvider
                 throw new InternalServerError('Inconsistent data in BlubberThread.');
             }
 
-            $related = new Link('/institutes/'.$institute->id);
+            $related = $this->createLinkToResource($institute);
             $data = $institute;
         }
 
         if ($related && $data) {
             $relationships[self::REL_CONTEXT] = [
-                self::SHOW_SELF => true,
-                self::LINKS => [
+                self::RELATIONSHIP_LINKS_SELF => true,
+                self::RELATIONSHIP_LINKS => [
                     Link::RELATED => $related,
                 ],
-                self::DATA => $data,
+                self::RELATIONSHIP_DATA => $data,
             ];
         }
 
diff --git a/lib/classes/JsonApi/Schemas/CalendarEvent.php b/lib/classes/JsonApi/Schemas/CalendarEvent.php
index 95fb1d9ee008d1c4d1b9a821279d8be5ef2c68e6..3ee6ab5f34b59b683790bbb831eccfdac20e8ad1 100644
--- a/lib/classes/JsonApi/Schemas/CalendarEvent.php
+++ b/lib/classes/JsonApi/Schemas/CalendarEvent.php
@@ -2,21 +2,20 @@
 
 namespace JsonApi\Schemas;
 
-use Neomerx\JsonApi\Document\Link;
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
+use Neomerx\JsonApi\Schema\Link;
 
 class CalendarEvent extends SchemaProvider
 {
     const TYPE = 'calendar-events';
     const REL_OWNER = 'owner';
 
-    protected $resourceType = self::TYPE;
-
-    public function getId($resource)
+    public function getId($resource): ?string
     {
         return $resource->id;
     }
 
-    public function getAttributes($resource)
+    public function getAttributes($resource, ContextInterface $context): iterable
     {
         return [
             'title' => $resource->title,
@@ -36,14 +35,17 @@ class CalendarEvent extends SchemaProvider
     /**
      * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
-    public function getRelationships($resource, $isPrimary, array $includeList)
+    public function getRelationships($resource, ContextInterface $context): iterable
     {
+        $isPrimary = $context->getPosition()->getLevel() === 0;
+        $includeList = $context->getIncludePaths();
+
         $relationships = [];
 
         if ($owner = $resource->getOwner()) {
-            $link = $this->getSchemaContainer()->getSchema($owner)->getSelfSubLink($owner);
+            $link = $this->createLinkToResource($owner);
             $relationships = [
-                self::REL_OWNER => [self::LINKS => [Link::RELATED => $link], self::DATA => $owner],
+                self::REL_OWNER => [self::RELATIONSHIP_LINKS => [Link::RELATED => $link], self::RELATIONSHIP_DATA => $owner],
             ];
         }
 
diff --git a/lib/classes/JsonApi/Schemas/ConfigValue.php b/lib/classes/JsonApi/Schemas/ConfigValue.php
index 3e75317088bb6d1de90388259c4da50b3fa1754a..e24c5943f76a55993b8bf16278bc2f9ae9599fe9 100644
--- a/lib/classes/JsonApi/Schemas/ConfigValue.php
+++ b/lib/classes/JsonApi/Schemas/ConfigValue.php
@@ -2,18 +2,18 @@
 
 namespace JsonApi\Schemas;
 
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
+
 class ConfigValue extends SchemaProvider
 {
     const TYPE = 'config-values';
 
-    protected $resourceType = self::TYPE;
-
-    public function getId($resource)
+    public function getId($resource): ?string
     {
         return join('_', [$resource['range_id'], $resource['field']]);
     }
 
-    public function getAttributes($resource)
+    public function getAttributes($resource, ContextInterface $context): iterable
     {
         $i18nAwareCast = function ($mixed) use ($resource) {
             return 'i18n' === $resource->entry['type'] ? (string) $mixed : $mixed;
@@ -29,4 +29,9 @@ class ConfigValue extends SchemaProvider
             'chdate' => date('c', $resource['chdate']),
         ];
     }
+
+    public function getRelationships($user, ContextInterface $context): iterable
+    {
+        return [];
+    }
 }
diff --git a/lib/classes/JsonApi/Schemas/ConsultationBlock.php b/lib/classes/JsonApi/Schemas/ConsultationBlock.php
index fd01004dd3a15082e1f94d26b66b5c2a67e6ebcb..75a29df11b38fdc7f98693a63672ea31aa3b8fa5 100644
--- a/lib/classes/JsonApi/Schemas/ConsultationBlock.php
+++ b/lib/classes/JsonApi/Schemas/ConsultationBlock.php
@@ -2,7 +2,8 @@
 
 namespace JsonApi\Schemas;
 
-use Neomerx\JsonApi\Document\Link;
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
+use Neomerx\JsonApi\Schema\Link;
 
 class ConsultationBlock extends SchemaProvider
 {
@@ -10,14 +11,12 @@ class ConsultationBlock extends SchemaProvider
     const REL_SLOTS = 'slots';
     const REL_RANGE = 'range';
 
-    protected $resourceType = self::TYPE;
-
-    public function getId($resource)
+    public function getId($resource): ?string
     {
         return $resource->id;
     }
 
-    public function getAttributes($resource)
+    public function getAttributes($resource, ContextInterface $context): iterable
     {
         $attributes = [
             'start' => date('c', $resource->start),
@@ -47,8 +46,11 @@ class ConsultationBlock extends SchemaProvider
      * spezifiziert werden.
      * {@inheritdoc}
      */
-    public function getRelationships($resource, $isPrimary, array $includeList)
+    public function getRelationships($resource, ContextInterface $context): iterable
     {
+        $isPrimary = $context->getPosition()->getLevel() === 0;
+        $includeList = $context->getIncludePaths();
+
         $shouldInclude = function ($key) use ($isPrimary, $includeList) {
             return $isPrimary && in_array($key, $includeList);
         };
@@ -78,10 +80,10 @@ class ConsultationBlock extends SchemaProvider
         }
 
         $relationships[self::REL_SLOTS] = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_SLOTS),
             ],
-            self::DATA => $relatedSlots,
+            self::RELATIONSHIP_DATA => $relatedSlots,
         ];
 
         return $relationships;
@@ -92,10 +94,10 @@ class ConsultationBlock extends SchemaProvider
         $range = $resource->range;
 
         $relationships[self::REL_RANGE] = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getLinkForRange($range),
             ],
-            self::DATA => $includeData ? $range : $this->getMinimalRange($range),
+            self::RELATIONSHIP_DATA => $includeData ? $range : $this->getMinimalRange($range),
         ];
 
         return $relationships;
@@ -103,16 +105,12 @@ class ConsultationBlock extends SchemaProvider
 
     private function getLinkForRange(Range $range)
     {
-        if ($range instanceof \Course) {
-            return new Link("/courses/{$range->id}");
-        }
-
-        if ($range instanceof \Institute) {
-            return new Link("/institutes/{$range->id}");
-        }
-
-        if ($range instanceof \User) {
-            return new Link("/users/{$range->id}");
+        if (
+            $range instanceof \Course ||
+            $range instanceof \Institute ||
+            $range instanceof \User
+        ) {
+            return $this->createLinkToResource($range);
         }
 
         throw new \Exception('Unknown range type');
diff --git a/lib/classes/JsonApi/Schemas/ConsultationBooking.php b/lib/classes/JsonApi/Schemas/ConsultationBooking.php
index 7fbc3c9490a9ce3af1fbe7222aaa31b42acc6f81..318f306f74039e33dc0d8f3f2b33634c834239ef 100644
--- a/lib/classes/JsonApi/Schemas/ConsultationBooking.php
+++ b/lib/classes/JsonApi/Schemas/ConsultationBooking.php
@@ -2,7 +2,8 @@
 
 namespace JsonApi\Schemas;
 
-use Neomerx\JsonApi\Document\Link;
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
+use Neomerx\JsonApi\Schema\Link;
 
 class ConsultationBooking extends SchemaProvider
 {
@@ -10,14 +11,12 @@ class ConsultationBooking extends SchemaProvider
     const REL_SLOT = 'slot';
     const REL_USER = 'user';
 
-    protected $resourceType = self::TYPE;
-
-    public function getId($resource)
+    public function getId($resource): ?string
     {
         return $resource->id;
     }
 
-    public function getAttributes($resource)
+    public function getAttributes($resource, ContextInterface $context): iterable
     {
         $attributes = [
             'reason' => $resource->reason,
@@ -34,8 +33,11 @@ class ConsultationBooking extends SchemaProvider
      * spezifiziert werden.
      * {@inheritdoc}
      */
-    public function getRelationships($resource, $isPrimary, array $includeList)
+    public function getRelationships($resource, ContextInterface $context): iterable
     {
+        $isPrimary = $context->getPosition()->getLevel() === 0;
+        $includeList = $context->getIncludePaths();
+
         $shouldInclude = function ($key) use ($isPrimary, $includeList) {
             return $isPrimary && in_array($key, $includeList);
         };
@@ -54,10 +56,10 @@ class ConsultationBooking extends SchemaProvider
         $slot = $resource->slot;
 
         $relationships[self::REL_SLOT] = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_SLOT),
             ],
-            self::DATA => $includeData ? $slot : \ConsultationSlot::build(['id' => $slot->id], false),
+            self::RELATIONSHIP_DATA => $includeData ? $slot : \ConsultationSlot::build(['id' => $slot->id], false),
         ];
 
         return $relationships;
@@ -68,10 +70,10 @@ class ConsultationBooking extends SchemaProvider
         $user = $resource->user;
 
         $relationships[self::REL_USER] = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_USER),
             ],
-            self::DATA => $includeData ? $user : \User::build(['id' => $user->id], false),
+            self::RELATIONSHIP_DATA => $includeData ? $user : \User::build(['id' => $user->id], false),
         ];
 
         return $relationships;
diff --git a/lib/classes/JsonApi/Schemas/ConsultationSlot.php b/lib/classes/JsonApi/Schemas/ConsultationSlot.php
index 7f4f7b8c3bbc514eb84d0d092461f5651d965cbd..1adf912be57de1458faba0a92356e11988a8e934 100644
--- a/lib/classes/JsonApi/Schemas/ConsultationSlot.php
+++ b/lib/classes/JsonApi/Schemas/ConsultationSlot.php
@@ -2,7 +2,8 @@
 
 namespace JsonApi\Schemas;
 
-use Neomerx\JsonApi\Document\Link;
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
+use Neomerx\JsonApi\Schema\Link;
 
 class ConsultationSlot extends SchemaProvider
 {
@@ -10,14 +11,14 @@ class ConsultationSlot extends SchemaProvider
     const REL_BLOCK = 'block';
     const REL_BOOKINGS = 'bookings';
 
-    protected $resourceType = self::TYPE;
 
-    public function getId($resource)
+
+    public function getId($resource): ?string
     {
         return $resource->id;
     }
 
-    public function getAttributes($resource)
+    public function getAttributes($resource, ContextInterface $context): iterable
     {
         $attributes = [
             'note'      => $resource->note,
@@ -38,8 +39,11 @@ class ConsultationSlot extends SchemaProvider
      * spezifiziert werden.
      * {@inheritdoc}
      */
-    public function getRelationships($resource, $isPrimary, array $includeList)
+    public function getRelationships($resource, ContextInterface $context): iterable
     {
+        $isPrimary = $context->getPosition()->getLevel() === 0;
+        $includeList = $context->getIncludePaths();
+
         $shouldInclude = function ($key) use ($isPrimary, $includeList) {
             return $isPrimary && in_array($key, $includeList);
         };
@@ -63,10 +67,10 @@ class ConsultationSlot extends SchemaProvider
         $block = $resource->block;
 
         $relationships[self::REL_BLOCK] = [
-            self::LINKS => [
-                Link::RELATED => new Link("/consultation-block/{$block->id}"),
+            self::RELATIONSHIP_LINKS => [
+                Link::RELATED => $this->createLinkToResource($block),
             ],
-            self::DATA => $includeData ? $block : \ConsultationBlock::build(['id' => $block->id], false),
+            self::RELATIONSHIP_DATA => $includeData ? $block : \ConsultationBlock::build(['id' => $block->id], false),
         ];
 
         return $relationships;
@@ -83,10 +87,10 @@ class ConsultationSlot extends SchemaProvider
         }
 
         $relationships[self::REL_BOOKINGS] = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_BOOKINGS),
             ],
-            self::DATA => $relatedBookings,
+            self::RELATIONSHIP_DATA => $relatedBookings,
         ];
 
         return $relationships;
diff --git a/lib/classes/JsonApi/Schemas/ContentTermsOfUse.php b/lib/classes/JsonApi/Schemas/ContentTermsOfUse.php
index 651b408c3ea464dd6ba2e4371d79ef71770473c5..b827788e1fcc79b567ba47114b7a0196f9d130ae 100644
--- a/lib/classes/JsonApi/Schemas/ContentTermsOfUse.php
+++ b/lib/classes/JsonApi/Schemas/ContentTermsOfUse.php
@@ -2,18 +2,18 @@
 
 namespace JsonApi\Schemas;
 
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
+
 class ContentTermsOfUse extends SchemaProvider
 {
     const TYPE = 'terms-of-use';
 
-    protected $resourceType = self::TYPE;
-
-    public function getId($resource)
+    public function getId($resource): ?string
     {
         return $resource->id;
     }
 
-    public function getAttributes($resource)
+    public function getAttributes($resource, ContextInterface $context): iterable
     {
         return [
             'name' => (string) $resource['name'],
@@ -24,4 +24,9 @@ class ContentTermsOfUse extends SchemaProvider
             'chdate' => date('c', $resource['chdate']),
         ];
     }
+
+    public function getRelationships($user, ContextInterface $context): iterable
+    {
+        return [];
+    }
 }
diff --git a/lib/classes/JsonApi/Schemas/Course.php b/lib/classes/JsonApi/Schemas/Course.php
index 3aa6711e03dab0615a9cd8d8e64e3bfa5a5636f0..98c890916f49e6125e3c46b9f785a8a47744f8be 100644
--- a/lib/classes/JsonApi/Schemas/Course.php
+++ b/lib/classes/JsonApi/Schemas/Course.php
@@ -3,7 +3,8 @@
 namespace JsonApi\Schemas;
 
 use JsonApi\Routes\Files\Authority as FilesAuth;
-use Neomerx\JsonApi\Document\Link;
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
+use Neomerx\JsonApi\Schema\Link;
 
 class Course extends SchemaProvider
 {
@@ -26,14 +27,12 @@ class Course extends SchemaProvider
     const REL_STATUS_GROUPS = 'status-groups';
     const REL_WIKI_PAGES = 'wiki-pages';
 
-    protected $resourceType = self::TYPE;
-
-    public function getId($course)
+    public function getId($course): ?string
     {
         return $course->seminar_id;
     }
 
-    public function getAttributes($course)
+    public function getAttributes($course, ContextInterface $context): iterable
     {
         $stringOrNull = function ($item) {
             return trim($item) != '' ? (string) $item : null;
@@ -54,8 +53,12 @@ class Course extends SchemaProvider
         ];
     }
 
-    public function getRelationships($course, $isPrimary, array $includeList)
+    public function getRelationships($course, ContextInterface $context): iterable
     {
+        $isPrimary = $context->getPosition()->getLevel() === 0;
+        $includeList = $context->getIncludePaths();
+
+
         $relationships = [];
 
         $relationships[self::REL_INSTITUTE] = $this->getInstitute($course, in_array(self::REL_INSTITUTE, $includeList));
@@ -89,10 +92,10 @@ class Course extends SchemaProvider
     private function getInstitute(\Course $course, $shouldInclude)
     {
         return [
-            self::LINKS => [
-                Link::RELATED => new Link('/institutes/'.$course->institut_id),
+            self::RELATIONSHIP_LINKS => [
+                Link::RELATED => $this->createLinkToResource($course->home_institut),
             ],
-            self::DATA => $course->home_institut,
+            self::RELATIONSHIP_DATA => $course->home_institut,
         ];
     }
 
@@ -103,10 +106,10 @@ class Course extends SchemaProvider
         }
 
         return [
-            self::LINKS => [
-                Link::RELATED => new Link('/semesters/'.$semester->id),
+            self::RELATIONSHIP_LINKS => [
+                Link::RELATED => $this->createLinkToResource($semester),
             ],
-            self::DATA => $semester,
+            self::RELATIONSHIP_DATA => $semester,
         ];
     }
 
@@ -117,29 +120,29 @@ class Course extends SchemaProvider
         }
 
         return [
-            self::LINKS => [
-                Link::RELATED => new Link('/semesters/'.$semester->id),
+            self::RELATIONSHIP_LINKS => [
+                Link::RELATED => $this->createLinkToResource($semester),
             ],
-            self::DATA => $semester,
+            self::RELATIONSHIP_DATA => $semester,
         ];
     }
 
     private function getFilesRelationship(array $relationships, \Course $resource)
     {
-        $user = $this->getDiContainer()->get('studip-current-user');
+        $user = $this->currentUser;
 
         if ($user && FilesAuth::canShowFileArea($user, $resource)) {
             $filesLink = $this->getRelationshipRelatedLink($resource, self::REL_FILES);
 
             $relationships[self::REL_FILES] = [
-                self::LINKS => [
+                self::RELATIONSHIP_LINKS => [
                     Link::RELATED => $filesLink,
                 ],
             ];
 
             $foldersLink = $this->getRelationshipRelatedLink($resource, self::REL_FOLDERS);
             $relationships[self::REL_FOLDERS] = [
-                self::LINKS => [
+                self::RELATIONSHIP_LINKS => [
                     Link::RELATED => $foldersLink,
                 ],
             ];
@@ -157,7 +160,7 @@ class Course extends SchemaProvider
         $includeData
     ) {
         $relationships[self::REL_FORUM_CATEGORIES] = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($course, self::REL_FORUM_CATEGORIES)
             ],
         ];
@@ -174,7 +177,7 @@ class Course extends SchemaProvider
         $includeData
     ) {
         $relationships[self::REL_BLUBBER] = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($course, self::REL_BLUBBER),
             ],
         ];
@@ -191,7 +194,7 @@ class Course extends SchemaProvider
         $includeData
     ) {
         $relationships[self::REL_EVENTS] = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($course, self::REL_EVENTS)
             ],
         ];
@@ -210,7 +213,7 @@ class Course extends SchemaProvider
 
         if (\Feedback::isActivated($course->id)) {
             $relationships[self::REL_FEEDBACK] = [
-                self::LINKS => [
+                self::RELATIONSHIP_LINKS => [
                     Link::RELATED => $this->getRelationshipRelatedLink($course, self::REL_FEEDBACK)
                 ],
             ];
@@ -228,8 +231,8 @@ class Course extends SchemaProvider
         $includeData
     ) {
         $relationships[self::REL_MEMBERSHIPS] = [
-            self::SHOW_SELF => true,
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS_SELF => true,
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($course, self::REL_MEMBERSHIPS)
             ],
         ];
@@ -246,7 +249,7 @@ class Course extends SchemaProvider
         $includeData
     ) {
         $relationships[self::REL_NEWS] = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($course, self::REL_NEWS)
             ],
         ];
@@ -263,7 +266,7 @@ class Course extends SchemaProvider
         $includeData
     ) {
         $relationships[self::REL_WIKI_PAGES] = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($course, self::REL_WIKI_PAGES)
             ],
         ];
@@ -286,7 +289,7 @@ class Course extends SchemaProvider
         );
 
         $relationships[self::REL_PARTICIPATING_INSTITUTES] = [
-            self::DATA => $institutes
+            self::RELATIONSHIP_DATA => $institutes
         ];
 
         return $relationships;
@@ -301,7 +304,7 @@ class Course extends SchemaProvider
         $includeData
     ) {
         $relationships[self::REL_SEM_CLASS] = [
-            self::DATA => $course->getSemClass()
+            self::RELATIONSHIP_DATA => $course->getSemClass()
         ];
 
         return $relationships;
@@ -316,7 +319,7 @@ class Course extends SchemaProvider
         $includeData
     ) {
         $relationships[self::REL_SEM_TYPE] = [
-            self::DATA => $course->getSemType()
+            self::RELATIONSHIP_DATA => $course->getSemType()
         ];
 
         return $relationships;
@@ -328,13 +331,13 @@ class Course extends SchemaProvider
         $includeData
     ) {
         $relation = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_STATUS_GROUPS),
             ]
         ];
         if (in_array(self::REL_STATUS_GROUPS, $includeData)) {
             $related = \Statusgruppen::findBySeminar_id($resource->id);
-            $relation[self::DATA] = $related;
+            $relation[self::RELATIONSHIP_DATA] = $related;
         }
 
         return array_merge($relationships, [self::REL_STATUS_GROUPS => $relation]);
diff --git a/lib/classes/JsonApi/Schemas/CourseEvent.php b/lib/classes/JsonApi/Schemas/CourseEvent.php
index 470d86bbb0f9d6972852337d0600c9e3785672fd..d2ec52dd6b6673622387b9f8277ebed56cd0c867 100644
--- a/lib/classes/JsonApi/Schemas/CourseEvent.php
+++ b/lib/classes/JsonApi/Schemas/CourseEvent.php
@@ -2,21 +2,20 @@
 
 namespace JsonApi\Schemas;
 
-use Neomerx\JsonApi\Document\Link;
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
+use Neomerx\JsonApi\Schema\Link;
 
 class CourseEvent extends SchemaProvider
 {
     const TYPE = 'course-events';
     const REL_OWNER = 'owner';
 
-    protected $resourceType = self::TYPE;
-
-    public function getId($resource)
+    public function getId($resource): ?string
     {
         return $resource->id;
     }
 
-    public function getAttributes($resource)
+    public function getAttributes($resource, ContextInterface $context): iterable
     {
         return [
             'title' => $resource->title,
@@ -35,14 +34,17 @@ class CourseEvent extends SchemaProvider
     /**
      * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
-    public function getRelationships($resource, $isPrimary, array $includeList)
+    public function getRelationships($resource, ContextInterface $context): iterable
     {
+        $isPrimary = $context->getPosition()->getLevel() === 0;
+        $includeList = $context->getIncludePaths();
+
         $relationships = [];
 
         if ($owner = $resource->course) {
-            $link = $this->getSchemaContainer()->getSchema($owner)->getSelfSubLink($owner);
+            $link = $this->createLinkToResource($owner);
             $relationships = [
-                self::REL_OWNER => [self::LINKS => [Link::RELATED => $link], self::DATA => $owner],
+                self::REL_OWNER => [self::RELATIONSHIP_LINKS => [Link::RELATED => $link], self::RELATIONSHIP_DATA => $owner],
             ];
         }
 
diff --git a/lib/classes/JsonApi/Schemas/CourseMember.php b/lib/classes/JsonApi/Schemas/CourseMember.php
index 8882c84e5e13d3bd1853a99f5f58b122f33d697c..1997e8b7d02649173a68ae99823833f7dff0e1d2 100644
--- a/lib/classes/JsonApi/Schemas/CourseMember.php
+++ b/lib/classes/JsonApi/Schemas/CourseMember.php
@@ -4,7 +4,8 @@ namespace JsonApi\Schemas;
 
 use JsonApi\Routes\Courses\Authority as CourseAuthority;
 use JsonApi\Routes\CourseMemberships\Authority as MembershipAuthority;
-use Neomerx\JsonApi\Document\Link;
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
+use Neomerx\JsonApi\Schema\Link;
 
 class CourseMember extends SchemaProvider
 {
@@ -12,14 +13,12 @@ class CourseMember extends SchemaProvider
     const REL_COURSE = 'course';
     const REL_USER = 'user';
 
-    protected $resourceType = self::TYPE;
-
-    public function getId($membership)
+    public function getId($membership): ?string
     {
         return $membership->id;
     }
 
-    public function getAttributes($membership)
+    public function getAttributes($membership, ContextInterface $context): iterable
     {
         $attributes = [
             'permission' => $membership->status,
@@ -28,14 +27,13 @@ class CourseMember extends SchemaProvider
             'mkdate' => date('c', $membership->mkdate),
             'label' => $membership->label,
         ];
-        // TODO: "bind_calendar": "1",
 
-        if ($user = $this->getDiContainer()->get('studip-current-user')) {
-            if (MembershipAuthority::canIndexMembershipsOfUser($user, $membership->user)) {
+        if ($this->currentUser) {
+            if (MembershipAuthority::canIndexMembershipsOfUser($this->currentUser, $membership->user)) {
                 # TODO: $attributes['notification'] = (int) $membership->notification;
                 $attributes['visible'] = $membership->visible;
             }
-            if (CourseAuthority::canEditCourse($user, $membership->course)) {
+            if (CourseAuthority::canEditCourse($this->currentUser, $membership->course)) {
                 $attributes['comment'] = $membership->comment;
                 $attributes['visible'] = $membership->visible;
             }
@@ -44,23 +42,26 @@ class CourseMember extends SchemaProvider
         return $attributes;
     }
 
-    public function getRelationships($membership, $isPrimary, array $includeList)
+    public function getRelationships($membership, ContextInterface $context): iterable
     {
+        $isPrimary = $context->getPosition()->getLevel() === 0;
+        $includeList = $context->getIncludePaths();
+
         $relationships = [];
 
         if ($isPrimary) {
             $relationships[self::REL_COURSE] = [
-                self::LINKS => [
-                    Link::RELATED => new Link('/courses/'.$membership->seminar_id),
+                self::RELATIONSHIP_LINKS => [
+                    Link::RELATED => $this->createLinkToResource($membership->course)
                 ],
-                self::DATA => $membership->course,
+                self::RELATIONSHIP_DATA => $membership->course,
             ];
 
             $relationships[self::REL_USER] = [
-                self::LINKS => [
-                    Link::RELATED => new Link('/users/'.$membership->user_id),
+                self::RELATIONSHIP_LINKS => [
+                    Link::RELATED => $this->createLinkToResource($membership->user)
                 ],
-                self::DATA => $membership->user,
+                self::RELATIONSHIP_DATA => $membership->user,
             ];
         }
 
diff --git a/lib/classes/JsonApi/Schemas/Courseware/Block.php b/lib/classes/JsonApi/Schemas/Courseware/Block.php
index 23c4eae3cb7b26f31a202af5dfb696adfd12c552..89434d976f2a413112f9d6d378c22310f1e4c229 100755
--- a/lib/classes/JsonApi/Schemas/Courseware/Block.php
+++ b/lib/classes/JsonApi/Schemas/Courseware/Block.php
@@ -5,7 +5,8 @@ namespace JsonApi\Schemas\Courseware;
 use Courseware\UserDataField;
 use Courseware\UserProgress;
 use JsonApi\Schemas\SchemaProvider;
-use Neomerx\JsonApi\Document\Link;
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
+use Neomerx\JsonApi\Schema\Link;
 
 class Block extends SchemaProvider
 {
@@ -21,12 +22,10 @@ class Block extends SchemaProvider
     const REL_USERPROGRESS = 'user-progress';
     const REL_FILES = 'file-refs';
 
-    protected $resourceType = self::TYPE;
-
     /**
      * {@inheritdoc}
      */
-    public function getId($resource)
+    public function getId($resource): ?string
     {
         return $resource->id;
     }
@@ -34,7 +33,7 @@ class Block extends SchemaProvider
     /**
      * {@inheritdoc}
      */
-    public function getAttributes($resource)
+    public function getAttributes($resource, ContextInterface $context): iterable
     {
         return [
             'position' => (int) $resource['position'],
@@ -50,87 +49,82 @@ class Block extends SchemaProvider
     /**
      * {@inheritdoc}
      */
-    public function getRelationships($resource, $isPrimary, array $includeList)
+    public function getRelationships($resource, ContextInterface $context): iterable
     {
+        $isPrimary = $context->getPosition()->getLevel() === 0;
+        $includeList = $context->getIncludePaths();
+
         $relationships = [];
 
         $relationships[self::REL_COMMENTS] = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_COMMENTS),
             ],
-            self::DATA => $resource->comments,
+            self::RELATIONSHIP_DATA => $resource->comments,
         ];
 
         $relationships[self::REL_CONTAINER] = [
-            self::LINKS => [
-                Link::RELATED => $this->getSchemaContainer()
-                    ->getSchema($resource->container)
-                    ->getSelfSubLink($resource->container),
+            self::RELATIONSHIP_LINKS => [
+                Link::RELATED => $this->createLinkToResource($resource->container),
             ],
-            self::DATA => $resource->container,
+            self::RELATIONSHIP_DATA => $resource->container,
         ];
 
         $relationships[self::REL_OWNER] = [
-            self::LINKS => [
-                Link::RELATED => $this->getSchemaContainer()
-                    ->getSchema($resource->owner)
-                    ->getSelfSubLink($resource->owner),
+            self::RELATIONSHIP_LINKS => [
+                Link::RELATED => $this->createLinkToResource($resource->owner),
             ],
-            self::DATA => $resource->owner,
+            self::RELATIONSHIP_DATA => $resource->owner,
         ];
 
         $relationships[self::REL_EDITOR] = $resource['editor_id']
             ? [
-                self::LINKS => [
-                    Link::RELATED => $this->getSchemaContainer()
-                        ->getSchema($resource->editor)
-                        ->getSelfSubLink($resource->editor),
+                self::RELATIONSHIP_LINKS => [
+                    Link::RELATED => $this->createLinkToResource($resource->editor),
                 ],
-                self::DATA => $resource->editor,
+                self::RELATIONSHIP_DATA => $resource->editor,
             ]
-            : [self::DATA => null];
+            : [self::RELATIONSHIP_DATA => null];
 
 
         $relationships[self::REL_EDITBLOCKER] = $resource['edit_blocker_id']
             ? [
-                self::SHOW_SELF => true,
-                self::LINKS => [
-                    Link::RELATED => $this->getSchemaContainer()
-                        ->getSchema($resource->edit_blocker)
-                        ->getSelfSubLink($resource->edit_blocker),
+                self::RELATIONSHIP_LINKS_SELF => true,
+                self::RELATIONSHIP_LINKS => [
+                    Link::RELATED => $this->createLinkToResource($resource->edit_blocker),
                 ],
-                self::DATA => $resource->edit_blocker,
+                self::RELATIONSHIP_DATA => $resource->edit_blocker,
             ]
-            : [self::SHOW_SELF => true, self::DATA => null];
+            : [self::RELATIONSHIP_LINKS_SELF => true, self::RELATIONSHIP_DATA => null];
 
         $relationships[self::REL_FEEDBACK] = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_FEEDBACK),
             ],
         ];
 
-        $user = $this->getDiContainer()->get('studip-current-user');
+        $user = $this->currentUser;
         $userDataField = UserDataField::getUserDataField($user, $resource);
         $relationships[self::REL_USERDATAFIELD] = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_USERDATAFIELD),
             ],
-            self::DATA => $userDataField,
+            self::RELATIONSHIP_DATA => $userDataField,
         ];
 
         $userProgress = UserProgress::getUserProgress($user, $resource);
         $relationships[self::REL_USERPROGRESS] = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_USERPROGRESS),
             ],
-            self::DATA => $userProgress,
+            self::RELATIONSHIP_DATA => $userProgress,
         ];
 
         if ($resource->files) {
             $filesLink = $this->getRelationshipRelatedLink($resource, self::REL_FILES);
 
             $relationships[self::REL_FILES] = [
-                self::LINKS => [
+                self::RELATIONSHIP_LINKS => [
                     Link::RELATED => $filesLink,
                 ],
             ];
diff --git a/lib/classes/JsonApi/Schemas/Courseware/BlockComment.php b/lib/classes/JsonApi/Schemas/Courseware/BlockComment.php
index fa13d55b7f5ebd4ac8c75f330fc844fece45eaf1..c2abd5bf1b74c5bf77aefb885badaeaa06d09db8 100755
--- a/lib/classes/JsonApi/Schemas/Courseware/BlockComment.php
+++ b/lib/classes/JsonApi/Schemas/Courseware/BlockComment.php
@@ -3,7 +3,8 @@
 namespace JsonApi\Schemas\Courseware;
 
 use JsonApi\Schemas\SchemaProvider;
-use Neomerx\JsonApi\Document\Link;
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
+use Neomerx\JsonApi\Schema\Link;
 
 class BlockComment extends SchemaProvider
 {
@@ -12,12 +13,10 @@ class BlockComment extends SchemaProvider
     const REL_BLOCK = 'block';
     const REL_USER = 'user';
 
-    protected $resourceType = self::TYPE;
-
     /**
      * {@inheritdoc}
      */
-    public function getId($resource)
+    public function getId($resource): ?string
     {
         return $resource->id;
     }
@@ -25,7 +24,7 @@ class BlockComment extends SchemaProvider
     /**
      * {@inheritdoc}
      */
-    public function getAttributes($resource)
+    public function getAttributes($resource, ContextInterface $context): iterable
     {
         return [
             'comment' => (string) $resource['comment'],
@@ -37,26 +36,25 @@ class BlockComment extends SchemaProvider
     /**
      * {@inheritdoc}
      */
-    public function getRelationships($resource, $isPrimary, array $includeList)
+    public function getRelationships($resource, ContextInterface $context): iterable
     {
+        $isPrimary = $context->getPosition()->getLevel() === 0;
+        $includeList = $context->getIncludePaths();
+
         $relationships = [];
 
         $relationships[self::REL_BLOCK] = [
-            self::LINKS => [
-                Link::RELATED => $this->getSchemaContainer()
-                    ->getSchema($resource->block)
-                    ->getSelfSubLink($resource->block),
+            self::RELATIONSHIP_LINKS => [
+                Link::RELATED => $this->createLinkToResource($resource->block),
             ],
-            self::DATA => $resource->block,
+            self::RELATIONSHIP_DATA => $resource->block,
         ];
 
         $relationships[self::REL_USER] = [
-            self::LINKS => [
-                Link::RELATED => $this->getSchemaContainer()
-                    ->getSchema($resource->user)
-                    ->getSelfSubLink($resource->user),
+            self::RELATIONSHIP_LINKS => [
+                Link::RELATED => $this->createLinkToResource($resource->user),
             ],
-            self::DATA => $resource->user,
+            self::RELATIONSHIP_DATA => $resource->user,
         ];
 
         return $relationships;
diff --git a/lib/classes/JsonApi/Schemas/Courseware/BlockFeedback.php b/lib/classes/JsonApi/Schemas/Courseware/BlockFeedback.php
index 5e5b2c60544c78243f2d8d4dbc5cb469c5bf7db8..b5992b7c2e096257cd86d3634faf8a8be1822553 100755
--- a/lib/classes/JsonApi/Schemas/Courseware/BlockFeedback.php
+++ b/lib/classes/JsonApi/Schemas/Courseware/BlockFeedback.php
@@ -3,7 +3,8 @@
 namespace JsonApi\Schemas\Courseware;
 
 use JsonApi\Schemas\SchemaProvider;
-use Neomerx\JsonApi\Document\Link;
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
+use Neomerx\JsonApi\Schema\Link;
 
 class BlockFeedback extends SchemaProvider
 {
@@ -12,12 +13,10 @@ class BlockFeedback extends SchemaProvider
     const REL_USER = 'user';
     const REL_BLOCK = 'block';
 
-    protected $resourceType = self::TYPE;
-
     /**
      * {@inheritdoc}
      */
-    public function getId($resource)
+    public function getId($resource): ?string
     {
         return $resource->id;
     }
@@ -25,7 +24,7 @@ class BlockFeedback extends SchemaProvider
     /**
      * {@inheritdoc}
      */
-    public function getAttributes($resource)
+    public function getAttributes($resource, ContextInterface $context): iterable
     {
         return [
             'feedback' => (string) $resource['feedback'],
@@ -37,26 +36,25 @@ class BlockFeedback extends SchemaProvider
     /**
      * {@inheritdoc}
      */
-    public function getRelationships($resource, $isPrimary, array $includeList)
+    public function getRelationships($resource, ContextInterface $context): iterable
     {
+        $isPrimary = $context->getPosition()->getLevel() === 0;
+        $includeList = $context->getIncludePaths();
+
         $relationships = [];
 
         $relationships[self::REL_BLOCK] = [
-            self::LINKS => [
-                Link::RELATED => $this->getSchemaContainer()
-                    ->getSchema($resource->block)
-                    ->getSelfSubLink($resource->block),
+            self::RELATIONSHIP_LINKS => [
+                Link::RELATED => $this->createLinkToResource($resource->block),
             ],
-            self::DATA => $resource->block,
+            self::RELATIONSHIP_DATA => $resource->block,
         ];
 
         $relationships[self::REL_USER] = [
-            self::LINKS => [
-                Link::RELATED => $this->getSchemaContainer()
-                    ->getSchema($resource->user)
-                    ->getSelfSubLink($resource->user),
+            self::RELATIONSHIP_LINKS => [
+                Link::RELATED => $this->createLinkToResource($resource->user),
             ],
-            self::DATA => $resource->user,
+            self::RELATIONSHIP_DATA => $resource->user,
         ];
 
         return $relationships;
diff --git a/lib/classes/JsonApi/Schemas/Courseware/Container.php b/lib/classes/JsonApi/Schemas/Courseware/Container.php
index db68fc796824b21a5a51843b1b08552007a639c1..ea6ab96957bc6ce6ba6b43ba83a891826175cb6b 100755
--- a/lib/classes/JsonApi/Schemas/Courseware/Container.php
+++ b/lib/classes/JsonApi/Schemas/Courseware/Container.php
@@ -3,7 +3,8 @@
 namespace JsonApi\Schemas\Courseware;
 
 use JsonApi\Schemas\SchemaProvider;
-use Neomerx\JsonApi\Document\Link;
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
+use Neomerx\JsonApi\Schema\Link;
 
 class Container extends SchemaProvider
 {
@@ -15,12 +16,10 @@ class Container extends SchemaProvider
     const REL_EDITBLOCKER = 'edit-blocker';
     const REL_STRUCTURAL_ELEMENT = 'structural-element';
 
-    protected $resourceType = self::TYPE;
-
     /**
      * {@inheritdoc}
      */
-    public function getId($resource)
+    public function getId($resource): ?string
     {
         return $resource->id;
     }
@@ -28,7 +27,7 @@ class Container extends SchemaProvider
     /**
      * {@inheritdoc}
      */
-    public function getAttributes($resource)
+    public function getAttributes($resource, ContextInterface $context): iterable
     {
         return [
             'position' => (int) $resource['position'],
@@ -46,8 +45,11 @@ class Container extends SchemaProvider
     /**
      * {@inheritdoc}
      */
-    public function getRelationships($resource, $isPrimary, array $includeList)
+    public function getRelationships($resource, ContextInterface $context): iterable
     {
+        $isPrimary = $context->getPosition()->getLevel() === 0;
+        $includeList = $context->getIncludePaths();
+
         $relationships = [];
 
         $shouldInclude = function ($key) use ($includeList) {
@@ -58,48 +60,40 @@ class Container extends SchemaProvider
 
         $relationships[self::REL_OWNER] = $resource['owner_id']
             ? [
-                self::LINKS => [
-                    Link::RELATED => $this->getSchemaContainer()
-                        ->getSchema($resource->owner)
-                        ->getSelfSubLink($resource->owner),
+                self::RELATIONSHIP_LINKS => [
+                    Link::RELATED => $this->createLinkToResource($resource->owner),
                 ],
-                self::DATA => $resource->owner,
+                self::RELATIONSHIP_DATA => $resource->owner,
             ]
-            : [self::DATA => $resource->owner];
+            : [self::RELATIONSHIP_DATA => $resource->owner];
 
         $relationships[self::REL_EDITOR] = $resource['editor_id']
             ? [
-                self::LINKS => [
-                    Link::RELATED => $this->getSchemaContainer()
-                        ->getSchema($resource->editor)
-                        ->getSelfSubLink($resource->editor),
+                self::RELATIONSHIP_LINKS => [
+                    Link::RELATED => $this->createLinkToResource($resource->editor),
                 ],
-                self::DATA => $resource->editor,
+                self::RELATIONSHIP_DATA => $resource->editor,
             ]
-            : [self::DATA => null];
+            : [self::RELATIONSHIP_DATA => null];
 
         $relationships[self::REL_EDITBLOCKER] = $resource['edit_blocker_id']
             ? [
-                self::SHOW_SELF => true,
-                self::LINKS => [
-                    Link::RELATED => $this->getSchemaContainer()
-                        ->getSchema($resource->edit_blocker)
-                        ->getSelfSubLink($resource->edit_blocker),
+                self::RELATIONSHIP_LINKS_SELF => true,
+                self::RELATIONSHIP_LINKS => [
+                    Link::RELATED => $this->createLinkToResource($resource->edit_blocker),
                 ],
-                self::DATA => $resource->edit_blocker,
+                self::RELATIONSHIP_DATA => $resource->edit_blocker,
             ]
-            : [self::SHOW_SELF => true, self::DATA => null];
+            : [self::RELATIONSHIP_LINKS_SELF => true, self::RELATIONSHIP_DATA => null];
 
         $relationships[self::REL_STRUCTURAL_ELEMENT] = $resource['structural_element_id']
             ? [
-                self::LINKS => [
-                    Link::RELATED => $this->getSchemaContainer()
-                        ->getSchema($resource->structural_element)
-                        ->getSelfSubLink($resource->structural_element),
+                self::RELATIONSHIP_LINKS => [
+                    Link::RELATED => $this->createLinkToResource($resource->structural_element),
                 ],
-                self::DATA => $resource->structural_element,
+                self::RELATIONSHIP_DATA => $resource->structural_element,
             ]
-            : [self::DATA => null];
+            : [self::RELATIONSHIP_DATA => null];
 
         return $relationships;
     }
@@ -107,13 +101,13 @@ class Container extends SchemaProvider
     private function addBlocksRelationship(array $relationships, $resource, $includeData)
     {
         $relation = [
-            self::SHOW_SELF => true,
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS_SELF => true,
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_BLOCKS),
             ],
         ];
 
-        $relation[self::DATA] = $resource->blocks;
+        $relation[self::RELATIONSHIP_DATA] = $resource->blocks;
 
         $relationships[self::REL_BLOCKS] = $relation;
 
diff --git a/lib/classes/JsonApi/Schemas/Courseware/Instance.php b/lib/classes/JsonApi/Schemas/Courseware/Instance.php
index 55a70f6d6ec5c3beac2ea4bd2f53a7bd9b9c564a..2fba0e3995b0485ba7670cf25b5d7b846b1285cb 100755
--- a/lib/classes/JsonApi/Schemas/Courseware/Instance.php
+++ b/lib/classes/JsonApi/Schemas/Courseware/Instance.php
@@ -3,7 +3,8 @@
 namespace JsonApi\Schemas\Courseware;
 
 use JsonApi\Schemas\SchemaProvider;
-use Neomerx\JsonApi\Document\Link;
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
+use Neomerx\JsonApi\Schema\Link;
 
 class Instance extends SchemaProvider
 {
@@ -12,12 +13,10 @@ class Instance extends SchemaProvider
     const REL_BOOKMARKS = 'bookmarks';
     const REL_ROOT = 'root';
 
-    protected $resourceType = self::TYPE;
-
     /**
      * {@inheritdoc}
      */
-    public function getId($resource)
+    public function getId($resource): ?string
     {
         $root = $resource->getRoot();
 
@@ -27,9 +26,9 @@ class Instance extends SchemaProvider
     /**
      * {@inheritdoc}
      */
-    public function getAttributes($resource)
+    public function getAttributes($resource, ContextInterface $context): iterable
     {
-        $user = $this->getDiContainer()->get('studip-current-user');
+        $user = $this->currentUser;
 
         return [
             'block-types' => array_map([$this, 'mapBlockType'], $resource->getBlockTypes()),
@@ -70,26 +69,27 @@ class Instance extends SchemaProvider
     /**
      * {@inheritdoc}
      */
-    public function getRelationships($resource, $isPrimary, array $includeList)
+    public function getRelationships($resource, ContextInterface $context): iterable
     {
+        $isPrimary = $context->getPosition()->getLevel() === 0;
+        $includeList = $context->getIncludePaths();
+
         $relationships = [];
 
-        $user = $this->getDiContainer()->get('studip-current-user');
+        $user = $this->currentUser;
         $relationships[self::REL_BOOKMARKS] = [
-            self::SHOW_SELF => true,
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS_SELF => true,
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_BOOKMARKS),
             ],
-            self::DATA => $resource->getUsersBookmarks($user),
+            self::RELATIONSHIP_DATA => $resource->getUsersBookmarks($user),
         ];
 
         $relationships[self::REL_ROOT] = [
-            self::LINKS => [
-                Link::RELATED => $this->getSchemaContainer()
-                    ->getSchema($resource->getRoot())
-                    ->getSelfSubLink($resource->getRoot()),
+            self::RELATIONSHIP_LINKS => [
+                Link::RELATED => $this->createLinkToResource($resource->getRoot()),
             ],
-            self::DATA => $resource->getRoot(),
+            self::RELATIONSHIP_DATA => $resource->getRoot(),
         ];
 
         return $relationships;
diff --git a/lib/classes/JsonApi/Schemas/Courseware/StructuralElement.php b/lib/classes/JsonApi/Schemas/Courseware/StructuralElement.php
index 6089b47bc6e7922f8752d9e678a623c15c2a9659..ed3e32d423e01ccb3983d6d6d1abf07666db1198 100755
--- a/lib/classes/JsonApi/Schemas/Courseware/StructuralElement.php
+++ b/lib/classes/JsonApi/Schemas/Courseware/StructuralElement.php
@@ -3,7 +3,8 @@
 namespace JsonApi\Schemas\Courseware;
 
 use JsonApi\Schemas\SchemaProvider;
-use Neomerx\JsonApi\Document\Link;
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
+use Neomerx\JsonApi\Schema\Link;
 
 class StructuralElement extends SchemaProvider
 {
@@ -21,12 +22,10 @@ class StructuralElement extends SchemaProvider
     const REL_PARENT = 'parent';
     const REL_USER = 'user';
 
-    protected $resourceType = self::TYPE;
-
     /**
      * {@inheritdoc}
      */
-    public function getId($resource)
+    public function getId($resource): ?string
     {
         return $resource->id;
     }
@@ -34,9 +33,9 @@ class StructuralElement extends SchemaProvider
     /**
      * {@inheritdoc}
      */
-    public function getAttributes($resource)
+    public function getAttributes($resource, ContextInterface $context): iterable
     {
-        $user = $this->getDiContainer()->get('studip-current-user');
+        $user = $this->currentUser;
 
         return [
             'position' => (int) $resource['position'],
@@ -65,8 +64,11 @@ class StructuralElement extends SchemaProvider
      * @param bool                                 $isPrimary
      * @param array                                $includeList
      */
-    public function getRelationships($resource, $isPrimary, array $includeList)
+    public function getRelationships($resource, ContextInterface $context): iterable
     {
+        $isPrimary = $context->getPosition()->getLevel() === 0;
+        $includeList = $context->getIncludePaths();
+
         $relationships = [];
 
         $shouldInclude = function ($key) use ($includeList) {
@@ -74,86 +76,74 @@ class StructuralElement extends SchemaProvider
         };
 
         $relationships[self::REL_CHILDREN] = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_CHILDREN),
             ],
-            self::DATA => $resource->children,
+            self::RELATIONSHIP_DATA => $resource->children,
         ];
 
         $relationships[self::REL_CONTAINERS] = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_CONTAINERS),
             ],
-            self::DATA => $resource->containers,
+            self::RELATIONSHIP_DATA => $resource->containers,
         ];
 
         if ($resource->course) {
             $relationships[self::REL_COURSE] = [
-                self::LINKS => [
-                    Link::RELATED => $this->getSchemaContainer()
-                        ->getSchema($resource->course)
-                        ->getSelfSubLink($resource->course),
+                self::RELATIONSHIP_LINKS => [
+                    Link::RELATED => $this->createLinkToResource($resource->course),
                 ],
-                self::DATA => $resource->course,
+                self::RELATIONSHIP_DATA => $resource->course,
             ];
         }
 
         if ($resource->user) {
             $relationships[self::REL_USER] = [
-                self::LINKS => [
-                    Link::RELATED => $this->getSchemaContainer()
-                        ->getSchema($resource->user)
-                        ->getSelfSubLink($resource->user),
+                self::RELATIONSHIP_LINKS => [
+                    Link::RELATED => $this->createLinkToResource($resource->user),
                 ],
-                self::DATA => $resource->user,
+                self::RELATIONSHIP_DATA => $resource->user,
             ];
         }
 
         $relationships[self::REL_OWNER] = $resource['owner_id']
             ? [
-                self::LINKS => [
-                    Link::RELATED => $this->getSchemaContainer()
-                        ->getSchema($resource->owner)
-                        ->getSelfSubLink($resource->owner),
+                self::RELATIONSHIP_LINKS => [
+                    Link::RELATED => $this->createLinkToResource($resource->owner),
                 ],
-                self::DATA => $resource->owner,
+                self::RELATIONSHIP_DATA => $resource->owner,
             ]
-            : [self::DATA => null];
+            : [self::RELATIONSHIP_DATA => null];
 
         $relationships[self::REL_EDITOR] = $resource['editor_id']
             ? [
-                self::LINKS => [
-                    Link::RELATED => $this->getSchemaContainer()
-                        ->getSchema($resource->editor)
-                        ->getSelfSubLink($resource->editor),
+                self::RELATIONSHIP_LINKS => [
+                    Link::RELATED => $this->createLinkToResource($resource->editor),
                 ],
-                self::DATA => $resource->editor,
+                self::RELATIONSHIP_DATA => $resource->editor,
             ]
-            : [self::DATA => $resource->editor];
+            : [self::RELATIONSHIP_DATA => $resource->editor];
 
         $relationships[self::REL_EDITBLOCKER] = $resource['edit_blocker_id']
             ? [
-                self::SHOW_SELF => true,
-                self::LINKS => [
-                    Link::RELATED => $this->getSchemaContainer()
-                        ->getSchema($resource->edit_blocker)
-                        ->getSelfSubLink($resource->edit_blocker),
+                self::RELATIONSHIP_LINKS_SELF => true,
+                self::RELATIONSHIP_LINKS => [
+                    Link::RELATED => $this->createLinkToResource($resource->edit_blocker),
                 ],
-                self::DATA => $resource->edit_blocker,
+                self::RELATIONSHIP_DATA => $resource->edit_blocker,
             ]
-            : [self::SHOW_SELF => true, self::DATA => null];
+            : [self::RELATIONSHIP_LINKS_SELF => true, self::RELATIONSHIP_DATA => null];
 
         $relationships[self::REL_PARENT] = $resource->parent_id
             ? [
-                self::LINKS => [
-                    Link::RELATED => $this->getSchemaContainer()
-                        ->getSchema($resource->parent)
-                        ->getSelfSubLink($resource->parent),
+                self::RELATIONSHIP_LINKS => [
+                    Link::RELATED => $this->createLinkToResource($resource->parent),
                 ],
 
-                self::DATA => $resource->parent,
+                self::RELATIONSHIP_DATA => $resource->parent,
             ]
-            : [self::DATA => null];
+            : [self::RELATIONSHIP_DATA => null];
 
         $relationships = $this->addAncestorsRelationship(
             $relationships,
@@ -174,14 +164,14 @@ class StructuralElement extends SchemaProvider
     private function addAncestorsRelationship(array $relationships, $resource, $includeData)
     {
         $relation = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_ANCESTORS),
             ],
         ];
 
         if ($includeData) {
             $related = $resource->findAncestors();
-            $relation[self::DATA] = $related;
+            $relation[self::RELATIONSHIP_DATA] = $related;
         }
 
         $relationships[self::REL_ANCESTORS] = $relation;
@@ -192,14 +182,14 @@ class StructuralElement extends SchemaProvider
     private function addDescendantsRelationship(array $relationships, $resource, $includeData)
     {
         $relation = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_DESCENDANTS),
             ],
         ];
 
         if ($includeData) {
             $related = $resource->findDescendants();
-            $relation[self::DATA] = $related;
+            $relation[self::RELATIONSHIP_DATA] = $related;
         }
 
         $relationships[self::REL_DESCENDANTS] = $relation;
@@ -210,11 +200,11 @@ class StructuralElement extends SchemaProvider
     private function addImageRelationship(array $relationships, $resource, $includeData)
     {
         $relation = [
-            self::DATA => $resource->image ?: null,
+            self::RELATIONSHIP_DATA => $resource->image ?: null,
         ];
 
         if ($resource->image) {
-            $relation[self::META] = [
+            $relation[self::RELATIONSHIP_META] = [
                 'download-url' => $resource->image->getFileType()->getDownloadURL(),
             ];
         }
diff --git a/lib/classes/JsonApi/Schemas/Courseware/UserDataField.php b/lib/classes/JsonApi/Schemas/Courseware/UserDataField.php
index d42a8007aa914c4eefbb4ce19a5e8485fd08cf6b..810e870ce2c80d4a635d9d937082032e3c210755 100755
--- a/lib/classes/JsonApi/Schemas/Courseware/UserDataField.php
+++ b/lib/classes/JsonApi/Schemas/Courseware/UserDataField.php
@@ -3,7 +3,8 @@
 namespace JsonApi\Schemas\Courseware;
 
 use JsonApi\Schemas\SchemaProvider;
-use Neomerx\JsonApi\Document\Link;
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
+use Neomerx\JsonApi\Schema\Link;
 
 class UserDataField extends SchemaProvider
 {
@@ -12,12 +13,10 @@ class UserDataField extends SchemaProvider
     const REL_BLOCK = 'block';
     const REL_USER = 'user';
 
-    protected $resourceType = self::TYPE;
-
     /**
      * {@inheritdoc}
      */
-    public function getId($resource)
+    public function getId($resource): ?string
     {
         return $resource->id;
     }
@@ -25,7 +24,7 @@ class UserDataField extends SchemaProvider
     /**
      * {@inheritdoc}
      */
-    public function getAttributes($resource)
+    public function getAttributes($resource, ContextInterface $context): iterable
     {
         return [
             'payload' => $resource['payload']->getIterator(),
@@ -37,26 +36,25 @@ class UserDataField extends SchemaProvider
     /**
      * {@inheritdoc}
      */
-    public function getRelationships($resource, $isPrimary, array $includeList)
+    public function getRelationships($resource, ContextInterface $context): iterable
     {
+        $isPrimary = $context->getPosition()->getLevel() === 0;
+        $includeList = $context->getIncludePaths();
+
         $relationships = [];
 
         $relationships[self::REL_BLOCK] = [
-            self::LINKS => [
-                Link::RELATED => $this->getSchemaContainer()
-                    ->getSchema($resource->block)
-                    ->getSelfSubLink($resource->block),
+            self::RELATIONSHIP_LINKS => [
+                Link::RELATED => $this->createLinkToResource($resource->block),
             ],
-            self::DATA => $resource->block,
+            self::RELATIONSHIP_DATA => $resource->block,
         ];
 
         $relationships[self::REL_USER] = [
-            self::LINKS => [
-                Link::RELATED => $this->getSchemaContainer()
-                    ->getSchema($resource->user)
-                    ->getSelfSubLink($resource->user),
+            self::RELATIONSHIP_LINKS => [
+                Link::RELATED => $this->createLinkToResource($resource->user),
             ],
-            self::DATA => $resource->user,
+            self::RELATIONSHIP_DATA => $resource->user,
         ];
 
         return $relationships;
diff --git a/lib/classes/JsonApi/Schemas/Courseware/UserProgress.php b/lib/classes/JsonApi/Schemas/Courseware/UserProgress.php
index 6b8629d1016ddcb611461cb9596105d0dc5221ca..5d6e68a5bfea4d798e114ba6da6d10723a508d74 100755
--- a/lib/classes/JsonApi/Schemas/Courseware/UserProgress.php
+++ b/lib/classes/JsonApi/Schemas/Courseware/UserProgress.php
@@ -3,7 +3,8 @@
 namespace JsonApi\Schemas\Courseware;
 
 use JsonApi\Schemas\SchemaProvider;
-use Neomerx\JsonApi\Document\Link;
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
+use Neomerx\JsonApi\Schema\Link;
 
 class UserProgress extends SchemaProvider
 {
@@ -12,12 +13,10 @@ class UserProgress extends SchemaProvider
     const REL_BLOCK = 'block';
     const REL_USER = 'user';
 
-    protected $resourceType = self::TYPE;
-
     /**
      * {@inheritdoc}
      */
-    public function getId($resource)
+    public function getId($resource): ?string
     {
         return $resource->id;
     }
@@ -25,7 +24,7 @@ class UserProgress extends SchemaProvider
     /**
      * {@inheritdoc}
      */
-    public function getAttributes($resource)
+    public function getAttributes($resource, ContextInterface $context): iterable
     {
         return [
             'grade' => (float) $resource['grade'],
@@ -37,26 +36,25 @@ class UserProgress extends SchemaProvider
     /**
      * {@inheritdoc}
      */
-    public function getRelationships($resource, $isPrimary, array $includeList)
+    public function getRelationships($resource, ContextInterface $context): iterable
     {
+        $isPrimary = $context->getPosition()->getLevel() === 0;
+        $includeList = $context->getIncludePaths();
+
         $relationships = [];
 
         $relationships[self::REL_BLOCK] = [
-            self::LINKS => [
-                Link::RELATED => $this->getSchemaContainer()
-                    ->getSchema($resource->block)
-                    ->getSelfSubLink($resource->block),
+            self::RELATIONSHIP_LINKS => [
+                Link::RELATED => $this->createLinkToResource($resource->block),
             ],
-            self::DATA => $resource->block,
+            self::RELATIONSHIP_DATA => $resource->block,
         ];
 
         $relationships[self::REL_USER] = [
-            self::LINKS => [
-                Link::RELATED => $this->getSchemaContainer()
-                    ->getSchema($resource->user)
-                    ->getSelfSubLink($resource->user),
+            self::RELATIONSHIP_LINKS => [
+                Link::RELATED => $this->createLinkToResource($resource->user),
             ],
-            self::DATA => $resource->user,
+            self::RELATIONSHIP_DATA => $resource->user,
         ];
 
         return $relationships;
diff --git a/lib/classes/JsonApi/Schemas/FeedbackElement.php b/lib/classes/JsonApi/Schemas/FeedbackElement.php
index b8a0f786ce25a99c975134a1e75f26980949a8a6..0709611687203ddb5be9d25a6a9c9aa2de276bf2 100644
--- a/lib/classes/JsonApi/Schemas/FeedbackElement.php
+++ b/lib/classes/JsonApi/Schemas/FeedbackElement.php
@@ -2,7 +2,8 @@
 
 namespace JsonApi\Schemas;
 
-use Neomerx\JsonApi\Document\Link;
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
+use Neomerx\JsonApi\Schema\Link;
 
 class FeedbackElement extends SchemaProvider
 {
@@ -12,14 +13,14 @@ class FeedbackElement extends SchemaProvider
     const REL_ENTRIES = 'entries';
     const REL_RANGE = 'range';
 
-    protected $resourceType = self::TYPE;
 
-    public function getId($resource)
+
+    public function getId($resource): ?string
     {
         return (int) $resource->id;
     }
 
-    public function getAttributes($resource)
+    public function getAttributes($resource, ContextInterface $context): iterable
     {
         $attributes = [
             'question' => (string) $resource['question'],
@@ -38,7 +39,15 @@ class FeedbackElement extends SchemaProvider
     /**
      * @inheritdoc
      */
-    public function getPrimaryMeta($resource)
+    public function hasResourceMeta($resource): bool
+    {
+        return true;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function getResourceMeta($resource)
     {
         return $resource['mode'] === 0
             ? null
@@ -55,8 +64,11 @@ class FeedbackElement extends SchemaProvider
      * spezifiziert werden.
      * {@inheritdoc}
      */
-    public function getRelationships($resource, $isPrimary, array $includeList)
+    public function getRelationships($resource, ContextInterface $context): iterable
     {
+        $isPrimary = $context->getPosition()->getLevel() === 0;
+        $includeList = $context->getIncludePaths();
+
         $shouldInclude = function ($key) use ($isPrimary, $includeList) {
             return $isPrimary && in_array($key, $includeList);
         };
@@ -76,12 +88,10 @@ class FeedbackElement extends SchemaProvider
         $userId = $resource['user_id'];
         $related = $includeData ? \User::find($userId) : \User::build(['id' => $userId], false);
         $relationships[self::REL_AUTHOR] = [
-            self::LINKS => [
-                Link::RELATED => $this->getSchemaContainer()
-                    ->getSchema($related)
-                    ->getSelfSubLink($related)
+            self::RELATIONSHIP_LINKS => [
+                Link::RELATED => $this->createLinkToResource($related)
             ],
-            self::DATA => $related
+            self::RELATIONSHIP_DATA => $related
         ];
 
         return $relationships;
@@ -92,53 +102,47 @@ class FeedbackElement extends SchemaProvider
         if ($courseId = $resource['course_id']) {
             $related = $includeData ? \Course::find($courseId) : \Course::build(['id' => $courseId], false);
             $relationships[self::REL_COURSE] = [
-                self::LINKS => [
-                    Link::RELATED => $this->getSchemaContainer()
-                        ->getSchema($related)
-                        ->getSelfSubLink($related)
+                self::RELATIONSHIP_LINKS => [
+                    Link::RELATED => $this->createLinkToResource($related)
                 ],
-                self::DATA => $related
+                self::RELATIONSHIP_DATA => $related
             ];
         }
 
         return $relationships;
     }
 
-    private function getEntriesRelationship(array $relationships, \FeedbackElement $resource, $includeData): array
+    private function getEntriesRelationship(array $relationships, \FeedbackElement $resource, bool $includeData): array
     {
         $relationships[self::REL_ENTRIES] = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_ENTRIES)
             ],
-            self::DATA => $resource->entries
+            self::RELATIONSHIP_DATA => $resource->entries
         ];
 
         return $relationships;
     }
 
-    private function getRangeRelationship(array $relationships, \FeedbackElement $resource, $includeData): array
+    private function getRangeRelationship(array $relationships, \FeedbackElement $resource, bool $includeData): array
     {
         $rangeType = $resource['range_type'];
-        $rangeSchema = null;
+        $link = null;
 
         try {
-            $rangeSchema = $this->getSchemaContainer()->getSchemaByType($rangeType);
-        } catch (\InvalidArgumentException $e) {
-        }
-
-        if (
-            isset($rangeSchema) &&
-            is_subclass_of($rangeType, \FeedbackRange::class) &&
-            is_subclass_of($rangeType, \SimpleORMap::class)
-        ) {
-            if ($range = $rangeType::find($resource['range_id'])) {
-                $link = $rangeSchema->getSelfSubLink($range);
-
-                $relationships[self::REL_RANGE] = [
-                    self::LINKS => [Link::RELATED => $link],
-                    self::DATA => $range
-                ];
+            $link = $this->createLinkToResource($rangeType);
+            if (
+                is_subclass_of($rangeType, \FeedbackRange::class) &&
+                is_subclass_of($rangeType, \SimpleORMap::class)
+            ) {
+                if ($range = $rangeType::find($resource['range_id'])) {
+                    $relationships[self::REL_RANGE] = [
+                        self::RELATIONSHIP_LINKS => [Link::RELATED => $link],
+                        self::RELATIONSHIP_DATA => $range
+                    ];
+                }
             }
+        } catch (\InvalidArgumentException $e) {
         }
 
         return $relationships;
diff --git a/lib/classes/JsonApi/Schemas/FeedbackEntry.php b/lib/classes/JsonApi/Schemas/FeedbackEntry.php
index 5583952fdb62bc25c0a1726677c417a790e16f9e..e919c13f5fa02a32d76571a839437323bde5b43b 100644
--- a/lib/classes/JsonApi/Schemas/FeedbackEntry.php
+++ b/lib/classes/JsonApi/Schemas/FeedbackEntry.php
@@ -2,7 +2,8 @@
 
 namespace JsonApi\Schemas;
 
-use Neomerx\JsonApi\Document\Link;
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
+use Neomerx\JsonApi\Schema\Link;
 
 class FeedbackEntry extends SchemaProvider
 {
@@ -10,20 +11,18 @@ class FeedbackEntry extends SchemaProvider
     const REL_AUTHOR = 'author';
     const REL_FEEDBACK = 'feedback-element';
 
-    protected $resourceType = self::TYPE;
-
-    public function getId($resource)
+    public function getId($resource): ?string
     {
         return $resource->id;
     }
 
-    public function getAttributes($resource)
+    public function getAttributes($resource, ContextInterface $context): iterable
     {
         $attributes = [
             'comment' => (string) $resource['comment'],
-            'rating' => $resource->feedback->mode === 0 ? null : $resource['rating'],
+            'rating' => 0 === $resource->feedback->mode ? null : $resource['rating'],
             'mkdate' => date('c', $resource['mkdate']),
-            'chdate' => date('c', $resource['chdate'])
+            'chdate' => date('c', $resource['chdate']),
         ];
 
         return $attributes;
@@ -32,10 +31,14 @@ class FeedbackEntry extends SchemaProvider
     /**
      * In dieser Methode können Relationships zu anderen Objekten
      * spezifiziert werden.
+     *
      * {@inheritdoc}
      */
-    public function getRelationships($resource, $isPrimary, array $includeList)
+    public function getRelationships($resource, ContextInterface $context): iterable
     {
+        $isPrimary = $context->getPosition()->getLevel() === 0;
+        $includeList = $context->getIncludePaths();
+
         $shouldInclude = function ($key) use ($isPrimary, $includeList) {
             return $isPrimary && in_array($key, $includeList);
         };
@@ -57,12 +60,10 @@ class FeedbackEntry extends SchemaProvider
         $userId = $resource['user_id'];
         $related = $includeData ? \User::find($userId) : \User::build(['id' => $userId], false);
         $relationships[self::REL_AUTHOR] = [
-            self::LINKS => [
-                Link::RELATED => $this->getSchemaContainer()
-                    ->getSchema($related)
-                    ->getSelfSubLink($related)
+            self::RELATIONSHIP_LINKS => [
+                Link::RELATED => $this->createLinkToResource($related),
             ],
-            self::DATA => $related
+            self::RELATIONSHIP_DATA => $related,
         ];
 
         return $relationships;
@@ -78,12 +79,10 @@ class FeedbackEntry extends SchemaProvider
             : \FeedbackElement::build(['id' => $resource->feedback_id], false);
 
         $relationships[self::REL_FEEDBACK] = [
-            self::LINKS => [
-                Link::RELATED => $this->getSchemaContainer()
-                    ->getSchema($related)
-                    ->getSelfSubLink($related)
+            self::RELATIONSHIP_LINKS => [
+                Link::RELATED => $this->createLinkToResource($related),
             ],
-            self::DATA => $related
+            self::RELATIONSHIP_DATA => $related,
         ];
 
         return $relationships;
diff --git a/lib/classes/JsonApi/Schemas/File.php b/lib/classes/JsonApi/Schemas/File.php
index 45bb2aac8e0e69f1cc5be40c7779821c631a2990..8293b5fc1cd75554c4bc526f0f68844af2b7c075 100644
--- a/lib/classes/JsonApi/Schemas/File.php
+++ b/lib/classes/JsonApi/Schemas/File.php
@@ -3,7 +3,8 @@
 namespace JsonApi\Schemas;
 
 use JsonApi\Routes\Files\Authority as FilesAuthority;
-use Neomerx\JsonApi\Document\Link;
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
+use Neomerx\JsonApi\Schema\Link;
 
 class File extends SchemaProvider
 {
@@ -12,19 +13,12 @@ class File extends SchemaProvider
     const REL_FILE_REFS = 'file-refs';
     const REL_OWNER = 'owner';
 
-    protected $resourceType = self::TYPE;
-
-    public function getId($resource)
+    public function getId($resource): ?string
     {
         return $resource->getId();
     }
 
-    public function getInclusionMeta($resource)
-    {
-        return $this->getPrimaryMeta($resource);
-    }
-
-    public function getAttributes($resource)
+    public function getAttributes($resource, ContextInterface $context): iterable
     {
         $attributes = [
             'name' => $resource['name'],
@@ -36,8 +30,7 @@ class File extends SchemaProvider
         ];
 
         if ($resource['metadata']['url']) {
-            $user = $this->getDiContainer()->get('studip-current-user');
-            if (FilesAuthority::canUpdateFile($user, $resource)) {
+            if (FilesAuthority::canUpdateFile($this->currentUser, $resource)) {
                 $attributes['url'] = $resource['metadata']['url'];
             }
         }
@@ -48,8 +41,11 @@ class File extends SchemaProvider
     /**
      * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
-    public function getRelationships($resource, $isPrimary, array $includeList)
+    public function getRelationships($resource, ContextInterface $context): iterable
     {
+        $isPrimary = $context->getPosition()->getLevel() === 0;
+        $includeList = $context->getIncludePaths();
+
         $relationships = [];
 
         if ($isPrimary) {
@@ -65,8 +61,8 @@ class File extends SchemaProvider
         $refs = $resource->refs;
 
         $relationships[self::REL_FILE_REFS] = [
-            self::SHOW_SELF => true,
-            self::DATA => $refs,
+            self::RELATIONSHIP_LINKS_SELF => true,
+            self::RELATIONSHIP_DATA => $refs,
         ];
 
         return $relationships;
@@ -76,11 +72,10 @@ class File extends SchemaProvider
     {
         if ($resource->user_id) {
             $relationships[self::REL_OWNER] = [
-            self::LINKS => [
-            Link::RELATED => $this->getSchemaContainer()
-                          ->getSchema($resource->owner)->getSelfSubLink($resource->owner),
-                          ],
-                        ];
+                self::RELATIONSHIP_LINKS => [
+                    Link::RELATED => $this->createLinkToResource($resource->owner),
+                ],
+            ];
         }
 
         return $relationships;
diff --git a/lib/classes/JsonApi/Schemas/FileRef.php b/lib/classes/JsonApi/Schemas/FileRef.php
index e92854491cce91c84b33634a7d5465694ec5da1d..5da46f6d475fc566540bc7fb586cb40fc25dd44e 100644
--- a/lib/classes/JsonApi/Schemas/FileRef.php
+++ b/lib/classes/JsonApi/Schemas/FileRef.php
@@ -2,8 +2,8 @@
 
 namespace JsonApi\Schemas;
 
-use JsonApi\Providers\JsonApiConfig as C;
-use Neomerx\JsonApi\Document\Link;
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
+use Neomerx\JsonApi\Schema\Link;
 
 class FileRef extends SchemaProvider
 {
@@ -18,24 +18,27 @@ class FileRef extends SchemaProvider
 
     const META_CONTENT = 'content';
 
-    protected $resourceType = self::TYPE;
-
-    public function getId($resource)
+    public function getId($resource): ?string
     {
         return $resource->getId();
     }
 
-    public function getPrimaryMeta($resource)
+    /**
+     * @inheritdoc
+     */
+    public function hasResourceMeta($resource): bool
     {
-        $link = $this->getDiContainer()->get(C::JSON_URL_PREFIX)
-              .$this->getRelationshipRelatedLink($resource, self::META_CONTENT)->getSubHref();
+        return true;
+    }
 
+    public function getResourceMeta($resource)
+    {
         return [
-            'download-url' => $link,
+            'download-url' => $resource->getDownloadURL(),
         ];
     }
 
-    public function getAttributes($resource)
+    public function getAttributes($resource, ContextInterface $context): iterable
     {
         $attributes = [
             'name' => $resource['name'],
@@ -50,7 +53,7 @@ class FileRef extends SchemaProvider
             'mime-type' => $resource->file->mime_type
         ];
 
-        $user = $this->getDiContainer()->get('studip-current-user');
+        $user = $this->currentUser;
         if ($folder = $resource->getFolderType()) {
             $filetype = $resource->getFileType();
             $attributes = array_merge(
@@ -70,8 +73,11 @@ class FileRef extends SchemaProvider
     /**
      * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
-    public function getRelationships($resource, $isPrimary, array $includeList)
+    public function getRelationships($resource, ContextInterface $context): iterable
     {
+        $isPrimary = $context->getPosition()->getLevel() === 0;
+        $includeList = $context->getIncludePaths();
+
         $relationships = [];
 
         $relationships = $this->getFeedbackRelationship($relationships, $resource);
@@ -94,7 +100,7 @@ class FileRef extends SchemaProvider
         if ($folder = $resource->getFolderType()) {
             if ($folder->range_id && $folder->range_type === 'course' && \Feedback::isActivated($folder->range_id)) {
                 $relationships[self::REL_FEEDBACK] = [
-                    self::LINKS => [
+                    self::RELATIONSHIP_LINKS => [
                         Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_FEEDBACK)
                     ],
                 ];
@@ -108,11 +114,9 @@ class FileRef extends SchemaProvider
     {
         if ($resource->file) {
             $relationships[self::REL_FILE] = [
-                self::DATA => $resource->file,
-                self::LINKS => [
-                    Link::RELATED => $this->getSchemaContainer()
-                    ->getSchema($resource->file)
-                    ->getSelfSubLink($resource->file),
+                self::RELATIONSHIP_DATA => $resource->file,
+                self::RELATIONSHIP_LINKS => [
+                    Link::RELATED => $this->createLinkToResource($resource->file),
                 ],
             ];
         }
@@ -123,16 +127,15 @@ class FileRef extends SchemaProvider
     private function addOwnerRelationship(array $relationships, \FileRef $resource)
     {
         $relationships[self::REL_OWNER] = [
-            self::META => [
+            self::RELATIONSHIP_META => [
                 'name' => $resource->getAuthorName(),
             ],
-            self::DATA => $resource->owner,
+            self::RELATIONSHIP_DATA => $resource->owner,
         ];
 
         if (isset($resource->owner)) {
-            $relationships[self::REL_OWNER][self::LINKS] = [
-                Link::RELATED => $this->getSchemaContainer()
-                ->getSchema($resource->owner)->getSelfSubLink($resource->owner),
+            $relationships[self::REL_OWNER][self::RELATIONSHIP_LINKS] = [
+                Link::RELATED => $this->createLinkToResource($resource->owner),
             ];
         }
 
@@ -144,11 +147,9 @@ class FileRef extends SchemaProvider
         if ($resource->folder_id) {
             $folder = $resource->getFolderType();
             $relationships[self::REL_PARENT] = [
-                self::DATA => $folder,
-                self::LINKS => [
-                    Link::RELATED => $this->getSchemaContainer()
-                    ->getSchema($folder)
-                    ->getSelfSubLink($folder),
+                self::RELATIONSHIP_DATA => $folder,
+                self::RELATIONSHIP_LINKS => [
+                    Link::RELATED => $this->createLinkToResource($folder),
                 ],
             ];
         }
@@ -163,11 +164,10 @@ class FileRef extends SchemaProvider
                 try {
                     $rangeType = $folder->range_type;
                     if ($range = $folder->$rangeType) {
-                        $schema = $this->getSchemaContainer()->getSchema($range);
                         $relationships[self::REL_RANGE] = [
-                            self::DATA => $range,
-                            self::LINKS => [
-                                Link::RELATED => $schema->getSelfSubLink($range),
+                            self::RELATIONSHIP_DATA => $range,
+                            self::RELATIONSHIP_LINKS => [
+                                Link::RELATED => $this->createLinkToResource($range),
                             ],
                         ];
                     }
@@ -182,8 +182,8 @@ class FileRef extends SchemaProvider
     private function addTermsRelationship(array $relationships, \FileRef $resource)
     {
         $relationships[self::REL_TERMS] = [
-            self::DATA => $resource->content_terms_of_use_id ? $resource->terms_of_use : null,
-            self::SHOW_SELF => true,
+            self::RELATIONSHIP_DATA => $resource->content_terms_of_use_id ? $resource->terms_of_use : null,
+            self::RELATIONSHIP_LINKS_SELF => true,
         ];
 
         return $relationships;
diff --git a/lib/classes/JsonApi/Schemas/Folder.php b/lib/classes/JsonApi/Schemas/Folder.php
index b28884d9f5df7f68d70e02b66b9348a3a9afa5f3..f2c5608ec4cab1e0dd7469f6a8eb0fcf68ee0728 100644
--- a/lib/classes/JsonApi/Schemas/Folder.php
+++ b/lib/classes/JsonApi/Schemas/Folder.php
@@ -2,7 +2,8 @@
 
 namespace JsonApi\Schemas;
 
-use Neomerx\JsonApi\Document\Link;
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
+use Neomerx\JsonApi\Schema\Link;
 
 class Folder extends SchemaProvider
 {
@@ -14,16 +15,14 @@ class Folder extends SchemaProvider
     const REL_FILE_REFS = 'file-refs';
     const REL_FOLDERS = 'folders';
 
-    protected $resourceType = self::TYPE;
-
-    public function getId($resource)
+    public function getId($resource): ?string
     {
         return $resource->getId();
     }
 
-    public function getAttributes($resource)
+    public function getAttributes($resource, ContextInterface $context): iterable
     {
-        $user = $this->getDiContainer()->get('studip-current-user');
+        $user = $this->currentUser;
 
         $attributes = [
             'folder-type' => $resource->folder_type,
@@ -51,8 +50,11 @@ class Folder extends SchemaProvider
     /**
      * @SuppressWarnings(PHPMD.UnusedFormalParameters)
      */
-    public function getRelationships($resource, $isPrimary, array $includeList)
+    public function getRelationships($resource, ContextInterface $context): iterable
     {
+        $isPrimary = $context->getPosition()->getLevel() === 0;
+        $includeList = $context->getIncludePaths();
+
         $relationships = [];
 
         if ($isPrimary) {
@@ -71,11 +73,9 @@ class Folder extends SchemaProvider
     {
         if ($resource->user_id && $resource->owner) {
             $relationships[self::REL_OWNER] = [
-                self::DATA => $resource->owner,
-                self::LINKS => [
-                    Link::RELATED => $this->getSchemaContainer()
-                    ->getSchema($resource->owner)
-                    ->getSelfSubLink($resource->owner),
+                self::RELATIONSHIP_DATA => $resource->owner,
+                self::RELATIONSHIP_LINKS => [
+                    Link::RELATED => $this->createLinkToResource($resource->owner),
                 ],
             ];
         }
@@ -88,11 +88,9 @@ class Folder extends SchemaProvider
         if ($resource->parent_id) {
             $parent = $resource->parentfolder->getTypedFolder();
             $relationships[self::REL_PARENT] = [
-                self::DATA => $parent,
-                self::LINKS => [
-                    Link::RELATED => $this->getSchemaContainer()
-                    ->getSchema($parent)
-                    ->getSelfSubLink($parent),
+                self::RELATIONSHIP_DATA => $parent,
+                self::RELATIONSHIP_LINKS => [
+                    Link::RELATED => $this->createLinkToResource($parent),
                 ],
             ];
         }
@@ -117,12 +115,11 @@ class Folder extends SchemaProvider
     {
         $rangeType = $resource->range_type;
         if ($range = $resource->$rangeType) {
-            $schema = $this->getSchemaContainer()->getSchema($range);
 
             return [
-                self::DATA => $range,
-                self::LINKS => [
-                    Link::RELATED => $schema->getSelfSubLink($range),
+                self::RELATIONSHIP_DATA => $range,
+                self::RELATIONSHIP_LINKS => [
+                    Link::RELATED => $this->createLinkToResource($range),
                 ],
             ];
         }
@@ -133,7 +130,7 @@ class Folder extends SchemaProvider
     private function getDefaultRangeRelationship($resource)
     {
         return [
-            self::META => [
+            self::RELATIONSHIP_META => [
                 'range_id' => $resource->range_id,
                 'range_type' => $resource->range_type,
             ],
@@ -145,7 +142,7 @@ class Folder extends SchemaProvider
         if ($resource->range_type === 'course') {
             if (\Feedback::isActivated($resource->range_id)) {
                 $relationships[self::REL_FEEDBACK] = [
-                    self::LINKS => [
+                    self::RELATIONSHIP_LINKS => [
                         Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_FEEDBACK)
                     ],
                 ];
@@ -158,7 +155,7 @@ class Folder extends SchemaProvider
     private function getFoldersRelationship(array $relationships, $resource)
     {
         $relationships[self::REL_FOLDERS] = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_FOLDERS),
             ],
         ];
@@ -169,7 +166,7 @@ class Folder extends SchemaProvider
     private function getFilesRelationship(array $relationships, $resource)
     {
         $relationships[self::REL_FILE_REFS] = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_FILE_REFS),
             ],
         ];
diff --git a/lib/classes/JsonApi/Schemas/ForumCategory.php b/lib/classes/JsonApi/Schemas/ForumCategory.php
index f26d70dc44d552646192fa6b474d83064d9933ae..411146498267568323f5413c2527301ab5a24db0 100644
--- a/lib/classes/JsonApi/Schemas/ForumCategory.php
+++ b/lib/classes/JsonApi/Schemas/ForumCategory.php
@@ -2,7 +2,8 @@
 
 namespace JsonApi\Schemas;
 
-use Neomerx\JsonApi\Document\Link;
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
+use Neomerx\JsonApi\Schema\Link;
 use JsonApi\Models\ForumEntry as Entry;
 
 class ForumCategory extends SchemaProvider
@@ -11,14 +12,14 @@ class ForumCategory extends SchemaProvider
     const REL_COURSE = 'course';
     const REL_ENTRY = 'entries';
 
-    protected $resourceType = self::TYPE;
 
-    public function getId($category)
+
+    public function getId($category): ?string
     {
         return $category->id;
     }
 
-    public function getAttributes($category)
+    public function getAttributes($category, ContextInterface $context): iterable
     {
         return [
             'title' => $category->entry_name,
@@ -26,8 +27,11 @@ class ForumCategory extends SchemaProvider
         ];
     }
 
-    public function getRelationships($category, $isPrimary, array $includeList)
+    public function getRelationships($category, ContextInterface $context): iterable
     {
+        $isPrimary = $context->getPosition()->getLevel() === 0;
+        $includeList = $context->getIncludePaths();
+
         $relationships = [];
         if ($isPrimary) {
             $relationships = $this->addCourseRelationship($category, $isPrimary, $includeList);
@@ -39,14 +43,14 @@ class ForumCategory extends SchemaProvider
 
     public function addCourseRelationship($category, $isPrimary, $includeList)
     {
-        $link = new Link('/courses/'.$category->seminar_id);
         $data = $isPrimary && in_array(self::REL_COURSE, $includeList)
               ? \Course::find($category->seminar_id)
               : \Course::buildExisting(['id' => $category->seminar_id]);
+        $link = $this->createLinkToResource($data);
         $relationships = [
             self::REL_COURSE => [
-                self::LINKS => [Link::RELATED => $link],
-                self::DATA => $data,
+                self::RELATIONSHIP_LINKS => [Link::RELATED => $link],
+                self::RELATIONSHIP_DATA => $data,
             ],
         ];
 
@@ -59,10 +63,10 @@ class ForumCategory extends SchemaProvider
     private function addEntryRelationship($category, $isPrimary, $includeList)
     {
         $data = Entry::getEntriesFromCat($category);
-        $link = new Link('/forum-categories/'.($category->id).'/entries');
+        $link = $this->getRelationshipRelatedLink($category, self::REL_ENTRY);
         $relationships[self::REL_ENTRY] = [
-            self::DATA => $data,
-            self::LINKS => [
+            self::RELATIONSHIP_DATA => $data,
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $link,
             ],
         ];
diff --git a/lib/classes/JsonApi/Schemas/ForumEntry.php b/lib/classes/JsonApi/Schemas/ForumEntry.php
index 10a6e218be16c5347664b15e4ea66a2c04821652..e99be46e7617e5c1a93071f53c9fa20756283e90 100644
--- a/lib/classes/JsonApi/Schemas/ForumEntry.php
+++ b/lib/classes/JsonApi/Schemas/ForumEntry.php
@@ -2,7 +2,8 @@
 
 namespace JsonApi\Schemas;
 
-use Neomerx\JsonApi\Document\Link;
+use Neomerx\JsonApi\Schema\Link;
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
 use JsonApi\Models\ForumCat;
 
 class ForumEntry extends SchemaProvider
@@ -11,14 +12,12 @@ class ForumEntry extends SchemaProvider
     const REL_CAT = 'category';
     const REL_ENTRY = 'entries';
 
-    protected $resourceType = self::TYPE;
-
-    public function getId($entry)
+    public function getId($entry): ?string
     {
         return $entry->topic_id;
     }
 
-    public function getAttributes($entry)
+    public function getAttributes($entry, ContextInterface $context): iterable
     {
         return [
             'title' => $entry->name,
@@ -30,8 +29,11 @@ class ForumEntry extends SchemaProvider
     /**
      * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
-    public function getRelationships($entry, $isPrimary, array $includeList)
+    public function getRelationships($entry, ContextInterface $context): iterable
     {
+        $isPrimary = $context->getPosition()->getLevel() === 0;
+        $includeList = $context->getIncludePaths();
+
         $relationships = [];
         if ($isPrimary) {
             $relationships = $this->addCategoryRelationship($relationships, $entry, $includeList);
@@ -43,16 +45,16 @@ class ForumEntry extends SchemaProvider
 
     private function addCategoryRelationship($relationships, $entry, $includeList)
     {
-        $cat_link = new Link('/forum-categories/'.($entry->category)->id);
+        $cat_link = $this->createLinkToResource($entry->category);
         $cat_data = in_array(self::REL_CAT, $includeList)
             ? ForumCat::find($entry->category->id)
             : ForumCat::buildExisting(['id' => $entry->category->id]);
 
         $relationships[self::REL_CAT] = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $cat_link,
             ],
-            self::DATA => $cat_data,
+            self::RELATIONSHIP_DATA => $cat_data,
         ];
 
         return $relationships;
@@ -64,9 +66,9 @@ class ForumEntry extends SchemaProvider
     private function addChildEntryRelationship($relationships, $entry, $includeList)
     {
         $relationships[self::REL_ENTRY] = [
-            self::DATA => $entry->getChildEntries($entry->id),
+            self::RELATIONSHIP_DATA => $entry->getChildEntries($entry->id),
 
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($entry, self::REL_ENTRY),
             ],
         ];
diff --git a/lib/classes/JsonApi/Schemas/Institute.php b/lib/classes/JsonApi/Schemas/Institute.php
index bc3c2e27f44d5080d111bdc520510d2fcacbdf34..0ee99d95f8fb719e235b2ae5e4a46b937be1e97f 100644
--- a/lib/classes/JsonApi/Schemas/Institute.php
+++ b/lib/classes/JsonApi/Schemas/Institute.php
@@ -2,7 +2,8 @@
 
 namespace JsonApi\Schemas;
 
-use Neomerx\JsonApi\Document\Link;
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
+use Neomerx\JsonApi\Schema\Link;
 
 class Institute extends SchemaProvider
 {
@@ -13,14 +14,12 @@ class Institute extends SchemaProvider
     const REL_FOLDERS = 'folders';
     const REL_STATUS_GROUPS = 'status-groups';
 
-    protected $resourceType = self::TYPE;
-
-    public function getId($institute)
+    public function getId($institute): ?string
     {
         return $institute->id;
     }
 
-    public function getAttributes($institute)
+    public function getAttributes($institute, ContextInterface $context): iterable
     {
         return [
             'name' => $institute['Name'],
@@ -37,8 +36,11 @@ class Institute extends SchemaProvider
     /**
      * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
-    public function getRelationships($resource, $isPrimary, array $includeList)
+    public function getRelationships($resource, ContextInterface $context): iterable
     {
+        $isPrimary = $context->getPosition()->getLevel() === 0;
+        $includeList = $context->getIncludePaths();
+
         $relationships = [];
 
         $shouldInclude = function ($key) use ($isPrimary, $includeList) {
@@ -47,20 +49,20 @@ class Institute extends SchemaProvider
 
         $filesLink = $this->getRelationshipRelatedLink($resource, self::REL_FILES);
         $relationships[self::REL_FILES] = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $filesLink,
             ],
         ];
 
         $foldersLink = $this->getRelationshipRelatedLink($resource, self::REL_FOLDERS);
         $relationships[self::REL_FOLDERS] = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $foldersLink,
             ],
         ];
 
         $relationships[self::REL_BLUBBER] = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_BLUBBER),
             ],
         ];
@@ -80,13 +82,13 @@ class Institute extends SchemaProvider
         $includeData
     ) {
         $relation = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_STATUS_GROUPS),
             ]
         ];
         if ($includeData) {
             $related = $resource->status_groups;
-            $relation[self::DATA] = $related;
+            $relation[self::RELATIONSHIP_DATA] = $related;
         }
 
         return array_merge($relationships, [self::REL_STATUS_GROUPS => $relation]);
diff --git a/lib/classes/JsonApi/Schemas/InstituteMember.php b/lib/classes/JsonApi/Schemas/InstituteMember.php
index 190343d2246dd3bfedcd51586b2f4d905bb36710..dbfe9170c6ca812d7fbff0376633ea4c2fa07742 100644
--- a/lib/classes/JsonApi/Schemas/InstituteMember.php
+++ b/lib/classes/JsonApi/Schemas/InstituteMember.php
@@ -2,7 +2,8 @@
 
 namespace JsonApi\Schemas;
 
-use Neomerx\JsonApi\Document\Link;
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
+use Neomerx\JsonApi\Schema\Link;
 
 class InstituteMember extends SchemaProvider
 {
@@ -10,14 +11,14 @@ class InstituteMember extends SchemaProvider
     const REL_INSTITUTE = 'institute';
     const REL_USER = 'user';
 
-    protected $resourceType = self::TYPE;
 
-    public function getId($membership)
+
+    public function getId($membership): ?string
     {
         return $membership->id;
     }
 
-    public function getAttributes($resource)
+    public function getAttributes($resource, ContextInterface $context): iterable
     {
         $defaultNull = function ($key) use ($resource) {
             return $resource->$key ?: null;
@@ -40,21 +41,24 @@ class InstituteMember extends SchemaProvider
     /**
      * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
-    public function getRelationships($resource, $isPrimary, array $includeList)
+    public function getRelationships($resource, ContextInterface $context): iterable
     {
+        $isPrimary = $context->getPosition()->getLevel() === 0;
+        $includeList = $context->getIncludePaths();
+
         $relationships = [
             self::REL_USER => [
-                self::LINKS => [
-                    Link::RELATED => $this->getSchemaContainer()->getSchema($resource->user)->getSelfSubLink($resource->user),
+                self::RELATIONSHIP_LINKS => [
+                    Link::RELATED => $this->createLinkToResource($resource->user),
                 ],
-                self::DATA => $resource->user,
+                self::RELATIONSHIP_DATA => $resource->user,
             ],
 
             self::REL_INSTITUTE => [
-                self::LINKS => [
-                    Link::RELATED => $this->getSchemaContainer()->getSchema($resource->institute)->getSelfSubLink($resource->institute),
+                self::RELATIONSHIP_LINKS => [
+                    Link::RELATED => $this->createLinkToResource($resource->institute),
                 ],
-                self::DATA => $resource->institute,
+                self::RELATIONSHIP_DATA => $resource->institute,
             ],
         ];
 
diff --git a/lib/classes/JsonApi/Schemas/LibraryFile.php b/lib/classes/JsonApi/Schemas/LibraryFile.php
index fa151db94d8e8faacd91466c5786b00028cb0ca8..9073acb91ef8953022efc6e2e0a32e4c6fef37f8 100644
--- a/lib/classes/JsonApi/Schemas/LibraryFile.php
+++ b/lib/classes/JsonApi/Schemas/LibraryFile.php
@@ -2,8 +2,8 @@
 
 namespace JsonApi\Schemas;
 
-use JsonApi\Providers\JsonApiConfig as C;
-use Neomerx\JsonApi\Document\Link;
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
+use Neomerx\JsonApi\Schema\Link;
 
 class LibraryFile extends SchemaProvider
 {
@@ -18,24 +18,29 @@ class LibraryFile extends SchemaProvider
 
     const META_CONTENT = 'content';
 
-    protected $resourceType = self::TYPE;
 
-    public function getId($resource)
+
+    public function getId($resource): ?string
     {
         return $resource->getId();
     }
 
-    public function getPrimaryMeta($resource)
+    /**
+     * @inheritdoc
+     */
+    public function hasResourceMeta($resource): bool
     {
-        $link = $this->getDiContainer()->get(C::JSON_URL_PREFIX)
-              .$this->getRelationshipRelatedLink($resource, self::META_CONTENT)->getSubHref();
+        return true;
+    }
 
+    public function getResourceMeta($resource)
+    {
         return [
-            'download-url' => $link,
+            'download-url' => $resource->getDownloadURL(),
         ];
     }
 
-    public function getAttributes($resource)
+    public function getAttributes($resource, ContextInterface $context): iterable
     {
         $attributes = [
             'name' => $resource->getFilename(),
@@ -49,16 +54,16 @@ class LibraryFile extends SchemaProvider
             'filesize' => (int) $resource->getSize()
         ];
 
-        $user = $this->getDiContainer()->get('studip-current-user');
+        $userId = $this->currentUser->id;
         if ($folder = $resource->getFolderType()) {
             $filetype = $resource->getFileType();
             $attributes = array_merge(
                 $attributes,
                 [
-                    'is-readable' => $folder->isReadable($user->id),
-                    'is-downloadable' => $filetype->isDownloadable($user->id),
-                    'is-editable' => $filetype->isEditable($user->id),
-                    'is-writable' => $filetype->isWritable($user->id),
+                    'is-readable' => $folder->isReadable($userId),
+                    'is-downloadable' => $filetype->isDownloadable($userId),
+                    'is-editable' => $filetype->isEditable($userId),
+                    'is-writable' => $filetype->isWritable($userId),
                 ]
             );
         }
@@ -69,8 +74,11 @@ class LibraryFile extends SchemaProvider
     /**
      * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
-    public function getRelationships($resource, $isPrimary, array $includeList)
+    public function getRelationships($resource, ContextInterface $context): iterable
     {
+        $isPrimary = $context->getPosition()->getLevel() === 0;
+        $includeList = $context->getIncludePaths();
+
         $relationships = [];
 
         $relationships = $this->getFeedbackRelationship($relationships, $resource);
@@ -93,7 +101,7 @@ class LibraryFile extends SchemaProvider
         if ($folder = $resource->getFolderType()) {
             if ($folder->range_id && $folder->range_type === 'course' && \Feedback::isActivated($folder->range_id)) {
                 $relationships[self::REL_FEEDBACK] = [
-                    self::LINKS => [
+                    self::RELATIONSHIP_LINKS => [
                         Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_FEEDBACK)
                     ],
                 ];
@@ -107,11 +115,9 @@ class LibraryFile extends SchemaProvider
     {
         if ($resource->file) {
             $relationships[self::REL_FILE] = [
-                self::DATA => $resource->file,
-                self::LINKS => [
-                    Link::RELATED => $this->getSchemaContainer()
-                    ->getSchema($resource->file)
-                    ->getSelfSubLink($resource->file),
+                self::RELATIONSHIP_DATA => $resource->file,
+                self::RELATIONSHIP_LINKS => [
+                    Link::RELATED => $this->createLinkToResource($resource->file),
                 ],
             ];
         }
@@ -122,16 +128,15 @@ class LibraryFile extends SchemaProvider
     private function addOwnerRelationship(array $relationships, \FileRef $resource)
     {
         $relationships[self::REL_OWNER] = [
-            self::META => [
+            self::RELATIONSHIP_META => [
                 'name' => $resource->getAuthorName(),
             ],
-            self::DATA => $resource->owner,
+            self::RELATIONSHIP_DATA => $resource->owner,
         ];
 
         if (isset($resource->owner)) {
-            $relationships[self::REL_OWNER][self::LINKS] = [
-                Link::RELATED => $this->getSchemaContainer()
-                ->getSchema($resource->owner)->getSelfSubLink($resource->owner),
+            $relationships[self::REL_OWNER][self::RELATIONSHIP_LINKS] = [
+                Link::RELATED => $this->createLinkToResource($resource->owner),
             ];
         }
 
@@ -143,11 +148,9 @@ class LibraryFile extends SchemaProvider
         if ($resource->folder_id) {
             $folder = $resource->getFolderType();
             $relationships[self::REL_PARENT] = [
-                self::DATA => $folder,
-                self::LINKS => [
-                    Link::RELATED => $this->getSchemaContainer()
-                    ->getSchema($folder)
-                    ->getSelfSubLink($folder),
+                self::RELATIONSHIP_DATA => $folder,
+                self::RELATIONSHIP_LINKS => [
+                    Link::RELATED => $this->createLinkToResource($folder),
                 ],
             ];
         }
@@ -155,18 +158,17 @@ class LibraryFile extends SchemaProvider
         return $relationships;
     }
 
-    private function addRangeRelationship(array $relationships, \FileRef $resource)
+    private function addRangeRelationship(array $relationships, \FileRef $resource): array
     {
         if ($folder = $resource->getFolderType()) {
             if ($folder->range_id) {
                 try {
                     $rangeType = $folder->range_type;
                     if ($range = $folder->$rangeType) {
-                        $schema = $this->getSchemaContainer()->getSchema($range);
                         $relationships[self::REL_RANGE] = [
-                            self::DATA => $range,
-                            self::LINKS => [
-                                Link::RELATED => $schema->getSelfSubLink($range),
+                            self::RELATIONSHIP_DATA => $range,
+                            self::RELATIONSHIP_LINKS => [
+                                Link::RELATED => $this->createLinkToResource($range),
                             ],
                         ];
                     }
@@ -181,8 +183,8 @@ class LibraryFile extends SchemaProvider
     private function addTermsRelationship(array $relationships, \FileRef $resource)
     {
         $relationships[self::REL_TERMS] = [
-            self::DATA => $resource->content_terms_of_use_id ? $resource->terms_of_use : null,
-            self::SHOW_SELF => true,
+            self::RELATIONSHIP_DATA => $resource->content_terms_of_use_id ? $resource->terms_of_use : null,
+            self::RELATIONSHIP_LINKS_SELF => true,
         ];
 
         return $relationships;
diff --git a/lib/classes/JsonApi/Schemas/Message.php b/lib/classes/JsonApi/Schemas/Message.php
index c7f053ce8b52ab13cd280b681920876b51ad11c9..211cf85405f93bd11c92fac399d0acffadd1a97d 100644
--- a/lib/classes/JsonApi/Schemas/Message.php
+++ b/lib/classes/JsonApi/Schemas/Message.php
@@ -2,7 +2,8 @@
 
 namespace JsonApi\Schemas;
 
-use Neomerx\JsonApi\Document\Link;
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
+use Neomerx\JsonApi\Schema\Link;
 
 class Message extends SchemaProvider
 {
@@ -10,16 +11,14 @@ class Message extends SchemaProvider
     const REL_SENDER = 'sender';
     const REL_RECIPIENTS = 'recipients';
 
-    protected $resourceType = self::TYPE;
-
-    public function getId($message)
+    public function getId($message): ?string
     {
         return $message->id;
     }
 
-    public function getAttributes($message)
+    public function getAttributes($message, ContextInterface $context): iterable
     {
-        $user = $this->getDiContainer()->get('studip-current-user');
+        $user = $this->currentUser;
 
         return [
             'subject' => $message->subject,
@@ -31,8 +30,11 @@ class Message extends SchemaProvider
         ];
     }
 
-    public function getRelationships($message, $isPrimary, array $includeList)
+    public function getRelationships($message, ContextInterface $context): iterable
     {
+        $isPrimary = $context->getPosition()->getLevel() === 0;
+        $includeList = $context->getIncludePaths();
+
         $shouldInclude = function ($key) use ($isPrimary, $includeList) {
             return $isPrimary && in_array($key, $includeList);
         };
@@ -54,15 +56,15 @@ class Message extends SchemaProvider
         $data = null;
         if ($userId) {
             $data = $includeData ? \User::find($userId) : \User::build(['id' => $userId], false);
-        }
 
-        $relationships[self::REL_SENDER] = [
-            // self::SHOW_SELF => true,
-            self::LINKS => [
-                Link::RELATED => new Link('/users/'.$userId),
-            ],
-            self::DATA => $data,
-        ];
+            $relationships[self::REL_SENDER] = [
+                // self::RELATIONSHIP_LINKS_SELF => true,
+                self::RELATIONSHIP_LINKS => [
+                    Link::RELATED => $this->createLinkToResource($data),
+                ],
+                self::RELATIONSHIP_DATA => $data,
+            ];
+        }
 
         return $relationships;
     }
@@ -73,11 +75,8 @@ class Message extends SchemaProvider
     private function getRecipientsRelationship(array $relationships, \Message $message, $includeData)
     {
         $relationships[self::REL_RECIPIENTS] = [
-            // self::SHOW_SELF => true,
-            self::LINKS => [
-                // Link::RELATED => new Link('/users/'.$userId),
-            ],
-            self::DATA => $message->receivers->map(function ($i) { return $i->user; }),
+            // self::RELATIONSHIP_LINKS_SELF => true,
+            self::RELATIONSHIP_DATA => $message->receivers->map(function ($i) { return $i->user; }),
         ];
 
         return $relationships;
diff --git a/lib/classes/JsonApi/Schemas/ScheduleEntry.php b/lib/classes/JsonApi/Schemas/ScheduleEntry.php
index 7e38ef6efa132617febaebd95d4e91127c011425..e737e614770d2c286b8da329a4dd4ec979aba3b1 100644
--- a/lib/classes/JsonApi/Schemas/ScheduleEntry.php
+++ b/lib/classes/JsonApi/Schemas/ScheduleEntry.php
@@ -2,21 +2,22 @@
 
 namespace JsonApi\Schemas;
 
-use Neomerx\JsonApi\Document\Link;
+use Neomerx\JsonApi\Schema\Link;
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
 
 class ScheduleEntry extends SchemaProvider
 {
     const TYPE = 'schedule-entries';
     const REL_OWNER = 'owner';
 
-    protected $resourceType = self::TYPE;
 
-    public function getId($entry)
+
+    public function getId($entry): ?string
     {
         return $entry->id;
     }
 
-    public function getAttributes($entry)
+    public function getAttributes($entry, ContextInterface $context): iterable
     {
         return [
             'title' => $entry->title,
@@ -33,16 +34,19 @@ class ScheduleEntry extends SchemaProvider
     /**
      * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
-    public function getRelationships($entry, $isPrimary, array $includeList)
+    public function getRelationships($entry, ContextInterface $context): iterable
     {
-        $link = $this->getSchemaContainer()->getSchema($entry->user)->getSelfSubLink($entry->user);
+        $isPrimary = $context->getPosition()->getLevel() === 0;
+        $includeList = $context->getIncludePaths();
+
+        $link = $this->createLinkToResource($entry->user);
 
         $relationships = [
             self::REL_OWNER => [
-                self::LINKS => [
+                self::RELATIONSHIP_LINKS => [
                     Link::RELATED => $link,
                 ],
-                self::DATA => $entry->user,
+                self::RELATIONSHIP_DATA => $entry->user,
             ],
         ];
 
diff --git a/lib/classes/JsonApi/Schemas/SchemaProvider.php b/lib/classes/JsonApi/Schemas/SchemaProvider.php
index b6ebdf32049e4e60f4f18b6f39be3102f6657864..5e158867ccb2e6996e65505dd8b453a668314a21 100644
--- a/lib/classes/JsonApi/Schemas/SchemaProvider.php
+++ b/lib/classes/JsonApi/Schemas/SchemaProvider.php
@@ -2,38 +2,60 @@
 
 namespace JsonApi\Schemas;
 
-use Neomerx\JsonApi\Contracts\Schema\ContainerInterface;
-use Neomerx\JsonApi\Contracts\Schema\SchemaFactoryInterface;
+use JsonApi\Errors\InternalServerError;
+use Neomerx\JsonApi\Contracts\Factories\FactoryInterface;
+use Neomerx\JsonApi\Contracts\Schema\LinkInterface;
+use Neomerx\JsonApi\Contracts\Schema\SchemaContainerInterface;
+use Neomerx\JsonApi\Schema\BaseSchema;
 
-abstract class SchemaProvider extends \Neomerx\JsonApi\Schema\SchemaProvider
+abstract class SchemaProvider extends BaseSchema
 {
-    private $schemaContainer;
-    private $diContainer;
+    /** @var SchemaContainerInterface */
+    protected $schemaContainer;
 
-    /**
-     * @param SchemaFactoryInterface $factory
-     * @param ContainerInterface     $factory
-     */
-    public function __construct(SchemaFactoryInterface $factory, ContainerInterface $schemaContainer)
+    /** @var ?\User */
+    protected $currentUser;
+
+    public function __construct(FactoryInterface $factory, SchemaContainerInterface $schemaContainer, ?\User $user)
     {
+        $this->schemaContainer = $schemaContainer;
+        $this->currentUser = $user;
+
         parent::__construct($factory);
+    }
 
-        $this->schemaContainer = $schemaContainer;
-        $this->diContainer = $factory->getDependencyInjectionContainer();
+    const TYPE = '';
+
+    public function getType(): string
+    {
+        return static::TYPE;
     }
 
     /**
-     * Return the set schema container.
-     *
-     * @return ContainerInterface the schema container
+     * @inheritdoc
      */
-    protected function getSchemaContainer()
+    public function isAddSelfLinkInRelationshipByDefault(string $relationshipName): bool
     {
-        return $this->schemaContainer;
+        return false;
     }
 
-    public function getDiContainer()
+    /**
+     * @inheritdoc
+     */
+    public function isAddRelatedLinkInRelationshipByDefault(string $relationshipName): bool
+    {
+        return false;
+    }
+
+    /**
+     * @param mixed $resource
+     */
+    public function createLinkToResource($resource): LinkInterface
     {
-        return $this->diContainer;
+        if (!$this->schemaContainer->hasSchema($resource)) {
+            throw new InternalServerError('Cannot create links to objects without schema.');
+        }
+
+        return $this->schemaContainer->getSchema($resource)->getSelfLink($resource);
     }
 }
diff --git a/lib/classes/JsonApi/Schemas/SemClass.php b/lib/classes/JsonApi/Schemas/SemClass.php
index e7fc448e84c2fe8b5b03b36aae788129b8e62830..694d548e8d35dc2d340434f1e4ad8929d06d604c 100644
--- a/lib/classes/JsonApi/Schemas/SemClass.php
+++ b/lib/classes/JsonApi/Schemas/SemClass.php
@@ -2,21 +2,20 @@
 
 namespace JsonApi\Schemas;
 
-use Neomerx\JsonApi\Document\Link;
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
+use Neomerx\JsonApi\Schema\Link;
 
 class SemClass extends SchemaProvider
 {
     const REL_SEM_TYPES = 'sem-types';
     const TYPE = 'sem-classes';
 
-    protected $resourceType = self::TYPE;
-
-    public function getId($resource)
+    public function getId($resource): ?string
     {
         return $resource['id'];
     }
 
-    public function getAttributes($resource)
+    public function getAttributes($resource, ContextInterface $context): iterable
     {
         return [
             'name' => (string) $resource['name'],
@@ -32,8 +31,11 @@ class SemClass extends SchemaProvider
         ];
     }
 
-    public function getRelationships($resource, $isPrimary, array $includeList)
+    public function getRelationships($resource, ContextInterface $context): iterable
     {
+        $isPrimary = $context->getPosition()->getLevel() === 0;
+        $includeList = $context->getIncludePaths();
+
         $relationships = [];
 
         $shouldInclude = function ($key) use ($isPrimary, $includeList) {
@@ -53,14 +55,14 @@ class SemClass extends SchemaProvider
     private function addSemTypesRelationship(array $relationships, $resource, $includeData)
     {
         $relation = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_SEM_TYPES),
-            ]
+            ],
         ];
 
         if ($includeData) {
             $related = $resource->getSemTypes();
-            $relation[self::DATA] = $related;
+            $relation[self::RELATIONSHIP_DATA] = $related;
         }
 
         $relationships[self::REL_SEM_TYPES] = $relation;
diff --git a/lib/classes/JsonApi/Schemas/SemType.php b/lib/classes/JsonApi/Schemas/SemType.php
index 0798d7411b7ec62435d9f1e3c7c2ac5f1aa947b8..eb44e93f29a04873283504b782992609a21cd775 100644
--- a/lib/classes/JsonApi/Schemas/SemType.php
+++ b/lib/classes/JsonApi/Schemas/SemType.php
@@ -2,21 +2,22 @@
 
 namespace JsonApi\Schemas;
 
-use Neomerx\JsonApi\Document\Link;
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
+use Neomerx\JsonApi\Schema\Link;
 
 class SemType extends SchemaProvider
 {
     const REL_SEM_CLASS = 'sem-class';
     const TYPE = 'sem-types';
 
-    protected $resourceType = self::TYPE;
 
-    public function getId($resource)
+
+    public function getId($resource): ?string
     {
         return $resource['id'];
     }
 
-    public function getAttributes($resource)
+    public function getAttributes($resource, ContextInterface $context): iterable
     {
         return [
             'name' => $resource['name'],
@@ -25,19 +26,20 @@ class SemType extends SchemaProvider
         ];
     }
 
-    public function getRelationships($resource, $isPrimary, array $includeList)
+    public function getRelationships($resource, ContextInterface $context): iterable
     {
+        $isPrimary = $context->getPosition()->getLevel() === 0;
+        $includeList = $context->getIncludePaths();
+
         $relationships = [];
 
         // SemClass
         $related = $resource->getClass();
         $relationships[self::REL_SEM_CLASS] = [
-            self::LINKS => [
-                Link::RELATED => $this->getSchemaContainer()
-                                      ->getSchema($related)
-                                      ->getSelfSubLink($related)
+            self::RELATIONSHIP_LINKS => [
+                Link::RELATED => $this->createLinkToResource($related)
             ],
-            self::DATA => $related,
+            self::RELATIONSHIP_DATA => $related,
         ];
 
         return $relationships;
diff --git a/lib/classes/JsonApi/Schemas/Semester.php b/lib/classes/JsonApi/Schemas/Semester.php
index 10b45bd529d721d559bf20299b0f9c0a50541ec5..f1bca9cba992d4acd24c97755a7378cae9519099 100644
--- a/lib/classes/JsonApi/Schemas/Semester.php
+++ b/lib/classes/JsonApi/Schemas/Semester.php
@@ -2,18 +2,18 @@
 
 namespace JsonApi\Schemas;
 
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
+
 class Semester extends SchemaProvider
 {
     const TYPE = 'semesters';
 
-    protected $resourceType = self::TYPE;
-
-    public function getId($semester)
+    public function getId($semester): ?string
     {
         return $semester->id;
     }
 
-    public function getAttributes($semester)
+    public function getAttributes($semester, ContextInterface $context): iterable
     {
         return [
             'title' => (string) $semester->name,
@@ -26,4 +26,9 @@ class Semester extends SchemaProvider
             'visible' => (bool) $semester->visible,
         ];
     }
+
+    public function getRelationships($user, ContextInterface $context): iterable
+    {
+        return [];
+    }
 }
diff --git a/lib/classes/JsonApi/Schemas/SeminarCycleDate.php b/lib/classes/JsonApi/Schemas/SeminarCycleDate.php
index a7476f34738c9a2249ac4d37184cd12485c3848d..531b58bfcb4cd850dcf17360629d8379b2f2fbe1 100644
--- a/lib/classes/JsonApi/Schemas/SeminarCycleDate.php
+++ b/lib/classes/JsonApi/Schemas/SeminarCycleDate.php
@@ -2,21 +2,22 @@
 
 namespace JsonApi\Schemas;
 
-use Neomerx\JsonApi\Document\Link;
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
+use Neomerx\JsonApi\Schema\Link;
 
 class SeminarCycleDate extends SchemaProvider
 {
     const TYPE = 'seminar-cycle-dates';
     const REL_OWNER = 'owner';
 
-    protected $resourceType = self::TYPE;
 
-    public function getId($entry)
+
+    public function getId($entry): ?string
     {
         return $entry->id;
     }
 
-    public function getAttributes($entry)
+    public function getAttributes($entry, ContextInterface $context): iterable
     {
         $course = \Course::find($entry->seminar_id);
 
@@ -37,14 +38,17 @@ class SeminarCycleDate extends SchemaProvider
     /**
      * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
-    public function getRelationships($entry, $isPrimary, array $includeList)
+    public function getRelationships($entry, ContextInterface $context): iterable
     {
+        $isPrimary = $context->getPosition()->getLevel() === 0;
+        $includeList = $context->getIncludePaths();
+
         $relationships = [];
 
         if ($course = \Course::find($entry->seminar_id)) {
-            $link = $this->getSchemaContainer()->getSchema($course)->getSelfSubLink($course);
+            $link = $this->createLinkToResource($course);
             $relationships = [
-                self::REL_OWNER => [self::LINKS => [Link::RELATED => $link], self::DATA => $course],
+                self::REL_OWNER => [self::RELATIONSHIP_LINKS => [Link::RELATED => $link], self::RELATIONSHIP_DATA => $course],
             ];
         }
 
diff --git a/lib/classes/JsonApi/Schemas/SlimRoute.php b/lib/classes/JsonApi/Schemas/SlimRoute.php
index 3e055e5ccdca6913933c4c146b098256e71102e5..cc055942cdf250af92834435337862f28c1bf64e 100644
--- a/lib/classes/JsonApi/Schemas/SlimRoute.php
+++ b/lib/classes/JsonApi/Schemas/SlimRoute.php
@@ -2,29 +2,35 @@
 
 namespace JsonApi\Schemas;
 
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
+
 class SlimRoute extends SchemaProvider
 {
     const TYPE = 'slim-routes';
 
-    protected $resourceType = self::TYPE;
-
-    public function getId($route)
+    public function getId($route): ?string
     {
         return $route->getIdentifier();
     }
 
-    public function getAttributes($route)
+    public function getAttributes($route, ContextInterface $context): iterable
     {
         return [
             'methods' => $route->getMethods(),
+            'name' => $route->getName(),
             'pattern' => $route->getPattern(),
         ];
     }
 
+    public function getRelationships($user, ContextInterface $context): iterable
+    {
+        return [];
+    }
+
     /**
      * @inheritdoc
      */
-    public function getResourceLinks($resource)
+    public function getLinks($resource): iterable
     {
         return [];
     }
diff --git a/lib/classes/JsonApi/Schemas/StatusGroup.php b/lib/classes/JsonApi/Schemas/StatusGroup.php
index bb0026caff8c49b422ca8f42bd544c8ff94f71e0..97993e10d5f442448c1b8414c693b287edbc72cf 100644
--- a/lib/classes/JsonApi/Schemas/StatusGroup.php
+++ b/lib/classes/JsonApi/Schemas/StatusGroup.php
@@ -2,21 +2,20 @@
 
 namespace JsonApi\Schemas;
 
-use Neomerx\JsonApi\Document\Link;
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
+use Neomerx\JsonApi\Schema\Link;
 
 class StatusGroup extends SchemaProvider
 {
     const REL_RANGE = 'range';
     const TYPE = 'status-groups';
 
-    protected $resourceType = self::TYPE;
-
-    public function getId($resource)
+    public function getId($resource): ?string
     {
         return $resource->id;
     }
 
-    public function getAttributes($resource)
+    public function getAttributes($resource, ContextInterface $context): iterable
     {
         $stringOrNull = function ($item) {
             return trim($item) != '' ? (string) $item : null;
@@ -42,8 +41,11 @@ class StatusGroup extends SchemaProvider
         ];
     }
 
-    public function getRelationships($resource, $isPrimary, array $includeList)
+    public function getRelationships($resource, ContextInterface $context): iterable
     {
+        $isPrimary = $context->getPosition()->getLevel() === 0;
+        $includeList = $context->getIncludePaths();
+
         $relationships = [];
 
         $shouldInclude = function ($key) use ($isPrimary, $includeList) {
@@ -68,14 +70,12 @@ class StatusGroup extends SchemaProvider
         $related = $this->findRange($resource);
 
         $relation = [
-            self::LINKS => [
-                Link::RELATED => $this->getSchemaContainer()
-                                      ->getSchema($related)
-                                      ->getSelfSubLink($related)
+            self::RELATIONSHIP_LINKS => [
+                Link::RELATED => $this->createLinkToResource($related)
             ]
         ];
         if ($includeData) {
-            $relation[self::DATA] = $related;
+            $relation[self::RELATIONSHIP_DATA] = $related;
         }
 
         return array_merge($relationships, [self::REL_RANGE => $relation]);
diff --git a/lib/classes/JsonApi/Schemas/Studip.php b/lib/classes/JsonApi/Schemas/Studip.php
index 1bf3bae706783fc75386ba527df70db5a6523498..a545504331db2c9960a1e27f71ad245e2cf99960 100644
--- a/lib/classes/JsonApi/Schemas/Studip.php
+++ b/lib/classes/JsonApi/Schemas/Studip.php
@@ -2,13 +2,13 @@
 
 namespace JsonApi\Schemas;
 
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
+
 class Studip extends SchemaProvider
 {
     const TYPE = 'global';
 
-    protected $resourceType = self::TYPE;
-
-    public function getId($resource)
+    public function getId($resource): ?string
     {
         return $resource->getId();
     }
@@ -16,7 +16,12 @@ class Studip extends SchemaProvider
     /**
      * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
-    public function getAttributes($news)
+    public function getAttributes($news, ContextInterface $context): iterable
+    {
+        return [];
+    }
+
+    public function getRelationships($user, ContextInterface $context): iterable
     {
         return [];
     }
diff --git a/lib/classes/JsonApi/Schemas/StudipComment.php b/lib/classes/JsonApi/Schemas/StudipComment.php
index a02af3161fee59f92996cd60e0f6a105d906d125..7ba2a2d0ba7734ee6cb761d9afe0cb24c0e9a595 100644
--- a/lib/classes/JsonApi/Schemas/StudipComment.php
+++ b/lib/classes/JsonApi/Schemas/StudipComment.php
@@ -2,7 +2,8 @@
 
 namespace JsonApi\Schemas;
 
-use Neomerx\JsonApi\Document\Link;
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
+use Neomerx\JsonApi\Schema\Link;
 
 class StudipComment extends SchemaProvider
 {
@@ -10,14 +11,12 @@ class StudipComment extends SchemaProvider
     const REL_AUTHOR = 'author';
     const REL_NEWS = 'news';
 
-    protected $resourceType = self::TYPE;
-
-    public function getId($comment)
+    public function getId($comment): ?string
     {
         return $comment->comment_id;
     }
 
-    public function getAttributes($comment)
+    public function getAttributes($comment, ContextInterface $context): iterable
     {
         return [
             'content' => $comment->content,
@@ -26,25 +25,31 @@ class StudipComment extends SchemaProvider
         ];
     }
 
-    public function getRelationships($comment, $isPrimary, array $includeList)
+    public function getRelationships($comment, ContextInterface $context): iterable
     {
+        $isPrimary = $context->getPosition()->getLevel() === 0;
+        $includeList = $context->getIncludePaths();
+
         $relationships = [];
 
         if ($isPrimary) {
-            $relationships[self::REL_AUTHOR] = [
-                self::LINKS => [
-                    Link::RELATED => new Link('/users/'.$comment->user_id),
-                ],
-                self::DATA => \User::build(['id' => $comment->user_id], false),
-            ];
-            $relationships[self::REL_NEWS] = [
-                self::LINKS => [
-                    Link::RELATED => new Link('/news/'.$comment->object_id),
-                ],
-                self::DATA => in_array(self::REL_NEWS, $includeList)
-                ? $comment->news :
-                \StudipNews::build(['id' => $comment->object_id], false),
-            ];
+            if ($author = \User::find($comment->user_id)) {
+                $relationships[self::REL_AUTHOR] = [
+                    self::RELATIONSHIP_LINKS => [
+                        Link::RELATED => $this->createLinkToResource($author),
+                    ],
+                    self::RELATIONSHIP_DATA => $author,
+                ];
+            }
+
+            if ($news = \StudipNews::find($comment->object_id)) {
+                $relationships[self::REL_NEWS] = [
+                    self::RELATIONSHIP_LINKS => [
+                        Link::RELATED => $this->createLinkToResource($news),
+                    ],
+                    self::RELATIONSHIP_DATA => $news,
+                ];
+            }
         }
 
         return $relationships;
diff --git a/lib/classes/JsonApi/Schemas/StudipNews.php b/lib/classes/JsonApi/Schemas/StudipNews.php
index 6803fd8d29cf5cfd5f82fa3983015e2e4573a8a3..98b2dc50f7dda8d2a25205a721c79dfc058086c6 100644
--- a/lib/classes/JsonApi/Schemas/StudipNews.php
+++ b/lib/classes/JsonApi/Schemas/StudipNews.php
@@ -2,7 +2,8 @@
 
 namespace JsonApi\Schemas;
 
-use Neomerx\JsonApi\Document\Link;
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
+use Neomerx\JsonApi\Schema\Link;
 
 class StudipNews extends SchemaProvider
 {
@@ -11,8 +12,6 @@ class StudipNews extends SchemaProvider
     const REL_COMMENTS = 'comments';
     const REL_RANGES = 'ranges';
 
-    protected $resourceType = self::TYPE;
-
     public static function getRangeClasses()
     {
         return [
@@ -34,12 +33,12 @@ class StudipNews extends SchemaProvider
         ];
     }
 
-    public function getId($news)
+    public function getId($news): ?string
     {
-        return $news->news_id;
+        return $news->getId();
     }
 
-    public function getAttributes($news)
+    public function getAttributes($news, ContextInterface $context): iterable
     {
         return [
             'title' => (string) $news->topic,
@@ -55,8 +54,11 @@ class StudipNews extends SchemaProvider
     /**
      * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
-    public function getRelationships($news, $isPrimary, array $includeList)
+    public function getRelationships($news, ContextInterface $context): iterable
     {
+        $isPrimary = $context->getPosition()->getLevel() === 0;
+        $includeList = $context->getIncludePaths();
+
         $relationships = [];
 
         $relationships = $this->addAuthorRelationship($relationships, $news, $includeList);
@@ -72,10 +74,10 @@ class StudipNews extends SchemaProvider
               ? $news->owner
               : \User::build(['id' => $news->user_id], false);
         $relationships[self::REL_AUTHOR] = [
-            self::LINKS => [
-                Link::RELATED => new Link('/users/'.$news->user_id),
+            self::RELATIONSHIP_LINKS => [
+                Link::RELATED => $this->createLinkToResource($news->owner),
             ],
-            self::DATA => $data,
+            self::RELATIONSHIP_DATA => $data,
         ];
 
         return $relationships;
@@ -87,9 +89,9 @@ class StudipNews extends SchemaProvider
     private function addCommentsRelationship($relationships, $news, $includeList)
     {
         $relationships[self::REL_COMMENTS] = [
-            self::LINKS => [
-                Link::RELATED => $this->getRelationshipRelatedLink($news, self::REL_COMMENTS)
-            ]
+            self::RELATIONSHIP_LINKS => [
+                Link::RELATED => $this->getRelationshipRelatedLink($news, self::REL_COMMENTS),
+            ],
         ];
 
         return $relationships;
@@ -98,8 +100,8 @@ class StudipNews extends SchemaProvider
     private function addRangesRelationship($relationships, $news, $includeList)
     {
         $relationships[self::REL_RANGES] = [
-            self::SHOW_SELF => true,
-            self::DATA => $this->prepareRanges($news, $includeList),
+            self::RELATIONSHIP_LINKS_SELF => true,
+            self::RELATIONSHIP_DATA => $this->prepareRanges($news, $includeList),
         ];
 
         return $relationships;
@@ -112,7 +114,7 @@ class StudipNews extends SchemaProvider
         return $news->news_ranges->map(function ($range) use ($include) {
             switch ($range->type) {
                 case 'global':
-                    return $this->getDiContainer()->get('studip-system-object');
+                    return new \Jsonapi\Models\Studip();
 
                 case 'sem':
                     return $include
diff --git a/lib/classes/JsonApi/Schemas/StudipProperty.php b/lib/classes/JsonApi/Schemas/StudipProperty.php
index d76c47ac54ff2f538d9d5a5f21c8840937ed7546..2f23fa2dd0e9293b2b9268177cad80c5ad9e1145 100644
--- a/lib/classes/JsonApi/Schemas/StudipProperty.php
+++ b/lib/classes/JsonApi/Schemas/StudipProperty.php
@@ -2,18 +2,18 @@
 
 namespace JsonApi\Schemas;
 
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
+
 class StudipProperty extends SchemaProvider
 {
     const TYPE = 'studip-properties';
 
-    protected $resourceType = self::TYPE;
-
-    public function getId($resource)
+    public function getId($resource): ?string
     {
         return $resource->field;
     }
 
-    public function getAttributes($resource)
+    public function getAttributes($resource, ContextInterface $context): iterable
     {
         return [
             'description' => $resource->description,
@@ -21,10 +21,15 @@ class StudipProperty extends SchemaProvider
         ];
     }
 
+    public function getRelationships($resource, ContextInterface $context): iterable
+    {
+        return [];
+    }
+
     /**
      * @inheritdoc
      */
-    public function getResourceLinks($resource)
+    public function getLinks($resource): iterable
     {
         return [];
     }
diff --git a/lib/classes/JsonApi/Schemas/StudyArea.php b/lib/classes/JsonApi/Schemas/StudyArea.php
index 39cfae9898297b0926c26fea7688bb345970f871..6ad289bfc7e8933543e97a96107d22eaa10f5a6a 100644
--- a/lib/classes/JsonApi/Schemas/StudyArea.php
+++ b/lib/classes/JsonApi/Schemas/StudyArea.php
@@ -2,7 +2,8 @@
 
 namespace JsonApi\Schemas;
 
-use Neomerx\JsonApi\Document\Link;
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
+use Neomerx\JsonApi\Schema\Link;
 
 class StudyArea extends SchemaProvider
 {
@@ -12,14 +13,12 @@ class StudyArea extends SchemaProvider
     const REL_PARENT = 'parent';
     const TYPE = 'study-areas';
 
-    protected $resourceType = self::TYPE;
-
-    public function getId($resource)
+    public function getId($resource): ?string
     {
         return $resource['id'];
     }
 
-    public function getAttributes($resource)
+    public function getAttributes($resource, ContextInterface $context): iterable
     {
         return [
             'name' => (string) $resource['name'],
@@ -29,8 +28,11 @@ class StudyArea extends SchemaProvider
         ];
     }
 
-    public function getRelationships($resource, $isPrimary, array $includeList)
+    public function getRelationships($resource, ContextInterface $context): iterable
     {
+        $isPrimary = $context->getPosition()->getLevel() === 0;
+        $includeList = $context->getIncludePaths();
+
         $relationships = [];
 
         $shouldInclude = function ($key) use ($isPrimary, $includeList) {
@@ -48,14 +50,14 @@ class StudyArea extends SchemaProvider
     private function addChildrenRelationship(array $relationships, $resource, $includeData)
     {
         $relationships[self::REL_CHILDREN] = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_CHILDREN),
             ],
         ];
 
         if ($includeData) {
             $children = $resource->getChildren();
-            $relationships[self::REL_CHILDREN][self::DATA] = $children;
+            $relationships[self::REL_CHILDREN][self::RELATIONSHIP_DATA] = $children;
         }
 
         return $relationships;
@@ -64,14 +66,14 @@ class StudyArea extends SchemaProvider
     private function addCoursesRelationship(array $relationships, $resource, $includeData)
     {
         $relationships[self::REL_COURSES] = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_COURSES),
             ],
         ];
 
         if ($includeData) {
             $children = $resource->courses;
-            $relationships[self::REL_COURSES][self::DATA] = $children;
+            $relationships[self::REL_COURSES][self::RELATIONSHIP_DATA] = $children;
         }
 
         return $relationships;
@@ -80,13 +82,13 @@ class StudyArea extends SchemaProvider
     private function addInstituteRelationship(array $relationships, $resource, $includeData)
     {
         $relationships[self::REL_INSTITUTE] = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_INSTITUTE),
             ],
         ];
 
         if ($includeData) {
-            $relationships[self::REL_INSTITUTE][self::DATA] = $resource->institute;
+            $relationships[self::REL_INSTITUTE][self::RELATIONSHIP_DATA] = $resource->institute;
         }
 
         return $relationships;
@@ -95,13 +97,13 @@ class StudyArea extends SchemaProvider
     private function addParentRelationship(array $relationships, $resource, $includeData)
     {
         $relationships[self::REL_PARENT] = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_PARENT),
             ],
         ];
 
         if ($includeData) {
-            $relationships[self::REL_PARENT][self::DATA] = $resource->getParent();
+            $relationships[self::REL_PARENT][self::RELATIONSHIP_DATA] = $resource->getParent();
         }
 
         return $relationships;
diff --git a/lib/classes/JsonApi/Schemas/User.php b/lib/classes/JsonApi/Schemas/User.php
index 3bc904ce8ba148a06c9ab4150ad2f19332698553..67e64d56282f8c4a6e867ba1d6b4e6c40bb87fc3 100644
--- a/lib/classes/JsonApi/Schemas/User.php
+++ b/lib/classes/JsonApi/Schemas/User.php
@@ -2,7 +2,9 @@
 
 namespace JsonApi\Schemas;
 
-use Neomerx\JsonApi\Document\Link;
+use Neomerx\JsonApi\Contracts\Factories\FactoryInterface;
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
+use Neomerx\JsonApi\Schema\Link;
 
 class User extends SchemaProvider
 {
@@ -23,18 +25,12 @@ class User extends SchemaProvider
     const REL_OUTBOX = 'outbox';
     const REL_SCHEDULE = 'schedule';
 
-    /**
-     * Hier wird der Typ des Schemas festgelegt.
-     * {@inheritdoc}
-     */
-    protected $resourceType = self::TYPE;
-
     /**
      * Diese Method entscheidet über die JSON-API-spezifische ID von
      * \User-Objekten.
      * {@inheritdoc}
      */
-    public function getId($user)
+    public function getId($user): ?string
     {
         return $user->id;
     }
@@ -44,7 +40,7 @@ class User extends SchemaProvider
      * für die Ausgabe vorbereitet werden.
      * {@inheritdoc}
      */
-    public function getAttributes($user)
+    public function getAttributes($user, ContextInterface $context): iterable
     {
         $attrs = [
             'username' => $user->username,
@@ -60,10 +56,10 @@ class User extends SchemaProvider
         return $attrs + iterator_to_array($this->getProfileAttributes($user));
     }
 
-    private function getProfileAttributes(\User $user)
+    private function getProfileAttributes(\User $user): iterable
     {
         $visibilities = $this->getVisibilities($user);
-        $observer = $this->getDiContainer()->get('studip-current-user');
+        $observer = $this->currentUser;
 
         $fields = [
             ['phone', 'privatnr', 'private_phone'],
@@ -72,13 +68,15 @@ class User extends SchemaProvider
         ];
 
         foreach ($fields as list($attr, $field, $vis)) {
-            $value = ($user[$field] && is_element_visible_for_user($observer->id, $user->id, $visibilities[$vis]))
-                   ? strip_tags((string) $user[$field]) : null;
+            $value =
+                $user[$field] && is_element_visible_for_user($observer->id, $user->id, $visibilities[$vis])
+                    ? strip_tags((string) $user[$field])
+                    : null;
             yield $attr => $value;
         }
     }
 
-    private function getVisibilities(\User $user)
+    private function getVisibilities(\User $user): array
     {
         $visibilities = get_local_visibility_by_id($user->id, 'homepage');
         if (is_array(json_decode($visibilities, true))) {
@@ -91,26 +89,26 @@ class User extends SchemaProvider
     /**
      * @inheritdoc
      */
-    public function getPrimaryMeta($resource)
+    public function hasResourceMeta($resource): bool
     {
-        $avatar = \Avatar::getAvatar($resource->id);
-
-        return [
-            'avatar' => [
-                'small'    => $avatar->getURL(\Avatar::SMALL),
-                'medium'   => $avatar->getURL(\Avatar::MEDIUM),
-                'normal'   => $avatar->getURL(\Avatar::NORMAL),
-                'original' => $avatar->getURL(\Avatar::ORIGINAL)
-            ]
-         ];
+        return true;
     }
 
     /**
-     * @inheritdoc
+     * {@inheritdoc}
      */
-    public function getInclusionMeta($resource)
+    public function getResourceMeta($resource)
     {
-        return $this->getPrimaryMeta($resource);
+        $avatar = \Avatar::getAvatar($resource->id);
+
+        return [
+            'avatar' => [
+                'small' => $avatar->getURL(\Avatar::SMALL),
+                'medium' => $avatar->getURL(\Avatar::MEDIUM),
+                'normal' => $avatar->getURL(\Avatar::NORMAL),
+                'original' => $avatar->getURL(\Avatar::ORIGINAL),
+            ],
+        ];
     }
 
     /**
@@ -119,8 +117,11 @@ class User extends SchemaProvider
      * eines Nutzers bei Bedarf am \User.
      * {@inheritdoc}
      */
-    public function getRelationships($user, $isPrimary, array $includeList)
+    public function getRelationships($user, ContextInterface $context): iterable
     {
+        $isPrimary = $context->getPosition()->getLevel() === 0;
+        $includeList = $context->getIncludePaths();
+
         $shouldInclude = function ($key) use ($isPrimary, $includeList) {
             return $isPrimary && in_array($key, $includeList);
         };
@@ -140,7 +141,7 @@ class User extends SchemaProvider
             $relationships = $this->getConfigValuesRelationship(
                 $relationships,
                 $user,
-                $shouldInclude(self::REL_CONTACTS)
+                $shouldInclude(self::REL_CONFIG_VALUES)
             );
             $relationships = $this->getContactsRelationship(
                 $relationships,
@@ -157,46 +158,18 @@ class User extends SchemaProvider
                 $user,
                 $shouldInclude(self::REL_COURSE_MEMBERSHIPS)
             );
-            $relationships = $this->getEventsRelationship(
-                $relationships,
-                $user,
-                $shouldInclude(self::REL_EVENTS)
-            );
-            $relationships = $this->getFileRefsRelationship(
-                $relationships,
-                $user,
-                $shouldInclude(self::REL_FILES)
-            );
-            $relationships = $this->getFoldersRelationship(
-                $relationships,
-                $user,
-                $shouldInclude(self::REL_FOLDERS)
-            );
-            $relationships = $this->getInboxRelationship(
-                $relationships,
-                $user,
-                $shouldInclude(self::REL_INBOX)
-            );
+            $relationships = $this->getEventsRelationship($relationships, $user, $shouldInclude(self::REL_EVENTS));
+            $relationships = $this->getFileRefsRelationship($relationships, $user, $shouldInclude(self::REL_FILES));
+            $relationships = $this->getFoldersRelationship($relationships, $user, $shouldInclude(self::REL_FOLDERS));
+            $relationships = $this->getInboxRelationship($relationships, $user, $shouldInclude(self::REL_INBOX));
             $relationships = $this->getInstituteMembershipsRelationship(
                 $relationships,
                 $user,
                 $shouldInclude(self::REL_INSTITUTE_MEMBERSHIPS)
             );
-            $relationships = $this->getNewsRelationship(
-                $relationships,
-                $user,
-                $shouldInclude(self::REL_NEWS)
-            );
-            $relationships = $this->getOutboxRelationship(
-                $relationships,
-                $user,
-                $shouldInclude(self::REL_OUTBOX)
-            );
-            $relationships = $this->getScheduleRelationship(
-                $relationships,
-                $user,
-                $shouldInclude(self::REL_SCHEDULE)
-            );
+            $relationships = $this->getNewsRelationship($relationships, $user, $shouldInclude(self::REL_NEWS));
+            $relationships = $this->getOutboxRelationship($relationships, $user, $shouldInclude(self::REL_OUTBOX));
+            $relationships = $this->getScheduleRelationship($relationships, $user, $shouldInclude(self::REL_SCHEDULE));
         }
 
         return $relationships;
@@ -205,13 +178,10 @@ class User extends SchemaProvider
     /**
      * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
-    private function getActivityStreamRelationship(
-        array $relationships,
-        \User $user,
-        $includeData
-    ) {
+    private function getActivityStreamRelationship(array $relationships, \User $user, $includeData)
+    {
         $relationships[self::REL_ACTIVITYSTREAM] = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($user, self::REL_ACTIVITYSTREAM),
             ],
         ];
@@ -222,13 +192,10 @@ class User extends SchemaProvider
     /**
      * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
-    private function getBlubberRelationship(
-        array $relationships,
-        \User $user,
-        $includeData
-    ) {
+    private function getBlubberRelationship(array $relationships, \User $user, $includeData)
+    {
         $relationships[self::REL_BLUBBER] = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($user, self::REL_BLUBBER),
             ],
         ];
@@ -239,14 +206,11 @@ class User extends SchemaProvider
     /**
      * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
-    private function getConfigValuesRelationship(
-        array $relationships,
-        \User $user,
-        $includeData
-    ) {
+    private function getConfigValuesRelationship(array $relationships, \User $user, $includeData)
+    {
         $relationships[self::REL_CONFIG_VALUES] = [
-            self::SHOW_SELF => true,
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS_SELF => true,
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($user, self::REL_CONFIG_VALUES),
             ],
         ];
@@ -257,14 +221,11 @@ class User extends SchemaProvider
     /**
      * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
-    private function getContactsRelationship(
-        array $relationships,
-        \User $user,
-        $includeData
-    ) {
+    private function getContactsRelationship(array $relationships, \User $user, $includeData)
+    {
         $relationships[self::REL_CONTACTS] = [
-            self::SHOW_SELF => true,
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS_SELF => true,
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($user, self::REL_CONTACTS),
             ],
         ];
@@ -275,13 +236,10 @@ class User extends SchemaProvider
     /**
      * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
-    private function getCoursesRelationship(
-        array $relationships,
-        \User $user,
-        $includeData
-    ) {
+    private function getCoursesRelationship(array $relationships, \User $user, $includeData)
+    {
         $relationships[self::REL_COURSES] = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($user, self::REL_COURSES),
             ],
         ];
@@ -292,13 +250,10 @@ class User extends SchemaProvider
     /**
      * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
-    private function getCourseMembershipsRelationship(
-        array $relationships,
-        \User $user,
-        $includeData
-    ) {
+    private function getCourseMembershipsRelationship(array $relationships, \User $user, $includeData)
+    {
         $relationships[self::REL_COURSE_MEMBERSHIPS] = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($user, self::REL_COURSE_MEMBERSHIPS),
             ],
         ];
@@ -309,13 +264,10 @@ class User extends SchemaProvider
     /**
      * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
-    private function getFileRefsRelationship(
-        array $relationships,
-        \User $user,
-        $includeData
-    ) {
+    private function getFileRefsRelationship(array $relationships, \User $user, $includeData)
+    {
         $relationships[self::REL_FILES] = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($user, self::REL_FILES),
             ],
         ];
@@ -326,13 +278,10 @@ class User extends SchemaProvider
     /**
      * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
-    private function getFoldersRelationship(
-        array $relationships,
-        \User $user,
-        $includeData
-    ) {
+    private function getFoldersRelationship(array $relationships, \User $user, $includeData)
+    {
         $relationships[self::REL_FOLDERS] = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($user, self::REL_FOLDERS),
             ],
         ];
@@ -343,13 +292,10 @@ class User extends SchemaProvider
     /**
      * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
-    private function getInboxRelationship(
-        array $relationships,
-        \User $user,
-        $includeData
-    ) {
+    private function getInboxRelationship(array $relationships, \User $user, $includeData)
+    {
         $relationships[self::REL_INBOX] = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($user, self::REL_INBOX),
             ],
         ];
@@ -360,13 +306,10 @@ class User extends SchemaProvider
     /**
      * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
-    private function getInstituteMembershipsRelationship(
-        array $relationships,
-        \User $user,
-        $includeData
-    ) {
+    private function getInstituteMembershipsRelationship(array $relationships, \User $user, $includeData)
+    {
         $relationships[self::REL_INSTITUTE_MEMBERSHIPS] = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($user, self::REL_INSTITUTE_MEMBERSHIPS),
             ],
         ];
@@ -377,13 +320,10 @@ class User extends SchemaProvider
     /**
      * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
-    private function getEventsRelationship(
-        array $relationships,
-        \User $user,
-        $includeData
-    ) {
+    private function getEventsRelationship(array $relationships, \User $user, $includeData)
+    {
         $relationships[self::REL_EVENTS] = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($user, self::REL_EVENTS),
             ],
         ];
@@ -394,13 +334,10 @@ class User extends SchemaProvider
     /**
      * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
-    private function getNewsRelationship(
-        array $relationships,
-        \User $user,
-        $includeData
-    ) {
+    private function getNewsRelationship(array $relationships, \User $user, $includeData)
+    {
         $relationships[self::REL_NEWS] = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($user, self::REL_NEWS),
             ],
         ];
@@ -411,13 +348,10 @@ class User extends SchemaProvider
     /**
      * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
-    private function getOutboxRelationship(
-        array $relationships,
-        \User $user,
-        $includeData
-    ) {
+    private function getOutboxRelationship(array $relationships, \User $user, $includeData)
+    {
         $relationships[self::REL_OUTBOX] = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($user, self::REL_OUTBOX),
             ],
         ];
@@ -425,17 +359,13 @@ class User extends SchemaProvider
         return $relationships;
     }
 
-
     /**
      * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
-    private function getScheduleRelationship(
-        array $relationships,
-        \User $user,
-        $includeData
-    ) {
+    private function getScheduleRelationship(array $relationships, \User $user, $includeData)
+    {
         $relationships[self::REL_SCHEDULE] = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($user, self::REL_SCHEDULE),
             ],
         ];
diff --git a/lib/classes/JsonApi/Schemas/WikiPage.php b/lib/classes/JsonApi/Schemas/WikiPage.php
index d68390fa256f236e07b2bd036220a15a1da6ec0a..30d3bb92bd6919c45063aa0f140bc0777d4690a7 100644
--- a/lib/classes/JsonApi/Schemas/WikiPage.php
+++ b/lib/classes/JsonApi/Schemas/WikiPage.php
@@ -2,7 +2,8 @@
 
 namespace JsonApi\Schemas;
 
-use Neomerx\JsonApi\Document\Link;
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
+use Neomerx\JsonApi\Schema\Link;
 
 class WikiPage extends SchemaProvider
 {
@@ -15,23 +16,7 @@ class WikiPage extends SchemaProvider
     const REL_PARENT = 'parent';
     const REL_RANGE = 'range';
 
-    protected $resourceType = self::TYPE;
-
-    /**
-     * {@inheritdoc}
-     */
-    public function getResourceLinks($resource)
-    {
-        $url = $this->getDiContainer()->get('router')->pathFor(
-            'get-wiki-page',
-            ['id' => sprintf("%s_%s", $resource->range_id, $resource->keyword)]
-        );
-        $links = [ Link::SELF => $this->createLink($url) ];
-
-        return $links;
-    }
-
-    public static function getRangeClasses()
+    public static function getRangeClasses(): array
     {
         return [
             'sem' => \Course::class,
@@ -39,7 +24,7 @@ class WikiPage extends SchemaProvider
         ];
     }
 
-    public static function getRangeTypes()
+    public static function getRangeTypes(): array
     {
         return [
             'sem' => Course::TYPE,
@@ -47,6 +32,11 @@ class WikiPage extends SchemaProvider
         ];
     }
 
+    /**
+     * @param mixed $resource
+     *
+     * @return ?string
+     */
     public static function getRangeClass($resource)
     {
         $classes = self::getRangeClasses();
@@ -59,6 +49,11 @@ class WikiPage extends SchemaProvider
         ];
     }
 
+    /**
+     * @param mixed $resource
+     *
+     * @return ?string
+     */
     public static function getRangeType($resource)
     {
         $types = self::getRangeTypes();
@@ -71,7 +66,7 @@ class WikiPage extends SchemaProvider
         ];
     }
 
-    public function getId($wiki)
+    public function getId($wiki): ?string
     {
         return sprintf(
             '%s_%s',
@@ -80,7 +75,7 @@ class WikiPage extends SchemaProvider
         );
     }
 
-    public function getAttributes($wiki)
+    public function getAttributes($wiki, ContextInterface $context): iterable
     {
         return [
             'keyword' => $wiki->keyword,
@@ -90,8 +85,11 @@ class WikiPage extends SchemaProvider
         ];
     }
 
-    public function getRelationships($wiki, $isPrimary, array $includeList)
+    public function getRelationships($wiki, ContextInterface $context): iterable
     {
+        $isPrimary = $context->getPosition()->getLevel() === 0;
+        $includeList = $context->getIncludePaths();
+
         $relationships = [];
 
         if ($isPrimary) {
@@ -105,18 +103,16 @@ class WikiPage extends SchemaProvider
         return $relationships;
     }
 
-    private function addParentRelationship($relationships, $wiki, $includeList)
+    private function addParentRelationship(array $relationships, \WikiPage $wiki, array $includeList): array
     {
         $related = $wiki->parent;
         $relationships[self::REL_PARENT] = [
-            self::SHOW_SELF => true,
-            self::DATA => $related
+            self::RELATIONSHIP_LINKS_SELF => true,
+            self::RELATIONSHIP_DATA => $related
         ];
         if ($related) {
-            $relationships[self::REL_PARENT][self::LINKS] = [
-                Link::RELATED => $this->getSchemaContainer()
-                    ->getSchema($related)
-                    ->getSelfSubLink($related)
+            $relationships[self::REL_PARENT][self::RELATIONSHIP_LINKS] = [
+                Link::RELATED => $this->createLinkToResource($related),
             ];
         }
 
@@ -126,10 +122,10 @@ class WikiPage extends SchemaProvider
     /**
      * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
-    private function addChildrenRelationship($relationships, $wiki, $includeList)
+    private function addChildrenRelationship(array $relationships, \WikiPage $wiki, array $includeList): array
     {
         $relationships[self::REL_CHILDREN] = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($wiki, self::REL_CHILDREN),
             ],
         ];
@@ -140,10 +136,10 @@ class WikiPage extends SchemaProvider
     /**
      * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
-    private function addDescendantsRelationship($relationships, $wiki, $includeList)
+    private function addDescendantsRelationship(array $relationships, \WikiPage $wiki, array $includeList): array
     {
         $relationships[self::REL_DESCENDANTS] = [
-            self::LINKS => [
+            self::RELATIONSHIP_LINKS => [
                 Link::RELATED => $this->getRelationshipRelatedLink($wiki, self::REL_DESCENDANTS),
             ],
         ];
@@ -151,37 +147,54 @@ class WikiPage extends SchemaProvider
         return $relationships;
     }
 
+    /**
+     * @param array $relationships
+     * @param \WikiPage $wiki
+     * @param array $includeList
+     *
+     * @return array
+     */
     private function addAuthorRelationship($relationships, $wiki, $includeList)
     {
-        $data = in_array(self::REL_AUTHOR, $includeList)
-              ? $wiki->author
-              : \User::build(['id' => $wiki->user_id], false);
-        $relationships[self::REL_AUTHOR] = [
-            self::LINKS => [
-                Link::RELATED => new Link('/users/' . $wiki->user_id),
-            ],
-            self::DATA => $data,
-        ];
+        if ($wiki->author) {
+            $relationships[self::REL_AUTHOR] = [
+                self::RELATIONSHIP_LINKS => [
+                    Link::RELATED => $this->createLinkToResource($wiki->author),
+                ],
+                self::RELATIONSHIP_DATA => $wiki->author,
+            ];
+        }
 
         return $relationships;
     }
 
+    /**
+     * @param array $relationships
+     * @param \WikiPage $wiki
+     * @param array $includeList
+     *
+     * @return array
+     */
     private function addRangeRelationship($relationships, $wiki, $includeList)
     {
+        $range = $this->prepareRange($wiki);
         $relationships[self::REL_RANGE] = [
-            self::LINKS => [
-                Link::RELATED => new Link('/' . self::getRangeType($wiki) . '/' . $wiki->range_id),
+            self::RELATIONSHIP_LINKS => [
+                Link::RELATED => $this->createLinkToResource($range),
             ],
-            self::DATA => $this->prepareRange($wiki, $includeList),
+            self::RELATIONSHIP_DATA => $range,
         ];
 
         return $relationships;
     }
 
-    private function prepareRange($wiki)
+    private function prepareRange(\WikiPage $wiki): \Range
     {
         $class = self::getRangeClass($wiki);
 
-        return $class::build(['id' => $wiki->range_id], false);
+        /** @var \Range $range */
+        $range = $class::build(['id' => $wiki->range_id], false);
+
+        return  $range;
     }
 }
diff --git a/lib/classes/JsonApi/dependencies.php b/lib/classes/JsonApi/dependencies.php
new file mode 100644
index 0000000000000000000000000000000000000000..904c8170fbf2528954181017d6fa186506800a67
--- /dev/null
+++ b/lib/classes/JsonApi/dependencies.php
@@ -0,0 +1,62 @@
+<?php
+
+namespace JsonApi;
+
+use DI\ContainerBuilder;
+use JsonApi\JsonApiIntegration\QueryParser;
+use JsonApi\JsonApiIntegration\QueryParserInterface;
+use Neomerx\JsonApi\Contracts\Encoder\EncoderInterface;
+use Neomerx\JsonApi\Contracts\Factories\FactoryInterface;
+use Neomerx\JsonApi\Contracts\Http\Headers\HeaderParametersParserInterface;
+use Neomerx\JsonApi\Contracts\Http\Headers\MediaTypeInterface;
+use Neomerx\JsonApi\Contracts\Http\ResponsesInterface;
+use Neomerx\JsonApi\Contracts\Schema\SchemaContainerInterface;
+use Neomerx\JsonApi\Http\Headers\HeaderParametersParser;
+use Neomerx\JsonApi\Http\Headers\MediaType;
+use Psr\Container\ContainerInterface;
+
+return function (ContainerBuilder $containerBuilder) {
+    $containerBuilder->addDefinitions([
+        FactoryInterface::class => \DI\create(JsonApiIntegration\Factory::class),
+        HeaderParametersParserInterface::class => function (FactoryInterface $factory) {
+            return new HeaderParametersParser($factory);
+        },
+
+        SchemaContainerInterface::class => function (ContainerInterface $container, FactoryInterface $factory) {
+            $schemas = [];
+            $user = $container->get('studip-current-user');
+            foreach ($container->get('json-api-integration-schemas') as $key => $classname) {
+                $schemas[$key] = function ($schemaContainer) use ($classname, $factory, $user) {
+                    return new $classname($factory, $schemaContainer, $user);
+                };
+            }
+
+            return $factory->createSchemaContainer($schemas);
+        },
+
+        EncoderInterface::class => function (
+            ContainerInterface $container,
+            FactoryInterface $factory,
+            SchemaContainerInterface $schemaContainer
+        ) {
+            $urlPrefix = $container->get('json-api-integration-urlPrefix');
+            $encoder = $factory->createEncoder($schemaContainer)->withUrlPrefix($urlPrefix);
+
+            return $encoder;
+        },
+
+        QueryParserInterface::class => function (ContainerInterface $container) {
+            $request = $container->get('request');
+            $parameters = $request->getQueryParams();
+            $queryParser = new QueryParser($parameters);
+
+            return $queryParser;
+        },
+
+        ResponsesInterface::class => function (EncoderInterface $encoder) {
+            $mediaType = new MediaType(MediaTypeInterface::JSON_API_TYPE, MediaTypeInterface::JSON_API_SUB_TYPE);
+
+            return new JsonApiIntegration\Responses($encoder, $mediaType);
+        },
+    ]);
+};
diff --git a/lib/classes/JsonApi/middleware.php b/lib/classes/JsonApi/middleware.php
new file mode 100644
index 0000000000000000000000000000000000000000..7a318d5b7cad34007153df713b579536ffb2b75d
--- /dev/null
+++ b/lib/classes/JsonApi/middleware.php
@@ -0,0 +1,50 @@
+<?php
+namespace JsonApi;
+
+use Slim\App;
+
+return function (App $app) {
+    /** @var \DI\Container */
+    $container = $app->getContainer();
+
+    $app->addBodyParsingMiddleware([
+        'application/vnd.api+json' => function ($input) {
+            return json_decode($input, true);
+        },
+    ]);
+
+    // set 'request' in container
+    $app->add(function ($request, $handler) use ($container) {
+        $container->set('request', $request);
+
+        return $handler->handle($request);
+    });
+
+    $app->add(new Middlewares\StudipMockNavigation());
+    $app->add(new Middlewares\RemoveTrailingSlashes());
+
+    // Add Routing Middleware
+    $app->addRoutingMiddleware();
+
+    /** @var array|null */
+    $corsOrigin = \Config::get()->getValue('JSONAPI_CORS_ORIGIN');
+    if (is_array($corsOrigin) && count($corsOrigin)) {
+        $app->add(
+            new \Tuupola\Middleware\CorsMiddleware([
+                'origin' => $corsOrigin,
+                'methods' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
+                'headers.allow' => [
+                    'Accept',
+                    'Accept-Encoding',
+                    'Accept-Language',
+                    'Authorization',
+                    'Content-Type',
+                    'Origin',
+                ],
+                'headers.expose' => ['Etag'],
+                'credentials' => true,
+                'cache' => 86400,
+            ])
+        );
+    }
+};
diff --git a/lib/classes/JsonApi/routes.php b/lib/classes/JsonApi/routes.php
new file mode 100644
index 0000000000000000000000000000000000000000..c0f5003bf2696ef6909a43385697210191c4dd68
--- /dev/null
+++ b/lib/classes/JsonApi/routes.php
@@ -0,0 +1,8 @@
+<?php
+namespace JsonApi;
+
+use Slim\App;
+
+return function (App $app) {
+    $app->group('/v1', new RouteMap($app));
+};
diff --git a/lib/classes/JsonApi/settings.php b/lib/classes/JsonApi/settings.php
new file mode 100644
index 0000000000000000000000000000000000000000..dfb8185089c18c4b57407e078df5fd7ccc103284
--- /dev/null
+++ b/lib/classes/JsonApi/settings.php
@@ -0,0 +1,55 @@
+<?php
+
+namespace JsonApi;
+
+use DI\ContainerBuilder;
+use Neomerx\JsonApi\Contracts\Factories\FactoryInterface;
+use Neomerx\JsonApi\Contracts\Schema\SchemaContainerInterface;
+use Psr\Container\ContainerInterface;
+
+return function (ContainerBuilder $containerBuilder) {
+    // Global Settings Object
+    $containerBuilder->addDefinitions([
+        'studip-current-user' => function () {
+            if ($user = $GLOBALS['user']) {
+                return $user->getAuthenticatedUser();
+            }
+
+            return null;
+        },
+        'studip-authenticator' => function () {
+            return function ($username, $password) {
+                $check = \StudipAuthAbstract::CheckAuthentication($username, $password);
+
+                if ($check['uid'] && 'nobody' != $check['uid']) {
+                    return \User::find($check['uid']);
+                }
+
+                return null;
+            };
+        },
+
+        'json-api-integration-schemas' => function () {
+            $schemaMap = new SchemaMap();
+            $coreSchemas = $schemaMap();
+
+            $allSchemas = $coreSchemas;
+            $pluginSchemas = \PluginEngine::sendMessage(Contracts\JsonApiPlugin::class, 'registerSchemas');
+            if (is_array($pluginSchemas) && count($pluginSchemas)) {
+                foreach ($pluginSchemas as $arrayOfSchemas) {
+                    $allSchemas = array_merge($allSchemas, $arrayOfSchemas);
+                }
+            }
+
+            return $allSchemas;
+        },
+
+        'json-api-integration-urlPrefix' => function () {
+            return rtrim(\URLHelper::getUrl('jsonapi.php/v1'), '/');
+        },
+
+        'json-api-error-encoder' => function (FactoryInterface $factory) {
+            return $factory->createEncoder($factory->createSchemaContainer([]));
+        },
+    ]);
+};
diff --git a/public/jsonapi.php b/public/jsonapi.php
index 6bc83841b6325b5409aa39e39a1963ebc7b6abd4..4b3237082a3dfbab1c4af9659da3c9717f363b1f 100644
--- a/public/jsonapi.php
+++ b/public/jsonapi.php
@@ -1,30 +1,60 @@
 <?php
 
-use JsonApi\AppFactory;
-use JsonApi\RouteMap;
+use DI\ContainerBuilder;
+use Slim\Factory\AppFactory;
 
 require '../lib/bootstrap.php';
 require '../composer/autoload.php';
 
-\StudipAutoloader::addAutoloadPath($GLOBALS['STUDIP_BASE_PATH'].DIRECTORY_SEPARATOR.'vendor/oauth-php/library/');
+\StudipAutoloader::addAutoloadPath($GLOBALS['STUDIP_BASE_PATH'] . DIRECTORY_SEPARATOR . 'vendor/oauth-php/library/');
 
-page_open(
-    [
-        'sess' => 'Seminar_Session',
-        'auth' => 'Seminar_Default_Auth',
-        'perm' => 'Seminar_Perm',
-        'user' => 'Seminar_User',
-    ]
-);
+page_open([
+    'sess' => 'Seminar_Session',
+    'auth' => 'Seminar_Default_Auth',
+    'perm' => 'Seminar_Perm',
+    'user' => 'Seminar_User',
+]);
 
 // Set base url for URLHelper class
 URLHelper::setBaseUrl($GLOBALS['CANONICAL_RELATIVE_PATH_STUDIP']);
 
-// create app
-$appFactory = new AppFactory();
-$app = $appFactory->makeApp();
+$containerBuilder = new ContainerBuilder();
 
-// add routes
-$app->group('/v1', new RouteMap($app));
+$settings = require 'lib/classes/JsonApi/settings.php';
+$settings($containerBuilder);
 
+$dependencies = require 'lib/classes/JsonApi/dependencies.php';
+$dependencies($containerBuilder);
+
+// Build PHP_DI Container
+$container = $containerBuilder->build();
+
+// Instantiate the app
+AppFactory::setContainer($container);
+$app = AppFactory::create();
+$container->set(\Slim\App::class, $app);
+
+// Set the base path
+$app->setBasePath('/jsonapi.php');
+
+// Register middleware
+$middleware = require 'lib/classes/JsonApi/middleware.php';
+$middleware($app);
+
+// Register routes
+$routes = require 'lib/classes/JsonApi/routes.php';
+$routes($app);
+
+// Add Error Middleware
+$displayErrors = false;
+if (defined('\\Studip\\ENV')) {
+    $displayErrors = constant('\\Studip\\ENV') === 'development';
+}
+$logError = true;
+$logErrorDetails = true;
+
+$errorMiddleware = $app->addErrorMiddleware($displayErrors, $logError, $logErrorDetails);
+$errorMiddleware->setDefaultErrorHandler(new \JsonApi\Errors\ErrorHandler($app));
+
+// Run app
 $app->run();
diff --git a/tests/_support/Helper/Jsonapi.php b/tests/_support/Helper/Jsonapi.php
index 8cd78f7324f64a34570e94a9a5a2dad5e2b3d4df..e435cad1c7259b3502c9e25f46ab8fe9a41cd56b 100644
--- a/tests/_support/Helper/Jsonapi.php
+++ b/tests/_support/Helper/Jsonapi.php
@@ -2,12 +2,14 @@
 
 namespace Helper;
 
+use DI\ContainerBuilder;
+use JsonApi\Errors\JsonApiErrorRenderer;
 use JsonApi\Middlewares\Authentication;
-use JsonApi\Middlewares\JsonApi as JsonApiMiddleware;
 use Psr\Http\Message\ResponseInterface;
-use Slim\Http\Environment;
-use Slim\Http\Request;
-use Slim\Http\Response;
+use Slim\Factory\AppFactory;
+use Slim\Interfaces\ErrorHandlerInterface;
+use Slim\Psr7\Factory\ServerRequestFactory;
+use Slim\Psr7\Request;
 use WoohooLabs\Yang\JsonApi\Request\JsonApiRequestBuilder;
 use WoohooLabs\Yang\JsonApi\Response\JsonApiResponse;
 
@@ -17,9 +19,15 @@ use WoohooLabs\Yang\JsonApi\Response\JsonApiResponse;
 class Jsonapi extends \Codeception\Module
 {
     /**
+     * @param array    $credentials
+     * @param callable $function
+     *
+     * @return mixed
+     *
      * @SuppressWarnings(PHPMD.Superglobals)
      */
-    public function withPHPLib($credentials, $function) {
+    public function withPHPLib($credentials, $function)
+    {
         // EVIL HACK
         $oldPerm = $GLOBALS['perm'];
         $oldUser = $GLOBALS['user'];
@@ -35,112 +43,154 @@ class Jsonapi extends \Codeception\Module
         return $result;
     }
 
+    /**
+     * @param array    $credentials
+     * @param string   $method
+     * @param string   $pattern
+     * @param callable $callable
+     * @param ?string  $name
+     *
+     * @return \Slim\App
+     */
     public function createApp($credentials, $method, $pattern, $callable, $name = null)
     {
-        return $this->createApp0(
-            $credentials,
-            function () use ($method, $pattern, $callable, $name) {
-                $route = $this->map([$method], $pattern, $callable);
-                if (isset($name)) {
-                    $route->setName($name);
-                }
+        return $this->createApp0($credentials, function ($app) use ($method, $pattern, $callable, $name) {
+            $route = $app->map([strtoupper($method)], $pattern, $callable);
+            if (isset($name)) {
+                $route->setName($name);
             }
-        );
+        });
     }
 
+    /**
+     * @param array|null $credentials
+     *
+     * @return JsonApiRequestBuilder
+     */
     public function createRequestBuilder($credentials = null)
     {
-        $env = [];
+        $serverParams = [];
         if ($credentials) {
-            $env = [
+            $serverParams = [
                 'PHP_AUTH_USER' => $credentials['username'],
                 'PHP_AUTH_PW' => $credentials['password'],
             ];
         }
+        $factory = new ServerRequestFactory();
+        $request = $factory->createServerRequest('GET', '', $serverParams);
 
-        $requestBuilder = new JsonApiRequestBuilder(
-            Request::createFromEnvironment(
-                Environment::mock($env)
-            )
-        );
+        $requestBuilder = new JsonApiRequestBuilder($request);
 
-        $requestBuilder
-            ->setProtocolVersion('1.0')
-            ->setHeader('Accept-Charset', 'utf-8');
+        $requestBuilder->setProtocolVersion('1.0')->setHeader('Accept-Charset', 'utf-8');
 
         return $requestBuilder;
     }
 
+    /**
+     * @param \Slim\App $app
+     *
+     * @return JsonApiResponse
+     */
     public function sendMockRequest($app, Request $request)
     {
+        /** @var \DI\Container */
         $container = $app->getContainer();
-        $container['request'] = function ($container) use ($request) {
-            return $request;
-        };
-        $response = $app($request, new Response());
+        $container->set('request', $request);
+
+        $response = $app->handle($request);
 
         return new JsonApiResponse($response);
     }
 
-    private function createApp0($credentials, $routerFn)
+    /**
+     * @param array|null $credentials
+     *
+     * @SuppressWarnings(PHPMD.Superglobals)
+     */
+    private function createApp0($credentials, callable $routerFn): \Slim\App
     {
         $app = $this->appFactory();
 
-        $authenticator = function ($username, $password) use ($credentials) {
-            // must return a \User
-            if ($username === $credentials['username'] && $password === $credentials['password']) {
-                return \User::find($credentials['id']);
-            }
-
-            return null;
-        };
-
         $group = $app->group('', $routerFn);
 
         if ($credentials) {
-            $group->add(function ($request, $response, $next) {
-                $user = $request->getAttribute(Authentication::USER_KEY, null);
-
-                $GLOBALS['auth'] = new \Seminar_Auth();
-                $GLOBALS['auth']->auth = array(
-                    'uid' => $user->user_id,
-                    'uname' => $user->username,
-                    'perm' => $user->perms,
-                );
-
-                $GLOBALS['user'] = new \Seminar_User($user->user_id);
+            $authenticator = function ($username, $password) use ($credentials) {
+                // must return a \User
+                if ($username === $credentials['username'] && $password === $credentials['password']) {
+                    $user = \User::find($credentials['id']);
 
-                $GLOBALS['perm'] = new \Seminar_Perm();
-                $GLOBALS['MAIL_VALIDATE_BOX'] = false;
+                    return $user;
+                }
 
-                return $next($request, $response);
-            })->add(new Authentication($authenticator));
+                return null;
+            };
+
+            $group
+                ->add(function ($request, $handler) {
+                    $user = $request->getAttribute(Authentication::USER_KEY, null);
+
+                    $GLOBALS['auth'] = new \Seminar_Auth();
+                    $GLOBALS['auth']->auth = [
+                        'uid' => $user->id,
+                        'uname' => $user->username,
+                        'perm' => $user->perms,
+                    ];
+                    $GLOBALS['user'] = new \Seminar_User($user->id);
+                    $GLOBALS['perm'] = new \Seminar_Perm();
+                    $GLOBALS['MAIL_VALIDATE_BOX'] = false;
+                    $y = new \Seminar_User($user->id);
+                    $x = $y->getAuthenticatedUser();
+
+
+                    $dbManager = \DBManager::get();
+                    $stmt = $dbManager->prepare("SELECT * FROM auth_user_md5 LEFT JOIN user_info USING (user_id) WHERE user_id = ?");
+                    $stmt->execute([$user->id]);
+
+                    return $handler->handle($request);
+                })
+                ->add(new Authentication($authenticator));
         }
 
-        $group->add(new JsonApiMiddleware($app));
-
         return $app;
     }
 
-    private function appFactory()
+    private function appFactory(): \Slim\App
     {
-        $factory = new \JsonApi\AppFactory();
+        $containerBuilder = new ContainerBuilder();
+
+        $settings = require 'lib/classes/JsonApi/settings.php';
+        $settings($containerBuilder);
 
-        return $factory->makeApp();
+        $dependencies = require 'lib/classes/JsonApi/dependencies.php';
+        $dependencies($containerBuilder);
+
+        // Build PHP_DI Container
+        $container = $containerBuilder->build();
+
+        // Instantiate the app
+        AppFactory::setContainer($container);
+        $app = AppFactory::create();
+        $container->set(\Slim\App::class, $app);
+
+        // Register middleware
+        $middleware = require 'lib/classes/JsonApi/middleware.php';
+        $middleware($app);
+
+        // Add Error Middleware
+        $errorMiddleware = $app->addErrorMiddleware(true, true, true);
+        $errorMiddleware->setDefaultErrorHandler(new \JsonApi\Errors\ErrorHandler($app));
+
+        return $app;
     }
 
     public function storeJsonMD(
-        $filename,
+        string $filename,
         ResponseInterface $response,
-        $limit = null,
-        $ellipsis = null
-    ) {
+        int $limit = null,
+        string $ellipsis = null
+    ): string {
         $body = "{$response->getBody()}";
-        $body = preg_replace(
-            '!plugins.php\\\\/argonautsplugin!',
-            'https:\\/\\/example.com',
-            $body
-        );
+        $body = preg_replace('!plugins.php\\\\/argonautsplugin!', 'https:\\/\\/example.com', $body);
         $body = preg_replace('!\\\\/!', '/', $body);
         $body = preg_replace(['!%5B!', '!%5D!'], ['[', ']'], $body);
 
@@ -156,19 +206,19 @@ class Jsonapi extends \Codeception\Module
         $jsonPretty = new \Camspiers\JsonPretty\JsonPretty();
         $json = $jsonPretty->prettify($jsonBody, JSON_UNESCAPED_SLASHES, '  ');
 
-        $dirname = codecept_output_dir().'json-for-slate/';
+        $dirname = codecept_output_dir() . 'json-for-slate/';
         if (!file_exists($dirname)) {
             @mkdir($dirname);
         }
 
         if (file_exists($dirname)) {
-            if (substr($filename, -3) !== '.md') {
+            if ('.md' !== substr($filename, -3)) {
                 $filename .= '.md';
             }
-            if ($filename[0] !== '_') {
-                $filename = '_'.$filename;
+            if ('_' !== $filename[0]) {
+                $filename = '_' . $filename;
             }
-            file_put_contents($dirname.$filename, "```json\n".$json."\n```\n");
+            file_put_contents($dirname . $filename, "```json\n" . $json . "\n```\n");
         }
 
         return $json;
diff --git a/tests/_support/Helper/StudipPDO.php b/tests/_support/Helper/StudipPDO.php
deleted file mode 100644
index b090f3d333986f9614aa9fec4788bfeb81014a77..0000000000000000000000000000000000000000
--- a/tests/_support/Helper/StudipPDO.php
+++ /dev/null
@@ -1,649 +0,0 @@
-<?php
-/**
- * StudipPDO.class.php - Stud.IP PDO class.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * @author      Elmar Ludwig
- * @license     http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
- *
- * @category    Stud.IP
- */
-
-/**
- * This is a special variant of the standard PDO class that does
- * not allow multiple statement execution.
- */
-class StudipPDO extends PDO
-{
-    const PARAM_ARRAY = 100;
-    const PARAM_COLUMN = 101;
-
-    // Counter for the queries sent to the database
-    public $query_count = 0;
-
-    /**
-     * Verifies that the given SQL query only contains a single statement.
-     *
-     * @param string    SQL statement to check
-     *
-     * @throws PDOException when the query contains multiple statements
-     */
-    protected function verify($statement)
-    {
-        if (mb_strpos($statement, ';') !== false) {
-            if (preg_match('/;\s*\S/', self::replaceStrings($statement))) {
-                throw new PDOException('multiple statement execution not allowed');
-            }
-        }
-
-        // Count executed queries (this is placed here since this is the only
-        // method that is executed on every call to the database)
-        $this->query_count += 1;
-    }
-
-    /**
-     * Replaces all string literals in the statement with placeholders.
-     *
-     * @param string    SQL statement
-     *
-     * @return string modified SQL statement
-     */
-    protected static function replaceStrings($statement)
-    {
-        $count = mb_substr_count($statement, '"') + mb_substr_count($statement, "'") + mb_substr_count($statement, '\\');
-
-        // use fast preg_replace() variant if possible
-        if ($count < 1000) {
-            $result = preg_replace('/"(""|\\\\.|[^\\\\"]+)*"|\'(\'\'|\\\\.|[^\\\\\']+)*\'/s', '?', $statement);
-        }
-
-        if (!isset($result)) {
-            // split string into parts at quotes and backslash
-            $parts = preg_split('/([\\\\"\'])/', $statement, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
-            $result = '';
-
-            for ($part = current($parts); $part !== false; $part = next($parts)) {
-                // inside quotes, "" is ", '' is ' and \x is x
-                if ($quote_chr !== null) {
-                    if ($part === $quote_chr) {
-                        $part = next($parts);
-
-                        if ($part !== $quote_chr) {
-                            // backtrack and terminate string
-                            prev($parts);
-                            $result .= '?';
-                            $quote_chr = null;
-                        }
-                    } elseif ($part === '\\') {
-                        // skip next part
-                        next($parts);
-                    }
-                } elseif ($part === "'" || $part === '"') {
-                    $quote_chr = $part;
-                    $saved_pos = key($parts);
-                } else {
-                    $result .= $part;
-                }
-            }
-
-            if ($quote_chr !== null) {
-                // unterminated quote: copy to end of string
-                $result .= implode(array_slice($parts, $saved_pos));
-            }
-        }
-
-        return $result;
-    }
-
-    /**
-     * Quotes the given value in a form appropriate for the type.
-     * If no explicit type is given, the value's PHP type is used.
-     *
-     * @param string    PHP value to quote
-     * @param int       parameter type (e.g. PDO::PARAM_STR)
-     *
-     * @return string quoted SQL string
-     */
-    public function quote($value, $type = null)
-    {
-        if (!isset($type)) {
-            if (is_null($value)) {
-                $type = PDO::PARAM_NULL;
-            } elseif (is_bool($value)) {
-                $type = PDO::PARAM_BOOL;
-            } elseif (is_int($value)) {
-                $type = PDO::PARAM_INT;
-            } elseif (is_array($value)) {
-                $type = self::PARAM_ARRAY;
-            } else {
-                $type = PDO::PARAM_STR;
-            }
-        }
-
-        switch ($type) {
-            case PDO::PARAM_NULL:
-                return 'NULL';
-            case PDO::PARAM_BOOL:
-                return $value ? '1' : '0';
-            case PDO::PARAM_INT:
-                return (int) $value;
-            case self::PARAM_ARRAY:
-                return is_array($value) && count($value) ? join(',', array_map(array($this, 'quote'), $value)) : 'NULL';
-            case self::PARAM_COLUMN:
-                return preg_replace('/\\W/', '', $value);
-            default:
-                return parent::quote($value);
-        }
-    }
-
-    /**
-     * Executes an SQL statement and returns the number of affected rows.
-     *
-     * @param string    SQL statement
-     *
-     * @return int number of affected rows
-     */
-    public function exec($statement)
-    {
-        $this->verify($statement);
-
-        return parent::exec($statement);
-    }
-
-    /**
-     * Executes an SQL statement, returning a result set as a statement object.
-     *
-     * @param string    SQL statement
-     * @param int       fetch mode (optional)
-     * @param mixed     fetch mode parameter (see PDOStatement::setFetchMode)
-     * @param mixed     fetch mode parameter (see PDOStatement::setFetchMode)
-     *
-     * @return object PDOStatement object
-     */
-    public function query($statement, $mode = null, $arg1 = null, $arg2 = null)
-    {
-        $this->verify($statement);
-
-        if (isset($mode)) {
-            $stmt = parent::query($statement, $mode, $arg1, $arg2);
-        } else {
-            $stmt = parent::query($statement);
-        }
-
-        $studip_stmt = new StudipPDOStatement($this, $statement, array());
-        $studip_stmt->setStatement($stmt);
-
-        return $studip_stmt;
-    }
-
-    /**
-     * Prepares a statement for execution and returns a statement object.
-     *
-     * @param string    SQL statement
-     *
-     * @return object PDOStatement object
-     */
-    public function prepare($statement, $driver_options = array())
-    {
-        $this->verify($statement);
-
-        return new StudipPDOStatement($this, $statement, $driver_options);
-    }
-
-    /**
-     * This method is intended only for use by the StudipPDOStatement class.
-     *
-     * @param string    SQL statement
-     *
-     * @return object PDOStatement object
-     */
-    public function prepareStatement($statement, $driver_options = array())
-    {
-        return parent::prepare($statement, $driver_options);
-    }
-
-    /**
-     * Executes sql statement with given parameters,
-     * returns number of affected rows, use only for INSERT,UPDATE etc.
-     *
-     * @param string $statement        SQL statement to execute
-     * @param array  $input_parameters parameters for statement
-     *
-     * @return int number of affected rows
-     */
-    public function execute($statement, $input_parameters = null)
-    {
-        $st = $this->prepare($statement);
-        $ok = $st->execute($input_parameters);
-        if ($ok === true) {
-            return $st->rowCount();
-        }
-    }
-
-    /**
-     * Executes sql statement with given parameters, and fetch results
-     * as sequential array, each row as associative array
-     * optionally apply given callable on each row, with current row and key as parameter.
-     *
-     * @param string   $statement        SQL statement to execute
-     * @param array    $input_parameters parameters for statement
-     * @param callable $callable         callable to be applied to each of the rows
-     *
-     * @return array result set as array of assoc arrays
-     */
-    public function fetchAll($statement, $input_parameters = null, $callable = null)
-    {
-        $st = $this->prepare($statement);
-        $st->execute($input_parameters);
-        if (is_callable($callable)) {
-            $data = array();
-            $st->setFetchMode(PDO::FETCH_ASSOC);
-            foreach ($st as $key => $row) {
-                $data[$key] = call_user_func($callable, $row, $key);
-            }
-        } else {
-            $data = $st->fetchAll(PDO::FETCH_ASSOC);
-        }
-
-        return $data;
-    }
-
-    /**
-     * Executes sql statement with given parameters, and fetch only
-     * the values from first column as sequential array
-     * optionally apply given callable on each row, with current value and key as parameter.
-     *
-     * @see StudipPDOStatement::fetchFirst()
-     *
-     * @param string   $statement        SQL statement to execute
-     * @param array    $input_parameters parameters for statement
-     * @param callable $callable         callable to be applied to each of the rows
-     *
-     * @return array result set
-     */
-    public function fetchFirst($statement, $input_parameters = null, $callable = null)
-    {
-        $st = $this->prepare($statement);
-        $st->execute($input_parameters);
-        $data = $st->fetchFirst();
-        if (is_callable($callable)) {
-            foreach ($data as $key => $row) {
-                $data[$key] = call_user_func($callable, $row, $key);
-            }
-        }
-
-        return $data;
-    }
-
-    /**
-     * Executes sql statement with given parameters, and fetch results
-     * as associative array, first columns value is used as a key, the others are grouped
-     * optionally apply given callable on each grouped row, with current row and key as parameter
-     * if no callable is given, 'current' is used, to return the first entry of the grouped row.
-     *
-     * @see StudipPDOStatement::fetchGrouped()
-     *
-     * @param string   $statement        SQL statement to execute
-     * @param array    $input_parameters parameters for statement
-     * @param callable $callable         callable to be applied to each of the rows
-     *
-     * @return array result set
-     */
-    public function fetchGrouped($statement, $input_parameters = null, $callable = null)
-    {
-        $st = $this->prepare($statement);
-        $st->execute($input_parameters);
-        $data = $st->fetchGrouped(PDO::FETCH_ASSOC, is_null($callable) ? 'current' : null);
-        if (is_callable($callable)) {
-            foreach ($data as $key => $row) {
-                $data[$key] = call_user_func($callable, $row, $key);
-            }
-        }
-
-        return $data;
-    }
-
-    /**
-     * Executes sql statement with given parameters, and fetch results
-     * as associative array, first columns value is used as a key, the other one is grouped
-     * use only when selecting 2 columns
-     * optionally apply given callable on each grouped row, with current row and key as parameter.
-     *
-     * @see StudipPDOStatement::fetchGroupedPairs()
-     *
-     * @param string   $statement        SQL statement to execute
-     * @param array    $input_parameters parameters for statement
-     * @param callable $callable         callable to be applied to each of the rows
-     *
-     * @return array result set
-     */
-    public function fetchGroupedPairs($statement, $input_parameters = null, $callable = null)
-    {
-        $st = $this->prepare($statement);
-        $st->execute($input_parameters);
-        $data = $st->fetchGroupedPairs();
-        if (is_callable($callable)) {
-            foreach ($data as $key => $row) {
-                $data[$key] = call_user_func($callable, $row, $key);
-            }
-        }
-
-        return $data;
-    }
-
-    /**
-     * Executes sql statement with given parameters, and fetch results
-     * as associative array, first columns value is used as a key, the other one as the value
-     * use only when selecting 2 columns
-     * optionally apply given callable on each grouped row, with current row and key as parameter.
-     *
-     * @see StudipPDOStatement::fetchGroupedPairs()
-     *
-     * @param string   $statement        SQL statement to execute
-     * @param array    $input_parameters parameters for statement
-     * @param callable $callable         callable to be applied to each of the rows
-     *
-     * @return array result set
-     */
-    public function fetchPairs($statement, $input_parameters = null, $callable = null)
-    {
-        $st = $this->prepare($statement);
-        $st->execute($input_parameters);
-        $data = $st->fetchPairs();
-        if (is_callable($callable)) {
-            foreach ($data as $key => $row) {
-                $data[$key] = call_user_func($callable, $row, $key);
-            }
-        }
-
-        return $data;
-    }
-
-    /**
-     * Executes sql statement with given parameters, and fetch only the first row
-     * as associative array.
-     *
-     * @see StudipPDOStatement::fetchOne()
-     *
-     * @param string $statement        SQL statement to execute
-     * @param array  $input_parameters parameters for statement
-     *
-     * @return array first row of result set
-     */
-    public function fetchOne($statement, $input_parameters = null)
-    {
-        $st = $this->prepare($statement);
-        $st->execute($input_parameters);
-
-        return $st->fetchOne();
-    }
-
-    /**
-     * Executes sql statement with given parameters, and fetch only the value of one column
-     * third param denotes the column, zero indexed.
-     *
-     * @param string $statement        SQL statement to execute
-     * @param array  $input_parameters parameters for statement
-     * @param int    $column           number of column to fetch
-     *
-     * @return string value of chosen column
-     */
-    public function fetchColumn($statement, $input_parameters = null, $column = 0)
-    {
-        $st = $this->prepare($statement);
-        $st->execute($input_parameters);
-
-        return $st->fetchColumn($column);
-    }
-}
-
-/**
- * This is a "fake" PDOStatement implementation that behaves mostly like
- * a real statement object, but has some additional features:.
- *
- * - Parameters passed to execute() are quoted according to their PHP type.
- * - A PHP NULL value will result in an actual SQL NULL value in the query.
- * - Array types are supported for all placeholders ("WHERE value IN (?)").
- * - Positional and named parameters can be mixed in the same query.
- */
-class StudipPDOStatement implements IteratorAggregate
-{
-    protected $db;
-    protected $query;
-    protected $options;
-    protected $columns;
-    protected $params;
-    protected $count;
-    protected $stmt;
-
-    /**
-     * Initializes a new StudipPDOStatement instance.
-     */
-    public function __construct($db, $query, $options)
-    {
-        $this->db = $db;
-        $this->query = $query;
-        $this->options = $options;
-        $this->params = array();
-    }
-
-    /**
-     * Injects a PDOStatement.
-     */
-    public function setStatement(PDOStatement $statement)
-    {
-        $this->stmt = $statement;
-    }
-
-    /**
-     * Arranges to have a particular variable bound to a given column in
-     * the result-set from a query. Each call to fetch() or fetchAll()
-     * will update all the variables that are bound to columns.
-     */
-    public function bindColumn($column, &$param/*, ...*/)
-    {
-        $args = func_get_args();
-        $args[1] = &$param;
-        $this->columns[] = $args;
-
-        return true;
-    }
-
-    /**
-     * Binds a PHP variable to a corresponding named or question mark place-
-     * holder in the SQL statement that was used to prepare the statement.
-     * Unlike bindValue(), the variable is bound as a reference and will
-     * only be evaluated at the time that execute() is called.
-     */
-    public function bindParam($parameter, &$variable, $data_type = null)
-    {
-        if (is_string($parameter) && $parameter[0] !== ':') {
-            $parameter = ':'.$parameter;
-        }
-
-        $this->params[$parameter] = array('value' => &$variable, 'type' => $data_type);
-
-        return true;
-    }
-
-    /**
-     * Binds a value to a corresponding named or question mark placeholder
-     * in the SQL statement that was used to prepare the statement.
-     */
-    public function bindValue($parameter, $value, $data_type = null)
-    {
-        if (is_string($parameter) && $parameter[0] !== ':') {
-            $parameter = ':'.$parameter;
-        }
-
-        $this->params[$parameter] = array('value' => $value, 'type' => $data_type);
-
-        return true;
-    }
-
-    /**
-     * Forwards all unknown methods to the actual statement object.
-     */
-    public function __call($name, array $arguments)
-    {
-        $callable = array($this->stmt, $name);
-        if (!is_callable($callable)) {
-            throw new BadMethodCallException();
-        }
-
-        return call_user_func_array($callable, $arguments);
-    }
-
-    /**
-     * Forwards all Iterator methods to the actual statement object.
-     */
-    public function getIterator()
-    {
-        return $this->stmt;
-    }
-
-    /**
-     * Executes the prepared statement and returns a PDOStatement object.
-     */
-    public function execute($input_parameters = null)
-    {
-        // bind additional parameters from execute()
-        if (isset($input_parameters)) {
-            foreach ($input_parameters as $key => $value) {
-                $this->bindValue(is_int($key) ? $key + 1 : $key, $value, null);
-            }
-        }
-
-        // emulate prepared statement if necessary
-        foreach ($this->params as $key => $param) {
-            if ($param['type'] === StudipPDO::PARAM_ARRAY ||
-                $param['type'] === StudipPDO::PARAM_COLUMN ||
-                $param['type'] === null && !is_string($param['value'])) {
-                $emulate_prepare = true;
-                break;
-            }
-        }
-
-        // build the actual query string and prepared statement
-        if ($emulate_prepare) {
-            $this->count = 1;
-            $query = preg_replace_callback('/\?|:\w+/', array($this, 'replaceParam'), $this->query);
-        } else {
-            $query = $this->query;
-        }
-
-        $this->stmt = $this->db->prepareStatement($query, $this->options);
-
-        // bind query parameters on the actual statement
-        if (!$emulate_prepare) {
-            foreach ($this->params as $key => $param) {
-                $this->stmt->bindValue($key, $param['value'], $param['type']);
-            }
-        }
-
-        // set up column bindings on the actual statement
-        if (isset($this->columns)) {
-            foreach ($this->columns as $args) {
-                call_user_func_array(array($this->stmt, 'bindColumn'), $args);
-            }
-        }
-
-        return $this->stmt->execute();
-    }
-
-    /**
-     * Replaces a placeholder with the corresponding parameter value.
-     * Throws an exception if there is no corresponding value.
-     */
-    protected function replaceParam($matches)
-    {
-        $name = $matches[0];
-
-        if ($name == '?') {
-            $key = $this->count++;
-        } else {
-            $key = $name;
-        }
-
-        if (!isset($this->params[$key])) {
-            throw new PDOException('missing parameter in query: '.$key);
-        }
-
-        return $this->db->quote($this->params[$key]['value'], $this->params[$key]['type']);
-    }
-
-    /**
-     * Returns the result set rows as a grouped associative array. The first field
-     * of each row is used as the array's keys.
-     * optionally apply given callable on each grouped row to aggregate results
-     * if no callable is given, 'current' is used, to return the first entry of the grouped row.
-     *
-     * @param int      $fetch_style Either PDO::FETCH_ASSOC or PDO::FETCH_COLUMN
-     * @param callable $group_func  function to aggregate grouped rows
-     *
-     * @return array grouped result set
-     */
-    public function fetchGrouped($fetch_style = PDO::FETCH_ASSOC, $group_func = 'current')
-    {
-        if (!($fetch_style & (PDO::FETCH_ASSOC | PDO::FETCH_COLUMN))) {
-            throw new PDOException('Fetch style not supported, try FETCH_ASSOC or FETCH_COLUMN');
-        }
-
-        $fetch_style |= PDO::FETCH_GROUP;
-        $rows = $this->fetchAll($fetch_style);
-
-        return is_callable($group_func) ? array_map($group_func, $rows) : $rows;
-    }
-
-    /**
-     * Returns the result set rows as a grouped associative array. The first field
-     * of each row is used as the array's keys, the other one is grouped
-     * use only when selecting 2 columns
-     * optionally apply given callable on each grouped row to aggregate results.
-     *
-     * @param callable $group_func function to aggregate grouped rows
-     *
-     * @return array grouped result set
-     */
-    public function fetchGroupedPairs($group_func = null)
-    {
-        return $this->fetchGrouped(PDO::FETCH_COLUMN, $group_func);
-    }
-
-    /**
-     * Returns result rows as associative array, first colum as key,
-     * second as value. Use only when selecting 2 columns.
-     *
-     * @return array result set
-     */
-    public function fetchPairs()
-    {
-        return $this->fetchAll(PDO::FETCH_KEY_PAIR);
-    }
-
-    /**
-     * Returns sequential array with values from first colum.
-     *
-     * @return array first row result set
-     */
-    public function fetchFirst()
-    {
-        return $this->fetchAll(PDO::FETCH_COLUMN);
-    }
-
-    /**
-     * Returns only first row of result set as associative array.
-     *
-     * @return array first row result set
-     */
-    public function fetchOne()
-    {
-        $data = $this->fetch(PDO::FETCH_ASSOC);
-
-        return $data ?: array();
-    }
-}
diff --git a/tests/_support/_generated/JsonapiTesterActions.php b/tests/_support/_generated/JsonapiTesterActions.php
index e4f4bde5a6d525277a2fb121afcdbee1cc83967c..9faaeec96dbbe35f2edf4acde9118df8e197d724 100644
--- a/tests/_support/_generated/JsonapiTesterActions.php
+++ b/tests/_support/_generated/JsonapiTesterActions.php
@@ -2096,6 +2096,11 @@ trait JsonapiTesterActions
     /**
      * [!] Method is generated. Documentation taken from corresponding module.
      *
+     * @param array    $credentials
+     * @param callable $function
+     *
+     * @return mixed
+     *
      * @SuppressWarnings(PHPMD.Superglobals)
      * @see \Helper\Jsonapi::withPHPLib()
      */
@@ -2107,7 +2112,13 @@ trait JsonapiTesterActions
     /**
      * [!] Method is generated. Documentation taken from corresponding module.
      *
+     * @param array    $credentials
+     * @param string   $method
+     * @param string   $pattern
+     * @param callable $callable
+     * @param ?string  $name
      *
+     * @return \Slim\App
      * @see \Helper\Jsonapi::createApp()
      */
     public function createApp($credentials, $method, $pattern, $callable, $name = NULL) {
@@ -2118,7 +2129,9 @@ trait JsonapiTesterActions
     /**
      * [!] Method is generated. Documentation taken from corresponding module.
      *
+     * @param array|null $credentials
      *
+     * @return JsonApiRequestBuilder
      * @see \Helper\Jsonapi::createRequestBuilder()
      */
     public function createRequestBuilder($credentials = NULL) {
@@ -2129,10 +2142,12 @@ trait JsonapiTesterActions
     /**
      * [!] Method is generated. Documentation taken from corresponding module.
      *
+     * @param \Slim\App $app
      *
+     * @return JsonApiResponse
      * @see \Helper\Jsonapi::sendMockRequest()
      */
-    public function sendMockRequest($app, \Slim\Http\Request $request) {
+    public function sendMockRequest($app, \Slim\Psr7\Request $request) {
         return $this->getScenario()->runStep(new \Codeception\Step\Action('sendMockRequest', func_get_args()));
     }
 
@@ -2143,7 +2158,7 @@ trait JsonapiTesterActions
      *
      * @see \Helper\Jsonapi::storeJsonMD()
      */
-    public function storeJsonMD($filename, \Psr\Http\Message\ResponseInterface $response, $limit = NULL, $ellipsis = NULL) {
+    public function storeJsonMD(string $filename, \Psr\Http\Message\ResponseInterface $response, ?int $limit = NULL, ?string $ellipsis = NULL): string {
         return $this->getScenario()->runStep(new \Codeception\Step\Action('storeJsonMD', func_get_args()));
     }
 }
diff --git a/tests/jsonapi/BlubberCommentsUpdateTest.php b/tests/jsonapi/BlubberCommentsUpdateTest.php
index 7ee569da2877aa1bdb5b97575484bc2183ab5b55..d9f4a1e705baaf80ea7a3fb3cc3a7aa9e6425983 100644
--- a/tests/jsonapi/BlubberCommentsUpdateTest.php
+++ b/tests/jsonapi/BlubberCommentsUpdateTest.php
@@ -50,13 +50,9 @@ class BlubberCommentsUpdateTest extends \Codeception\Test\Unit
         $comment = $this->createBlubberComment($credentialsAutor, $thread, 'Autolykos knows him.');
         $this->tester->assertEquals($num + 1, \BlubberComment::countBySQL('1'));
 
-        $this->tester->expectThrowable(\JsonApi\Errors\AuthorizationFailedException::class, function () use (
-            $credentialsDozent,
-            $comment
-        ) {
-            $content = 'Who knows Erginos?';
-            $this->updateBlubberCommentJSONAPI($credentialsDozent, $comment, $content);
-        });
+        $content = 'Who knows Erginos?';
+        $response = $this->updateBlubberCommentJSONAPI($credentialsDozent, $comment, $content);
+        $this->tester->assertSame(403, $response->getStatusCode());
     }
 
     public function testUpdateOtherCommentSuccess()
diff --git a/tests/jsonapi/BlubberThreadsCreateTest.php b/tests/jsonapi/BlubberThreadsCreateTest.php
index 9bcbf41626345534c5b7576195fdaab1c8676527..4b275d54249107b3d62fbbb459e792610dbc8b6f 100644
--- a/tests/jsonapi/BlubberThreadsCreateTest.php
+++ b/tests/jsonapi/BlubberThreadsCreateTest.php
@@ -69,14 +69,14 @@ class BlubberThreadsCreateTest extends \Codeception\Test\Unit
         // given
         $credentials = $this->tester->getCredentialsForTestAutor();
 
-        $this->expectException(JsonApi\Errors\BadRequestException::class);
-        $this->createThread($credentials, 'course');
+        $response = $this->createThread($credentials, 'course');
+        $this->tester->assertSame(400, $response->getStatusCode());
 
-        $this->expectException(JsonApi\Errors\BadRequestException::class);
-        $this->createThread($credentials, 'institute');
+        $response = $this->createThread($credentials, 'institute');
+        $this->tester->assertSame(400, $response->getStatusCode());
 
-        $this->expectException(JsonApi\Errors\BadRequestException::class);
-        $this->createThread($credentials, 'public');
+        $response = $this->createThread($credentials, 'public');
+        $this->tester->assertSame(400, $response->getStatusCode());
     }
 
 
diff --git a/tests/jsonapi/BlubberThreadsIndexTest.php b/tests/jsonapi/BlubberThreadsIndexTest.php
index 304f6b1d75f5dc4887699debd26923c719792206..ea665ab108221849bbd3accf4d88e21d588bb869 100644
--- a/tests/jsonapi/BlubberThreadsIndexTest.php
+++ b/tests/jsonapi/BlubberThreadsIndexTest.php
@@ -118,10 +118,9 @@ class BlubberThreadsIndexTest extends \Codeception\Test\Unit
         // given
         $credentials = $this->tester->getCredentialsForTestAutor();
 
-        $this->tester->expectThrowable(RecordNotFoundException::class, function () use ($credentials) {
-            $courseId = 'missing';
-            $this->fetchCourseThreads($credentials, $courseId);
-        });
+        $courseId = 'missing';
+        $response = $this->fetchCourseThreads($credentials, $courseId);
+        $this->tester->assertSame(404, $response->getStatusCode());
     }
 
     public function testIndexAllThreadsOfAnInstitute()
@@ -151,10 +150,9 @@ class BlubberThreadsIndexTest extends \Codeception\Test\Unit
         // given
         $credentials = $this->tester->getCredentialsForTestAutor();
 
-        $this->tester->expectThrowable(RecordNotFoundException::class, function () use ($credentials) {
-            $instituteId = 'missing';
-            $this->fetchInstituteThreads($credentials, $instituteId);
-        });
+        $instituteId = 'missing';
+        $response = $this->fetchInstituteThreads($credentials, $instituteId);
+        $this->tester->assertSame(404, $response->getStatusCode());
     }
 
     public function testIndexAllThreadsWithSinceFilter()
@@ -265,7 +263,8 @@ class BlubberThreadsIndexTest extends \Codeception\Test\Unit
         );
     }
 
-    private function fetchCourseThreads(array $credentials, string $courseId, array $filters = [])
+    private function fetchCourseThreads
+        (array $credentials, string $courseId, array $filters = [])
     {
         $requestBuilder = $this->tester
                         ->createRequestBuilder($credentials)
diff --git a/tests/jsonapi/FileRefsCreateTest.php b/tests/jsonapi/FileRefsCreateTest.php
index 1e3c5b535a80edd12ce9af5dae847fdb996c74ea..d58e1813c2efdc6cce2c2f6f76c7cbc7bf3be22b 100644
--- a/tests/jsonapi/FileRefsCreateTest.php
+++ b/tests/jsonapi/FileRefsCreateTest.php
@@ -1,10 +1,10 @@
-<?php
-
+><?php
 use JsonApi\Errors\RecordNotFoundException;
 use JsonApi\Errors\UnprocessableEntityException;
-use JsonApi\Routes\Files\FileRefsCreate;
+use JsonApi\Routes\Files\NegotiateFileRefsCreate as FileRefsCreate;
 use JsonApi\Schemas\ContentTermsOfUse;
 use JsonApi\Schemas\FileRef;
+use Slim\Psr7\Factory\ServerRequestFactory;
 
 require_once 'FilesTestHelper.php';
 
@@ -36,13 +36,7 @@ class FileRefsCreateTest extends \Codeception\Test\Unit
         $name = 'filename.jpg';
         $description = 'a description';
 
-        $response = $this->sendCreateFileRefInFolder(
-            $credentials,
-            $folder,
-            $name,
-            $description,
-            $license
-        );
+        $response = $this->sendCreateFileRefInFolder($credentials, $folder, $name, $description, $license);
 
         $this->assertFileRefCreated($response, $name, $description, $license);
     }
@@ -56,18 +50,8 @@ class FileRefsCreateTest extends \Codeception\Test\Unit
         $name = 'filename.jpg';
         $description = 'a description';
 
-        $this->tester->expectThrowable(
-            RecordNotFoundException::class,
-            function () use ($credentials, $missingFolder, $name, $description, $license) {
-                $this->sendCreateFileRefInFolder(
-                    $credentials,
-                    $missingFolder,
-                    $name,
-                    $description,
-                    $license
-                );
-            }
-        );
+        $response = $this->sendCreateFileRefInFolder($credentials, $missingFolder, $name, $description, $license);
+        $this->tester->assertSame(404, $response->getStatusCode());
     }
 
     public function testShouldFailOnEmptyName()
@@ -80,18 +64,8 @@ class FileRefsCreateTest extends \Codeception\Test\Unit
         $name = '';
         $description = 'a description';
 
-        $this->tester->expectThrowable(
-            UnprocessableEntityException::class,
-            function () use ($credentials, $folder, $name, $description, $license) {
-                $this->sendCreateFileRefInFolder(
-                    $credentials,
-                    $folder,
-                    $name,
-                    $description,
-                    $license
-                );
-            }
-        );
+        $response = $this->sendCreateFileRefInFolder($credentials, $folder, $name, $description, $license);
+        $this->tester->assertSame(422, $response->getStatusCode());
     }
 
     public function testShouldFailOnMissingLicense()
@@ -103,18 +77,8 @@ class FileRefsCreateTest extends \Codeception\Test\Unit
         $name = 'a-real-filename.gif';
         $description = 'a description';
 
-        $this->tester->expectThrowable(
-            UnprocessableEntityException::class,
-            function () use ($credentials, $folder, $name, $description) {
-                $this->sendCreateFileRefInFolder(
-                    $credentials,
-                    $folder,
-                    $name,
-                    $description,
-                    null
-                );
-            }
-        );
+        $response = $this->sendCreateFileRefInFolder($credentials, $folder, $name, $description, null);
+        $this->tester->assertSame(422, $response->getStatusCode());
     }
 
     public function testShouldCreateLinkIfSameUser()
@@ -133,8 +97,8 @@ class FileRefsCreateTest extends \Codeception\Test\Unit
             $credentials,
             $folder,
             $file,
-            $name = "another-name.jpg",
-            $description = "another description",
+            $name = 'another-name.jpg',
+            $description = 'another description',
             $license
         );
 
@@ -162,8 +126,8 @@ class FileRefsCreateTest extends \Codeception\Test\Unit
             $credentialsAutor,
             $folder,
             $file,
-            $name = "another-name.jpg",
-            $description = "another description",
+            $name = 'another-name.jpg',
+            $description = 'another description',
             $license
         );
 
@@ -174,20 +138,47 @@ class FileRefsCreateTest extends \Codeception\Test\Unit
         $this->assertFileRefCreated($response, $name, $description, $license);
     }
 
+    public function testShouldCreateFileRefByUpload()
+    {
+        $credentials = $this->tester->getCredentialsForTestDozent();
+        $courseId = 'a07535cf2f8a72df33c12ddfa4b53dde';
+        $folder = $this->prepareTopFolder($credentials, $courseId);
+        $license = $this->getSampleLicense();
+
+        $name = 'tiny.gif';
+        $filename = __DIR__ . '/' . $name;
+        $description = 'a description';
+        $content = base64_decode('R0lGODlhAQABAIABAP///wAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==');
+        if (!file_exists($filename)) {
+            file_put_contents($filename, $content);
+        }
+        $this->tester->assertTrue(file_exists($filename));
+        $file = new \Slim\Psr7\UploadedFile($this->fileToStreamInterface($filename), $name);
+
+        $app = $this->tester->createApp($credentials, 'POST', '/folders/{id}/file-refs', FileRefsCreate::class);
+
+        $factory = new ServerRequestFactory();
+        $serverParams = [
+            'PHP_AUTH_USER' => $credentials['username'],
+            'PHP_AUTH_PW' => $credentials['password'],
+        ];
+        $request = $factory->createServerRequest('POST', '/folders/' . $folder->id . '/file-refs', $serverParams);
+        $request = $request->withUploadedFiles([$file])->withHeader('Content-Type', 'multipart/form-data');
+
+        $response = $this->tester->sendMockRequest($app, $request);
+        $this->tester->assertSame(201, $response->getStatusCode());
+        $this->tester->assertArrayHasKey('Location', $response->getHeaders());
+    }
+
     // **** helper functions ****
     private function sendCreateFileRefInFolder($user, $folder, $name, $description, $license)
     {
-        $app = $this->tester->createApp(
-            $user,
-            'POST',
-            '/folders/{id}/file-refs',
-            FileRefsCreate::class
-        );
+        $app = $this->tester->createApp($user, 'POST', '/folders/{id}/file-refs', FileRefsCreate::class);
 
         $requestBuilder = $this->tester->createRequestBuilder($user);
         $requestBuilder
             ->setJsonApiBody($this->prepareValidFileRefBody($name, $description, $license))
-            ->setUri('/folders/'.($folder->id).'/file-refs')
+            ->setUri('/folders/' . $folder->id . '/file-refs')
             ->create();
 
         return $this->tester->sendMockRequest($app, $requestBuilder->getRequest());
@@ -195,24 +186,12 @@ class FileRefsCreateTest extends \Codeception\Test\Unit
 
     private function sendCopyFileInFolder($credentials, $folder, $file, $name, $description, $license)
     {
-        $app = $this->tester->createApp(
-            $credentials,
-            'POST',
-            '/folders/{id}/file-refs',
-            FileRefsCreate::class
-        );
+        $app = $this->tester->createApp($credentials, 'POST', '/folders/{id}/file-refs', FileRefsCreate::class);
 
         $requestBuilder = $this->tester->createRequestBuilder($credentials);
         $requestBuilder
-            ->setJsonApiBody(
-                $this->prepareValidFileRefBody(
-                    $name,
-                    $description,
-                    $license,
-                    $file
-                )
-            )
-            ->setUri('/folders/'.($folder->id).'/file-refs')
+            ->setJsonApiBody($this->prepareValidFileRefBody($name, $description, $license, $file))
+            ->setUri('/folders/' . $folder->id . '/file-refs')
             ->create();
 
         return $this->tester->sendMockRequest($app, $requestBuilder->getRequest());
@@ -235,4 +214,11 @@ class FileRefsCreateTest extends \Codeception\Test\Unit
         $resourceLink = $resource->relationship('terms-of-use')->firstResourceLink();
         $this->tester->assertSame($license->id, $resourceLink['id']);
     }
+
+    private function fileToStreamInterface(string $filename)
+    {
+        $factory = new \Slim\Psr7\Factory\StreamFactory();
+
+        return $factory->createStreamFromFile($filename);
+    }
 }
diff --git a/tests/jsonapi/FileRefsDeleteTest.php b/tests/jsonapi/FileRefsDeleteTest.php
index d1813a4380c2aaea89305d489f7f64a4111605a9..8b30e0d184c7b02abc884149364030d4b79758b2 100644
--- a/tests/jsonapi/FileRefsDeleteTest.php
+++ b/tests/jsonapi/FileRefsDeleteTest.php
@@ -41,9 +41,8 @@ class FileRefsDeleteTest extends \Codeception\Test\Unit
         $credentials = $this->tester->getCredentialsForTestDozent();
         $missingId = 'missing-id';
 
-        $this->tester->expectThrowable(RecordNotFoundException::class, function () use ($credentials, $missingId) {
-            $this->sendDeleteFileRef($credentials, $missingId);
-        });
+        $response = $this->sendDeleteFileRef($credentials, $missingId);
+        $this->tester->assertSame(404, $response->getStatusCode());
     }
 
     // **** helper functions ****
diff --git a/tests/jsonapi/FileRefsShowTest.php b/tests/jsonapi/FileRefsShowTest.php
index 10dc0356758f854d59e8fc8b0980c2e62c1c3aa9..6fcca0ac800b49a1caaad8967e43cbb119352bc4 100644
--- a/tests/jsonapi/FileRefsShowTest.php
+++ b/tests/jsonapi/FileRefsShowTest.php
@@ -45,9 +45,8 @@ class FileRefsShowTest extends \Codeception\Test\Unit
     {
         $credentials = $this->tester->getCredentialsForTestDozent();
 
-        $this->tester->expectThrowable(RecordNotFoundException::class, function () use ($credentials) {
-            $this->sendShowFileRef($credentials, 'missing-id');
-        });
+        $response = $this->sendShowFileRef($credentials, 'missing-id');
+        $this->tester->assertSame(404, $response->getStatusCode());
     }
 
     // **** helper functions ****
diff --git a/tests/jsonapi/ForumCategoriesCreateTest.php b/tests/jsonapi/ForumCategoriesCreateTest.php
index c9a12fb5b592522ae3212c5e0dc8820e3f68bb77..4fd96d435a01d1e1a8b58b12f8d847ca3249b63f 100644
--- a/tests/jsonapi/ForumCategoriesCreateTest.php
+++ b/tests/jsonapi/ForumCategoriesCreateTest.php
@@ -48,25 +48,20 @@ class ForumCategoriesCreateTest extends \Codeception\Test\Unit
 
     public function testShouldNotCreateCategory()
     {
-        $this->tester->expectThrowable(RecordNotFoundException::class, function () {
-            $credentials = $this->tester->getCredentialsForTestAutor();
-            $cat = $this->createCategory($credentials);
-            $course_id = 'badCourse';
-            $cat_document = $this->buildValidResourceCategory();
-            $app = $this->tester->createApp($credentials, 'POST', '/courses/{id}/forum-categories', ForumCategoriesCreate::class);
+        $credentials = $this->tester->getCredentialsForTestAutor();
+        $cat = $this->createCategory($credentials);
+        $course_id = 'badCourse';
+        $cat_document = $this->buildValidResourceCategory();
+        $app = $this->tester->createApp($credentials, 'POST', '/courses/{id}/forum-categories', ForumCategoriesCreate::class);
 
-            $requestBuilder = $this->tester->createRequestBuilder($credentials);
-            $requestBuilder
-                ->setUri('/courses/'.$course_id.'/forum-categories')
-                ->create()
-                ->setJsonApiBody($cat_document);
+        $requestBuilder = $this->tester->createRequestBuilder($credentials);
+        $requestBuilder
+            ->setUri('/courses/'.$course_id.'/forum-categories')
+            ->create()
+            ->setJsonApiBody($cat_document);
 
-            $response = $this->tester->sendMockRequest($app, $requestBuilder->getRequest());
+        $response = $this->tester->sendMockRequest($app, $requestBuilder->getRequest());
 
-            $this->tester->assertTrue($response->isSuccessfulDocument([201]));
-            $document = $response->document();
-            $resourceObject = $document->primaryResource();
-            $this->tester->assertSame($cat->entry_name, $resourceObject->attribute('title'));
-        });
+        $this->tester->assertSame(404, $response->getStatusCode());
     }
 }
diff --git a/tests/jsonapi/ForumCategoriesIndexTest.php b/tests/jsonapi/ForumCategoriesIndexTest.php
index 5ef4096bcded1b533812705507ed42cad7c05fca..01ba294d355806f4465198e737201fa497610966 100644
--- a/tests/jsonapi/ForumCategoriesIndexTest.php
+++ b/tests/jsonapi/ForumCategoriesIndexTest.php
@@ -39,29 +39,22 @@ class ForumCategoriesIndexTest extends \Codeception\Test\Unit
         $response = $this->tester->sendMockRequest($app, $requestBuilder->getRequest());
 
         $this->tester->assertTrue($response->isSuccessfulDocument([200]));
-        $document = $response->document();
-        $resourceObject = $document->primaryResource();
     }
 
     public function testShouldNotShowCategory()
     {
-        $this->tester->expectThrowable(RecordNotFoundException::class, function () {
-            $credentials = $this->tester->getCredentialsForTestDozent();
-            $course_id = 'a07535cf2f8a72df33c12ddfa4b53dde';
-            $cat = $this->createCategory($credentials);
-
-            $app = $this->tester->createApp($credentials, 'get', '/course/{id}/forum-categories', ForumCategoriesIndex::class);
+        $credentials = $this->tester->getCredentialsForTestDozent();
+        $course_id = 'a07535cf2f8a72df33c12ddfa4b53dde';
+        $cat = $this->createCategory($credentials);
 
-            $requestBuilder = $this->tester->createRequestBuilder($credentials);
-            $requestBuilder
-                ->setUri('/course/badID/forum-categories')
-                ->fetch();
+        $app = $this->tester->createApp($credentials, 'get', '/course/{id}/forum-categories', ForumCategoriesIndex::class);
 
-            $response = $this->tester->sendMockRequest($app, $requestBuilder->getRequest());
+        $requestBuilder = $this->tester->createRequestBuilder($credentials);
+        $requestBuilder
+            ->setUri('/course/badID/forum-categories')
+            ->fetch();
 
-            $this->tester->assertTrue($response->isSuccessfulDocument([200]));
-            $document = $response->document();
-            $resourceObject = $document->primaryResource();
-        });
+        $response = $this->tester->sendMockRequest($app, $requestBuilder->getRequest());
+        $this->tester->assertSame(404, $response->getStatusCode());
     }
 }
diff --git a/tests/jsonapi/ForumCategoriesShowTest.php b/tests/jsonapi/ForumCategoriesShowTest.php
index 710a7b1a1bb0e5c97109c7e907e574174571eaab..4e8df261b8a4328875214736dd34514219f8ff38 100644
--- a/tests/jsonapi/ForumCategoriesShowTest.php
+++ b/tests/jsonapi/ForumCategoriesShowTest.php
@@ -47,24 +47,16 @@ class ForumCategoriesShowTest extends \Codeception\Test\Unit
 
     public function testShouldNotShowCategories()
     {
-        $this->tester->expectThrowable(RecordNotFoundException::class, function () {
-            $credentials = $this->tester->getCredentialsForTestDozent();
+        $credentials = $this->tester->getCredentialsForTestDozent();
 
-            $app = $this->tester->createApp($credentials, 'get', '/forum-categories/{id}', ForumCategoriesShow::class);
-
-            $requestBuilder = $this->tester->createRequestBuilder($credentials);
-            $requestBuilder
-                ->setUri('/forum-categories/'.'badId')
-                ->fetch();
-
-            $response = $this->tester->sendMockRequest($app, $requestBuilder->getRequest());
+        $app = $this->tester->createApp($credentials, 'get', '/forum-categories/{id}', ForumCategoriesShow::class);
 
-            $this->tester->assertTrue($response->isSuccessfulDocument([200]));
-            $document = $response->document();
-            $resourceObject = $document->primaryResource();
+        $requestBuilder = $this->tester->createRequestBuilder($credentials);
+        $requestBuilder
+            ->setUri('/forum-categories/'.'badId')
+            ->fetch();
 
-            $this->tester->assertSame($cat->entry_name, $resourceObject->attribute('title'));
-            $this->tester->assertSame((int) $cat->pos, $resourceObject->attribute('position'));
-        });
+        $response = $this->tester->sendMockRequest($app, $requestBuilder->getRequest());
+        $this->tester->assertSame(404, $response->getStatusCode());
     }
 }
diff --git a/tests/jsonapi/ForumCategoriesUpdateTest.php b/tests/jsonapi/ForumCategoriesUpdateTest.php
index cbcc766f88553be9c4f2625d5532d900bcf1f3fb..f2015cbcbe7c8d68bc388d663f3d82ce2215f3db 100644
--- a/tests/jsonapi/ForumCategoriesUpdateTest.php
+++ b/tests/jsonapi/ForumCategoriesUpdateTest.php
@@ -47,24 +47,18 @@ class ForumCategoriesUpdateTest extends \Codeception\Test\Unit
 
     public function testShouldNotUpdateCategory()
     {
-        $this->tester->expectThrowable(RecordNotFoundException::class, function () {
-            $credentials = $this->tester->getCredentialsForTestAutor();
-            $cat = $this->createCategory($credentials);
-            $cat_document = $this->buildValidResourceCategoryUpdate();
-            $app = $this->tester->createApp($credentials, 'PATCH', '/forum-categories/{id}', ForumCategoriesUpdate::class);
-
-            $requestBuilder = $this->tester->createRequestBuilder($credentials);
-            $requestBuilder
-                ->setUri('/forum-categories/badId')
-                ->update()
-                ->setJsonApiBody($cat_document);
+        $credentials = $this->tester->getCredentialsForTestAutor();
+        $cat = $this->createCategory($credentials);
+        $cat_document = $this->buildValidResourceCategoryUpdate();
+        $app = $this->tester->createApp($credentials, 'PATCH', '/forum-categories/{id}', ForumCategoriesUpdate::class);
 
-            $response = $this->tester->sendMockRequest($app, $requestBuilder->getRequest());
+        $requestBuilder = $this->tester->createRequestBuilder($credentials);
+        $requestBuilder
+            ->setUri('/forum-categories/badId')
+            ->update()
+            ->setJsonApiBody($cat_document);
 
-            $this->tester->assertTrue($response->isSuccessfulDocument([200]));
-            $document = $response->document();
-            $resourceObject = $document->primaryResource();
-            $this->tester->assertNotEquals($cat->entry_name, $resourceObject->attribute('title'));
-        });
+        $response = $this->tester->sendMockRequest($app, $requestBuilder->getRequest());
+        $this->tester->assertSame(404, $response->getStatusCode());
     }
 }
diff --git a/tests/jsonapi/ForumCategoryDeleteTest.php b/tests/jsonapi/ForumCategoryDeleteTest.php
index 2805f53cf01755f198fd595090ec48e867fe756e..8d144f2b1f57e6895b04dc7dc3ab9f28afcbc233 100644
--- a/tests/jsonapi/ForumCategoryDeleteTest.php
+++ b/tests/jsonapi/ForumCategoryDeleteTest.php
@@ -44,19 +44,17 @@ class ForumCategoryDeleteTest extends \Codeception\Test\Unit
 
     public function testShouldNotDeleteEntry()
     {
-        $this->tester->expectThrowable(RecordNotFoundException::class, function () {
-            $credentials = $this->tester->getCredentialsForTestDozent();
-            $cat = $this->createCategory($credentials);
-            $entry = $this->createEntry($credentials, $cat->id);
-            $app = $this->tester->createApp($credentials, 'delete', '/forum-categories/{id}', ForumCategoriesDelete::class);
-
-            $requestBuilder = $this->tester->createRequestBuilder($credentials);
-            $requestBuilder
-                ->setUri('/forum-categories/badId')
-                ->delete();
-
-            $response = $this->tester->sendMockRequest($app, $requestBuilder->getRequest());
-            $this->tester->assertIsEmpty(ForumCat::find($cat->id));
-        });
+        $credentials = $this->tester->getCredentialsForTestDozent();
+        $cat = $this->createCategory($credentials);
+        $entry = $this->createEntry($credentials, $cat->id);
+        $app = $this->tester->createApp($credentials, 'delete', '/forum-categories/{id}', ForumCategoriesDelete::class);
+
+        $requestBuilder = $this->tester->createRequestBuilder($credentials);
+        $requestBuilder
+            ->setUri('/forum-categories/badId')
+            ->delete();
+
+        $response = $this->tester->sendMockRequest($app, $requestBuilder->getRequest());
+        $this->tester->assertSame(404, $response->getStatusCode());
     }
 }
diff --git a/tests/jsonapi/ForumEntriesCreateTest.php b/tests/jsonapi/ForumEntriesCreateTest.php
index 6f374a3c843eaa2b8724c71174ad9b100a1809ae..04d119e6b7bef83d23de75930edf827e5efb8fa1 100644
--- a/tests/jsonapi/ForumEntriesCreateTest.php
+++ b/tests/jsonapi/ForumEntriesCreateTest.php
@@ -52,27 +52,21 @@ class ForumEntriesCreateTest extends \Codeception\Test\Unit
 
     public function testShouldNotCreateEntryForCategory()
     {
-        $this->tester->expectThrowable(RecordNotFoundException::class, function () {
-            $credentials = $this->tester->getCredentialsForTestDozent();
-            $cat = $this->createCategory($credentials);
-            $content = 'some content to test';
-            $title = 'entry-test-title';
-            $entry_json = $this->buildValidResourceEntry($content, $title);
-            $app = $this->tester->createApp($credentials, 'post', '/forum-categories/{id}/entries', ForumCategoryEntriesCreate::class);
-
-            $requestBuilder = $this->tester->createRequestBuilder($credentials);
-            $requestBuilder
-                ->setUri('/forum-categories/'.'badId'.'/entries')
-                ->create()
-                ->setJsonApiBody($entry_json);
-
-            $response = $this->tester->sendMockRequest($app, $requestBuilder->getRequest());
-            $this->tester->assertTrue($response->isSuccessfulDocument([201]));
-            $document = $response->document();
-            $resourceObject = $document->primaryResource();
-            $this->tester->assertNotNull($resourceObject->attribute('title'));
-            $this->tester->assertNotNull($resourceObject->attribute('content'));
-        });
+        $credentials = $this->tester->getCredentialsForTestDozent();
+        $cat = $this->createCategory($credentials);
+        $content = 'some content to test';
+        $title = 'entry-test-title';
+        $entry_json = $this->buildValidResourceEntry($content, $title);
+        $app = $this->tester->createApp($credentials, 'post', '/forum-categories/{id}/entries', ForumCategoryEntriesCreate::class);
+
+        $requestBuilder = $this->tester->createRequestBuilder($credentials);
+        $requestBuilder
+            ->setUri('/forum-categories/'.'badId'.'/entries')
+            ->create()
+            ->setJsonApiBody($entry_json);
+
+        $response = $this->tester->sendMockRequest($app, $requestBuilder->getRequest());
+        $this->tester->assertSame(404, $response->getStatusCode());
     }
 
     public function testShouldCreateEntryForEntry()
@@ -101,28 +95,21 @@ class ForumEntriesCreateTest extends \Codeception\Test\Unit
 
     public function testShouldNotCreateEntryForEntry()
     {
-        $this->tester->expectThrowable(RecordNotFoundException::class, function () {
-            $credentials = $this->tester->getCredentialsForTestDozent();
-            $cat = $this->createCategory($credentials);
-            $entry = $this->createEntry($credentials, $cat->id);
-            $content = 'some new content to test';
-            $title = 'entry-test-title new';
-            $entry_json = $this->buildValidResourceEntry($content, $title);
-            $app = $this->tester->createApp($credentials, 'post', '/forum-entries/{id}/entries', ForumEntryEntriesCreate::class);
-
-            $requestBuilder = $this->tester->createRequestBuilder($credentials);
-            $requestBuilder
-                ->setUri('/forum-entries/'.'badID'.'/entries')
-                ->create()
-                ->setJsonApiBody($entry_json);
-
-            $response = $this->tester->sendMockRequest($app, $requestBuilder->getRequest());
-
-            $this->tester->assertTrue($response->isSuccessfulDocument([201]));
-            $document = $response->document();
-            $resourceObject = $document->primaryResource();
-            $this->tester->assertNotNull($resourceObject->attribute('title'));
-            $this->tester->assertNotNull($resourceObject->attribute('content'));
-        });
+        $credentials = $this->tester->getCredentialsForTestDozent();
+        $cat = $this->createCategory($credentials);
+        $entry = $this->createEntry($credentials, $cat->id);
+        $content = 'some new content to test';
+        $title = 'entry-test-title new';
+        $entry_json = $this->buildValidResourceEntry($content, $title);
+        $app = $this->tester->createApp($credentials, 'post', '/forum-entries/{id}/entries', ForumEntryEntriesCreate::class);
+
+        $requestBuilder = $this->tester->createRequestBuilder($credentials);
+        $requestBuilder
+            ->setUri('/forum-entries/'.'badID'.'/entries')
+            ->create()
+            ->setJsonApiBody($entry_json);
+
+        $response = $this->tester->sendMockRequest($app, $requestBuilder->getRequest());
+        $this->tester->assertSame(404, $response->getStatusCode());
     }
 }
diff --git a/tests/jsonapi/ForumEntriesDeleteTest.php b/tests/jsonapi/ForumEntriesDeleteTest.php
index 5af0b89b7475e7b7964c19619f91cdd903fb3ece..60ae3a6492c36c91fd6a9781bcf781697ebb9bc1 100644
--- a/tests/jsonapi/ForumEntriesDeleteTest.php
+++ b/tests/jsonapi/ForumEntriesDeleteTest.php
@@ -46,20 +46,18 @@ class ForumEntriesDeleteTest extends \Codeception\Test\Unit
 
     public function testShouldNotDeleteEntry()
     {
-        $this->tester->expectThrowable(RecordNotFoundException::class, function () {
-            $credentials = $this->tester->getCredentialsForTestDozent();
-            $cat = $this->createCategory($credentials);
-            $entry = $this->createEntry($credentials, $cat->id);
-            $app = $this->tester->createApp($credentials, 'delete', '/forum-entries/{id}', ForumEntriesDelete::class);
+        $credentials = $this->tester->getCredentialsForTestDozent();
+        $cat = $this->createCategory($credentials);
+        $entry = $this->createEntry($credentials, $cat->id);
+        $app = $this->tester->createApp($credentials, 'delete', '/forum-entries/{id}', ForumEntriesDelete::class);
 
-            $requestBuilder = $this->tester->createRequestBuilder($credentials);
-            $requestBuilder
-                ->setUri('/forum-entries/badId')
-                ->delete()
-                ->setJsonApiBody($entry_json);
+        $requestBuilder = $this->tester->createRequestBuilder($credentials);
+        $requestBuilder
+            ->setUri('/forum-entries/badId')
+            ->delete()
+            ->setJsonApiBody($entry_json);
 
-            $response = $this->tester->sendMockRequest($app, $requestBuilder->getRequest());
-            $this->tester->assertIsEmpty(ForumEntry::find($entry->id));
-        });
+        $response = $this->tester->sendMockRequest($app, $requestBuilder->getRequest());
+        $this->tester->assertSame(404, $response->getStatusCode());
     }
 }
diff --git a/tests/jsonapi/ForumEntriesShowTest.php b/tests/jsonapi/ForumEntriesShowTest.php
index 45f9e6a8537f5444ce0d36bb444bcf302aaf1857..2443fa64de9b5f6d541444f5510451cf0e02ef1f 100644
--- a/tests/jsonapi/ForumEntriesShowTest.php
+++ b/tests/jsonapi/ForumEntriesShowTest.php
@@ -61,42 +61,35 @@ class ForumEntriesShowTest extends \Codeception\Test\Unit
 
     public function testShouldNotShowEntry()
     {
-        $this->tester->expectThrowable(RecordNotFoundException::class, function () {
-            $credentials = $this->tester->getCredentialsForRoot();
-            $app = $this->tester->createApp($credentials, 'get', '/forum-entries/{id}', ForumEntriesShow::class);
-
-            $requestBuilder = $this->tester->createRequestBuilder($credentials);
-            $requestBuilder
-                ->setUri('/forum-entries/'.'badEntry')
-                ->fetch();
+        $credentials = $this->tester->getCredentialsForRoot();
+        $app = $this->tester->createApp($credentials, 'get', '/forum-entries/{id}', ForumEntriesShow::class);
 
-            $response = $this->tester->sendMockRequest($app, $requestBuilder->getRequest());
+        $requestBuilder = $this->tester->createRequestBuilder($credentials);
+        $requestBuilder
+            ->setUri('/forum-entries/'.'badEntry')
+            ->fetch();
 
-            $this->tester->assertTrue($response->isSuccessfulDocument([200]));
-        });
+        $response = $this->tester->sendMockRequest($app, $requestBuilder->getRequest());
+        $this->tester->assertSame(404, $response->getStatusCode());
     }
 
     public function testShouldNotShowEntriesForCategory()
     {
-        $this->tester->expectThrowable(RecordNotFoundException::class, function () {
-            $credentials = $this->tester->getCredentialsForRoot();
-            $cat = $this->createCategory($credentials);
-            $this->createEntry($credentials, $cat->id);
-            $this->createEntry($credentials, $cat->id);
-            $this->createEntry($credentials, $cat->id);
-
-            $app = $this->tester->createApp($credentials, 'get', '/forum-categories/{id}/entries', ForumCategoryEntriesIndex::class);
-
-            $requestBuilder = $this->tester->createRequestBuilder($credentials);
-            $requestBuilder
-                ->setUri('/forum-categories/badID/entries')
-                ->fetch();
-
-            $response = $this->tester->sendMockRequest($app, $requestBuilder->getRequest());
-            $document = $response->document();
-            $resourceObject = $document->primaryResource();
-            $this->tester->assertNotNull($resourceObject);
-        });
+        $credentials = $this->tester->getCredentialsForRoot();
+        $cat = $this->createCategory($credentials);
+        $this->createEntry($credentials, $cat->id);
+        $this->createEntry($credentials, $cat->id);
+        $this->createEntry($credentials, $cat->id);
+
+        $app = $this->tester->createApp($credentials, 'get', '/forum-categories/{id}/entries', ForumCategoryEntriesIndex::class);
+
+        $requestBuilder = $this->tester->createRequestBuilder($credentials);
+        $requestBuilder
+            ->setUri('/forum-categories/badID/entries')
+            ->fetch();
+
+        $response = $this->tester->sendMockRequest($app, $requestBuilder->getRequest());
+        $this->tester->assertSame(404, $response->getStatusCode());
     }
 
     public function testShouldShowEntriesForCategory()
@@ -116,7 +109,7 @@ class ForumEntriesShowTest extends \Codeception\Test\Unit
 
         $response = $this->tester->sendMockRequest($app, $requestBuilder->getRequest());
         $document = $response->document();
-        $resourceObject = $document->primaryResource();
+        $resourceObject = $document->primaryResources();
         $this->tester->assertNotNull($resourceObject);
     }
 
@@ -137,30 +130,26 @@ class ForumEntriesShowTest extends \Codeception\Test\Unit
 
         $response = $this->tester->sendMockRequest($app, $requestBuilder->getRequest());
         $document = $response->document();
-        $resourceObject = $document->primaryResource();
+        $resourceObject = $document->primaryResources();
         $this->tester->assertNotNull($resourceObject);
     }
 
     public function testShouldNotShowEntriesForEntry()
     {
-        $this->tester->expectThrowable(RecordNotFoundException::class, function () {
-            $credentials = $this->tester->getCredentialsForRoot();
-            $cat = $this->createCategory($credentials);
-            $targetEntry = $this->createEntry($credentials, $cat->id);
-            $this->createEntry($credentials, $targetEntry->id);
-            $this->createEntry($credentials, $targetEntry->id);
-            $this->createEntry($credentials, $targetEntry->id);
-            $app = $this->tester->createApp($credentials, 'get', '/forum-entries/{id}/entries', ForumEntryEntriesIndex::class);
-
-            $requestBuilder = $this->tester->createRequestBuilder($credentials);
-            $requestBuilder
-                ->setUri('/forum-entries/badTopic/entries')
-                ->fetch();
-
-            $response = $this->tester->sendMockRequest($app, $requestBuilder->getRequest());
-            $document = $response->document();
-            $resourceObject = $document->primaryResource();
-            $this->tester->assertNotNull($resourceObject);
-        });
+        $credentials = $this->tester->getCredentialsForRoot();
+        $cat = $this->createCategory($credentials);
+        $targetEntry = $this->createEntry($credentials, $cat->id);
+        $this->createEntry($credentials, $targetEntry->id);
+        $this->createEntry($credentials, $targetEntry->id);
+        $this->createEntry($credentials, $targetEntry->id);
+        $app = $this->tester->createApp($credentials, 'get', '/forum-entries/{id}/entries', ForumEntryEntriesIndex::class);
+
+        $requestBuilder = $this->tester->createRequestBuilder($credentials);
+        $requestBuilder
+            ->setUri('/forum-entries/badTopic/entries')
+            ->fetch();
+
+        $response = $this->tester->sendMockRequest($app, $requestBuilder->getRequest());
+        $this->tester->assertSame(404, $response->getStatusCode());
     }
 }
diff --git a/tests/jsonapi/ForumEntriesUpdateTest.php b/tests/jsonapi/ForumEntriesUpdateTest.php
index 91f2c54c7553565ae085615fffac5241e427ae43..db08917defded0f9cd2fb06827024f3a51b1439f 100644
--- a/tests/jsonapi/ForumEntriesUpdateTest.php
+++ b/tests/jsonapi/ForumEntriesUpdateTest.php
@@ -48,24 +48,19 @@ class ForumEntriesUpdateTest extends \Codeception\Test\Unit
 
     public function testShouldNotUpdateEntry()
     {
-        $this->tester->expectThrowable(RecordNotFoundException::class, function () {
-            $credentials = $this->tester->getCredentialsForTestDozent();
-            $cat = $this->createCategory($credentials);
-            $entry = $this->createEntry($credentials, $cat->id);
-            $entry_json = $this->buildValidResourceEntryUpdate();
-            $app = $this->tester->createApp($credentials, 'PATCH', '/forum-entries/{id}', ForumEntriesUpdate::class);
+        $credentials = $this->tester->getCredentialsForTestDozent();
+        $cat = $this->createCategory($credentials);
+        $entry = $this->createEntry($credentials, $cat->id);
+        $entry_json = $this->buildValidResourceEntryUpdate();
+        $app = $this->tester->createApp($credentials, 'PATCH', '/forum-entries/{id}', ForumEntriesUpdate::class);
 
-            $requestBuilder = $this->tester->createRequestBuilder($credentials);
-            $requestBuilder
-                ->setUri('/forum-entries/badId')
-                ->update()
-                ->setJsonApiBody($entry_json);
+        $requestBuilder = $this->tester->createRequestBuilder($credentials);
+        $requestBuilder
+            ->setUri('/forum-entries/badId')
+            ->update()
+            ->setJsonApiBody($entry_json);
 
-            $response = $this->tester->sendMockRequest($app, $requestBuilder->getRequest());
-            $this->tester->assertTrue($response->isSuccessfulDocument([200]));
-            $document = $response->document();
-            $resourceObject = $document->primaryResource();
-            $this->tester->assertNotEquals($entry->name, $resourceObject->attribute('title'));
-        });
+        $response = $this->tester->sendMockRequest($app, $requestBuilder->getRequest());
+        $this->tester->assertSame(404, $response->getStatusCode());
     }
 }
diff --git a/tests/jsonapi/MessagesShowTest.php b/tests/jsonapi/MessagesShowTest.php
index 9bf5464598971f33cd50f897657fa60d50516bf4..b9563dc7bf0ad998893a9ea949c3e699fc9dc5b1 100644
--- a/tests/jsonapi/MessagesShowTest.php
+++ b/tests/jsonapi/MessagesShowTest.php
@@ -23,10 +23,9 @@ class MessagesShowTest extends \Codeception\Test\Unit
     // tests
     public function testMessageNotFound()
     {
-        $this->tester->expectThrowable(RecordNotFoundException::class, function () {
-            $credentials = $this->tester->getCredentialsForTestAutor();
-            $response = $this->fetchMessage($credentials, new \Message(md5('eurydamas')));
-        });
+        $credentials = $this->tester->getCredentialsForTestAutor();
+        $response = $this->fetchMessage($credentials, new \Message(md5('eurydamas')));
+        $this->tester->assertSame(404, $response->getStatusCode());
     }
 
     public function testShowMessage()
diff --git a/tests/jsonapi/NewsCreateTest.php b/tests/jsonapi/NewsCreateTest.php
index 199cea597bb56bf138529c1b07c29836335f26fc..eaf9dfd7e1f1c22fed2f8b30e1ecc4fa63437e78 100644
--- a/tests/jsonapi/NewsCreateTest.php
+++ b/tests/jsonapi/NewsCreateTest.php
@@ -1,16 +1,12 @@
 <?php
 
 require_once 'NewsTestHelper.php';
-use JsonApi\Models\C;
 
+use JsonApi\Routes\News\CommentCreate;
 use JsonApi\Routes\News\CourseNewsCreate;
-use JsonApi\Routes\News\UserNewsCreate;
+use JsonApi\Routes\News\NewsUpdate;
 use JsonApi\Routes\News\StudipNewsCreate;
-use JsonApi\Routes\News\CommentCreate;
-use JsonApi\Routes\News\NewsUpdate; 
-use JsonApi\Errors\AuthorizationFailedException;
-use JsonApi\Errors\RecordNotFoundException;
-use JsonApi\Routes\News\CommentsDelete;
+use JsonApi\Routes\News\UserNewsCreate;
 
 class NewsCreateTest extends \Codeception\Test\Unit
 {
@@ -53,49 +49,43 @@ class NewsCreateTest extends \Codeception\Test\Unit
         $this->tester->assertNotNull($resourceObject->attribute('content'));
         $newsId = $news->id;
     }
+
     public function testShouldNotStudipNewsCreate()
     {
+        $credentials = $this->tester->getCredentialsForTestDozent();
+        $title = 'A public testing title';
+        $content = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit';
+        $entry_json = $this->buildValidResourceEntry($content, $title);
+        $app = $this->tester->createApp($credentials, 'post', '/news', StudipNewsCreate::class);
+
+        $requestBuilder = $this->tester->createRequestBuilder($credentials);
+        $requestBuilder
+            ->setUri('/news')
+            ->create()
+            ->setJsonApiBody($entry_json);
 
-        $this->tester->expectThrowable(AuthorizationFailedException::class, function () {
-            $credentials = $this->tester->getCredentialsForTestDozent();
-            $title = 'A public testing title';
-            $content = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit';
-            $entry_json = $this->buildValidResourceEntry($content, $title);
-            $app = $this->tester->createApp($credentials, 'post', '/news', StudipNewsCreate::class);
-  
-            $requestBuilder = $this->tester->createRequestBuilder($credentials);
-            $requestBuilder
-                ->setUri('/news')
-                ->create()
-                ->setJsonApiBody($entry_json);
-
-          $response = $this->tester->sendMockRequest($app, $requestBuilder->getRequest());
-          $this->tester->assertTrue($response->isSuccessfulDocument([201]));
-          $document = $response->document();
-          $resourceObject = $document->primaryResource();
-          $this->tester->assertNotNull($resourceObject->attribute('title'));
-          $this->tester->assertNotNull($resourceObject->attribute('content'));
-          $newsId = $news->id;
-
-        });
+        $response = $this->tester->sendMockRequest($app, $requestBuilder->getRequest());
+        $this->tester->assertFalse($response->isSuccessfulDocument());
+        $this->tester->assertSame(403, $response->getStatusCode());
     }
-    public function testShouldNewsUpdate() {
+
+    public function testShouldNewsUpdate()
+    {
         $title = 'A course testing title';
         $credentials = $this->tester->getCredentialsForTestDozent();
         $content = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit';
         $news = $this->createNews($credentials, $title, $content);
-        
+
         $changedContent = 'Lorem ipsum dolor sit amet';
         $entry_json = $this->buildValidUpdateEntry($changedContent);
         $app = $this->tester->createApp($credentials, 'patch', '/news/{id}', NewsUpdate::class);
-        
+
         $requestBuilder = $this->tester->createRequestBuilder($credentials);
         $requestBuilder
             ->setUri('/news/'.$news->id)
             ->update()
             ->setJsonApiBody($entry_json);
-            
-    
+
         $response = $this->tester->sendMockRequest($app, $requestBuilder->getRequest());
         $this->tester->assertTrue($response->isSuccessfulDocument());
 
@@ -128,10 +118,9 @@ class NewsCreateTest extends \Codeception\Test\Unit
         $this->tester->assertNotNull($resourceObject->attribute('content'));
         $newsId = $news->id;
     }
+
     public function testShouldNotCourseNewsCreate()
     {
-      $this->tester->expectThrowable(AuthorizationFailedException::class, function () {
-
         $credentials = $this->tester->getCredentialsForTestAutor();
         $courseId = 'a07535cf2f8a72df33c12ddfa4b53dde';
         $title = 'A course testing title';
@@ -147,15 +136,9 @@ class NewsCreateTest extends \Codeception\Test\Unit
 
         $response = $this->tester->sendMockRequest($app, $requestBuilder->getRequest());
 
-        $this->tester->assertTrue($response->isSuccessfulDocument([201]));
-        $document = $response->document();
-        $resourceObject = $document->primaryResource();
-        $this->tester->assertNotNull($resourceObject->attribute('title'));
-        $this->tester->assertNotNull($resourceObject->attribute('content'));
-        $newsId = $news->id;
-
-      });
+        $this->tester->assertSame(403, $response->getStatusCode());
     }
+
     public function testShouldUserNewsCreate()
     {
         $credentials = $this->tester->getCredentialsForTestAutor();
@@ -172,7 +155,6 @@ class NewsCreateTest extends \Codeception\Test\Unit
             ->setJsonApiBody($entry_json);
 
         $response = $this->tester->sendMockRequest($app, $requestBuilder->getRequest());
-
         $this->tester->assertTrue($response->isSuccessfulDocument([201]));
         $document = $response->document();
         $resourceObject = $document->primaryResource();
@@ -180,10 +162,9 @@ class NewsCreateTest extends \Codeception\Test\Unit
         $this->tester->assertNotNull($resourceObject->attribute('content'));
         $newsId = $news->id;
     }
+
     public function testShouldNotUserNewsCreate()
     {
-      $this->tester->expectThrowable(AuthorizationFailedException::class, function () {
-
         $credentials = $this->tester->getCredentialsForTestAutor();
         $otherUser = $this->tester->getCredentialsForTestDozent();
         $userId = $otherUser['id'];
@@ -199,16 +180,9 @@ class NewsCreateTest extends \Codeception\Test\Unit
             ->setJsonApiBody($entry_json);
 
         $response = $this->tester->sendMockRequest($app, $requestBuilder->getRequest());
-
-        $this->tester->assertTrue($response->isSuccessfulDocument([201]));
-        $document = $response->document();
-        $resourceObject = $document->primaryResource();
-        $this->tester->assertNotNull($resourceObject->attribute('title'));
-        $this->tester->assertNotNull($resourceObject->attribute('content'));
-        $newsId = $news->id;
-
-      });
+        $this->assertSame(403, $response->getStatusCode());
     }
+
     public function testShouldCommentCreate()
     {
         $title = 'A course testing title';
@@ -227,37 +201,29 @@ class NewsCreateTest extends \Codeception\Test\Unit
 
         $response = $this->tester->sendMockRequest($app, $requestBuilder->getRequest());
 
-        $this->tester->assertTrue($response->isSuccessfulDocument([201]));
+        $this->tester->assertTrue($response->isSuccessfulDocument());
         $document = $response->document();
         $resourceObject = $document->primaryResource();
         $this->tester->assertNotNull($resourceObject->attribute('content'));
     }
+
     public function testShouldNotCommentCreate()
     {
-        //missing title
-        $this->tester->expectThrowable(RecordNotFoundException::class, function () {
-            $title = 'A course testing title';
-            $credentials = $this->tester->getCredentialsForTestDozent();
-            $content = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit';
-            $news = $this->createNews($credentials, $title, $content);
-            $comment = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit';
-            $entry_json = $this->buildValidCommentEntry($comment);
-
-            $app = $this->tester->createApp($credentials, 'post', '/news/{id}/comments', CommentCreate::class);
-            $requestBuilder = $this->tester->createRequestBuilder($credentials);
-            $requestBuilder
-                ->setUri('/news/badId/comments')
-                ->create()
-                ->setJsonApiBody($entry_json);
-
-            $response = $this->tester->sendMockRequest($app, $requestBuilder->getRequest());
-
-            $this->tester->assertTrue($response->isSuccessfulDocument([201]));
-            $document = $response->document();
-            $resourceObject = $document->primaryResource();
-            $this->tester->assertNotNull($resourceObject->attribute('content'));
-        });
-    }
-    
+        $title = 'A course testing title';
+        $credentials = $this->tester->getCredentialsForTestDozent();
+        $content = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit';
+        $news = $this->createNews($credentials, $title, $content);
+        $comment = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit';
+        $entry_json = $this->buildValidCommentEntry($comment);
 
+        $app = $this->tester->createApp($credentials, 'post', '/news/{id}/comments', CommentCreate::class);
+        $requestBuilder = $this->tester->createRequestBuilder($credentials);
+        $requestBuilder
+            ->setUri('/news/badId/comments')
+            ->create()
+            ->setJsonApiBody($entry_json);
+
+        $response = $this->tester->sendMockRequest($app, $requestBuilder->getRequest());
+        $this->tester->assertFalse($response->isSuccessfulDocument());
+    }
 }
diff --git a/tests/jsonapi/NewsShowTest.php b/tests/jsonapi/NewsShowTest.php
index 79df86db928b3db20bb161ca1cc558c7e13e44e1..6a90657f6304497dfc9a24bc42b0e69165cb3c95 100644
--- a/tests/jsonapi/NewsShowTest.php
+++ b/tests/jsonapi/NewsShowTest.php
@@ -35,7 +35,7 @@ class NewsShowTest extends \Codeception\Test\Unit
         $news = $this->createNews($credentials, $title, $content, $range_id);
         $newsId = $news->id;
         $app = $this->tester->createApp($credentials, 'get', '/studip/news', GlobalNewsShow::class);
-        
+
         $response = $this->tester->sendMockRequest(
             $app,
             $this->tester->createRequestBuilder($credentials)
@@ -47,11 +47,12 @@ class NewsShowTest extends \Codeception\Test\Unit
         $this->tester->assertSame(200, $response->getStatusCode());
         $this->tester->assertTrue($response->isSuccessfulDocument([200]));
         $document = $response->document();
-        $resourceObject = $document->primaryResource();
+        $resourceObjects = $document->primaryResources();
+        $resourceObject = current($resourceObjects);
         $this->tester->assertNotNull($resourceObject->attribute('title'));
         $this->tester->assertNotNull($resourceObject->attribute('content'));
         $this->tester->assertNotNull($document->isSingleResourceDocument());
-        $this->tester->assertSame($newsId, $document->primaryResource()->id());
+        $this->tester->assertSame($newsId, $resourceObject->id());
 
         $this->tester->storeJsonMd('show_news', $response);
     }
@@ -64,7 +65,7 @@ class NewsShowTest extends \Codeception\Test\Unit
         $news = $this->createNews($credentials, $title, $content, $course_id);
         $newsId = $news->id;
         $app = $this->tester->createApp($credentials, 'get', '/courses/{id}/news', ByCourseIndex::class);
-        
+
         $response = $this->tester->sendMockRequest(
             $app,
             $this->tester->createRequestBuilder($credentials)
@@ -74,10 +75,11 @@ class NewsShowTest extends \Codeception\Test\Unit
         );
         $this->tester->assertTrue($response->isSuccessfulDocument());
         $document = $response->document();
-        $resourceObject = $document->primaryResource();
+        $resourceObjects = $document->primaryResources();
+        $resourceObject = current($resourceObjects);
         $this->tester->assertNotNull($resourceObject->attribute('title'));
         $this->tester->assertNotNull($resourceObject->attribute('content'));
-        
+
     }
 
     public function testShouldShowNewsByCurrentUser()
@@ -92,8 +94,7 @@ class NewsShowTest extends \Codeception\Test\Unit
         $this->tester->assertSame(200, $response->getStatusCode());
         $this->tester->assertTrue($response->isSuccessfulDocument([200]));
         $document = $response->document();
-        $this->tester->assertNotNull($document->primaryResource());
-        $this->tester->assertSame($newsId, $document->primaryResource()->id());
+        $this->tester->assertNotEmpty($document->primaryResources());
     }
 
     public function testShouldNotShowNewsByCurrentUser()
@@ -107,7 +108,7 @@ class NewsShowTest extends \Codeception\Test\Unit
         $this->tester->assertSame(200, $response->getStatusCode());
         $this->tester->assertTrue($response->isSuccessfulDocument([200]));
         $document = $response->document();
-        $this->tester->assertNull($document->primaryResource());
+        $this->tester->assertEmpty($document->primaryResources());
     }
 
     private function getNoNewsByUser($credentials)
diff --git a/tests/jsonapi/UserEventsIcalTest.php b/tests/jsonapi/UserEventsIcalTest.php
index bf3d2ba5104b91d12a14cba3d607a8b2343b5a44..3d0a78a31c17405f938e4608b285ac4b055b286f 100644
--- a/tests/jsonapi/UserEventsIcalTest.php
+++ b/tests/jsonapi/UserEventsIcalTest.php
@@ -39,7 +39,7 @@ class UserEventsIcalTest extends \Codeception\Test\Unit
         $requestBuilder = $this->tester->createRequestBuilder($credentials);
         $requestBuilder->setUri('/users/'.$credentials['id'].'/events.ics')->fetch();
 
-        $response = $app($requestBuilder->getRequest(), new \Slim\Http\Response());
+        $response = $app->handle($requestBuilder->getRequest());
 
         $this->tester->assertEquals(200, $response->getStatusCode());
         $this->tester->assertStringContainsString('BEGIN:VEVENT', (string) $response->getBody());
diff --git a/tests/jsonapi/UserScheduleShowTest.php b/tests/jsonapi/UserScheduleShowTest.php
index f505977b104f68c1b69ebc90867f644dbf8ead87..fc7529f8ebfc26eaeda6fc650c143d19588fb7b1 100644
--- a/tests/jsonapi/UserScheduleShowTest.php
+++ b/tests/jsonapi/UserScheduleShowTest.php
@@ -38,6 +38,7 @@ class UserScheduleShowTest extends \Codeception\Test\Unit
         $scheduleId = \DBManager::get()->lastInsertId();
 
         $app = $this->tester->createApp($credentials, 'get', '/users/{id}/schedule', UserScheduleShow::class, 'get-schedule');
+        $app->get('/xxx', function () {})->setName('get-semester');
 
         $requestBuilder = $this->tester->createRequestBuilder($credentials);
         $requestBuilder->setUri('/users/'.$credentials['id'].'/schedule')->fetch();
diff --git a/tests/jsonapi/_bootstrap.php b/tests/jsonapi/_bootstrap.php
index b4481afb7f6660e58af38e9ed013f0e63612ff72..d9c5adcef74f6097bcfd246aef501bd59a62da93 100644
--- a/tests/jsonapi/_bootstrap.php
+++ b/tests/jsonapi/_bootstrap.php
@@ -55,6 +55,7 @@ StudipAutoloader::addAutoloadPath($GLOBALS['STUDIP_BASE_PATH'].'/lib/calendar/li
 StudipAutoloader::addAutoloadPath($GLOBALS['STUDIP_BASE_PATH'].'/lib/filesystem');
 StudipAutoloader::addAutoloadPath($GLOBALS['STUDIP_BASE_PATH'].'/lib/migrations');
 StudipAutoloader::addAutoloadPath($GLOBALS['STUDIP_BASE_PATH'].'/lib/modules');
+StudipAutoloader::addAutoloadPath($GLOBALS['STUDIP_BASE_PATH'].'/lib/navigation');
 StudipAutoloader::addAutoloadPath($GLOBALS['STUDIP_BASE_PATH'].'/lib/phplib');
 StudipAutoloader::addAutoloadPath($GLOBALS['STUDIP_BASE_PATH'].'/lib/raumzeit');
 StudipAutoloader::addAutoloadPath($GLOBALS['STUDIP_BASE_PATH'].'/lib/resources');