Ver código fonte

checkin of everything

praktikant 3 semanas atrás
pai
commit
b246a920d5

+ 40 - 13
.idea/internship-playground.iml

@@ -6,39 +6,66 @@
       <sourceFolder url="file://$MODULE_DIR$/src-php/app/internship" isTestSource="false" packagePrefix="custom\" />
       <sourceFolder url="file://$MODULE_DIR$/src-php/test/internship" isTestSource="true" packagePrefix="custom\" />
       <sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
-      <sourceFolder url="file://$MODULE_DIR$/src-php/app" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/src-php/test" isTestSource="true" />
+      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/composer" />
+      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/doctrine/instantiator" />
       <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/myclabs/deep-copy" />
+      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/n2n/hangar" />
+      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/n2n/hangar-api" />
+      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/n2n/n2n" />
+      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/n2n/n2n-batch" />
+      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/n2n/n2n-cache" />
+      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/n2n/n2n-composer-module-installer" />
+      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/n2n/n2n-concurrency" />
+      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/n2n/n2n-config" />
+      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/n2n/n2n-context" />
+      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/n2n/n2n-impl-persistence-meta" />
+      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/n2n/n2n-impl-persistence-orm" />
+      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/n2n/n2n-impl-web-dispatch" />
+      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/n2n/n2n-impl-web-ui" />
+      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/n2n/n2n-io" />
+      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/n2n/n2n-l10n" />
+      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/n2n/n2n-log4php" />
+      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/n2n/n2n-mail" />
+      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/n2n/n2n-persistence" />
+      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/n2n/n2n-reflection" />
+      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/n2n/n2n-spec-dbo" />
+      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/n2n/n2n-spec-valobj" />
+      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/n2n/n2n-test" />
+      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/n2n/n2n-util" />
+      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/n2n/n2n-web" />
+      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/n2n/phpbob" />
       <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/nikic/php-parser" />
-      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/composer" />
       <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/phar-io/manifest" />
       <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/phar-io/version" />
-      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/doctrine/instantiator" />
-      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/theseer/tokenizer" />
+      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/phpunit/php-code-coverage" />
+      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/phpunit/php-file-iterator" />
+      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/phpunit/php-invoker" />
+      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/phpunit/php-text-template" />
+      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/phpunit/php-timer" />
+      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/phpunit/phpunit" />
+      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/psr/cache" />
+      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/psr/container" />
+      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/psr/http-factory" />
+      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/psr/http-message" />
+      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/psr/simple-cache" />
       <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/sebastian/cli-parser" />
       <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/sebastian/code-unit" />
       <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/sebastian/code-unit-reverse-lookup" />
       <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/sebastian/comparator" />
       <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/sebastian/complexity" />
       <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/sebastian/diff" />
-      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/phpunit/php-code-coverage" />
       <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/sebastian/environment" />
-      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/phpunit/php-file-iterator" />
       <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/sebastian/exporter" />
-      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/phpunit/php-invoker" />
       <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/sebastian/global-state" />
-      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/phpunit/php-text-template" />
       <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/sebastian/lines-of-code" />
-      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/phpunit/php-timer" />
       <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/sebastian/object-enumerator" />
-      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/phpunit/phpunit" />
       <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/sebastian/object-reflector" />
       <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/sebastian/recursion-context" />
       <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/sebastian/resource-operations" />
       <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/sebastian/type" />
       <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/sebastian/version" />
-      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/psr/container" />
-      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/psr/http-factory" />
-      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/psr/http-message" />
+      <excludeFolder url="file://$MODULE_DIR$/src-php/vendor/theseer/tokenizer" />
     </content>
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />

+ 1 - 1
.idea/php-test-framework.xml

@@ -5,7 +5,7 @@
       <tool tool_name="PHPUnit">
         <cache>
           <versions>
-            <info id="Local" version="9.5.27" />
+            <info id="Local" version="9.6.28" />
           </versions>
         </cache>
       </tool>

+ 45 - 37
.idea/php.xml

@@ -7,67 +7,75 @@
     <option name="transferred" value="true" />
   </component>
   <component name="PHPCodeSnifferOptionsConfiguration">
+    <option name="highlightLevel" value="WARNING" />
     <option name="transferred" value="true" />
   </component>
   <component name="PhpIncludePathManager">
     <include_path>
-      <path value="$PROJECT_DIR$/vendor/n2n/n2n-composer-module-installer" />
-      <path value="$PROJECT_DIR$/src-php/vendor/myclabs/deep-copy" />
+      <path value="$PROJECT_DIR$/src-php/vendor/n2n/n2n-web" />
       <path value="$PROJECT_DIR$/src-php/vendor/nikic/php-parser" />
+      <path value="$PROJECT_DIR$/src-php/vendor/n2n/n2n-persistence" />
+      <path value="$PROJECT_DIR$/src-php/vendor/n2n/n2n-test" />
       <path value="$PROJECT_DIR$/src-php/vendor/composer" />
+      <path value="$PROJECT_DIR$/src-php/vendor/theseer/tokenizer" />
+      <path value="$PROJECT_DIR$/src-php/vendor/n2n/n2n-impl-web-ui" />
+      <path value="$PROJECT_DIR$/src-php/vendor/n2n/n2n-reflection" />
+      <path value="$PROJECT_DIR$/src-php/vendor/n2n/n2n-impl-persistence-meta" />
       <path value="$PROJECT_DIR$/src-php/vendor/n2n/n2n-composer-module-installer" />
+      <path value="$PROJECT_DIR$/src-php/vendor/n2n/n2n" />
       <path value="$PROJECT_DIR$/src-php/vendor/n2n/n2n-config" />
-      <path value="$PROJECT_DIR$/src-php/vendor/n2n/hangar" />
-      <path value="$PROJECT_DIR$/src-php/vendor/n2n/n2n-io" />
+      <path value="$PROJECT_DIR$/src-php/vendor/n2n/n2n-util" />
+      <path value="$PROJECT_DIR$/src-php/vendor/n2n/n2n-spec-valobj" />
       <path value="$PROJECT_DIR$/src-php/vendor/n2n/n2n-mail" />
+      <path value="$PROJECT_DIR$/src-php/vendor/n2n/n2n-io" />
+      <path value="$PROJECT_DIR$/src-php/vendor/n2n/n2n-concurrency" />
+      <path value="$PROJECT_DIR$/src-php/vendor/myclabs/deep-copy" />
+      <path value="$PROJECT_DIR$/src-php/vendor/n2n/hangar" />
+      <path value="$PROJECT_DIR$/src-php/vendor/n2n/n2n-batch" />
+      <path value="$PROJECT_DIR$/src-php/vendor/n2n/hangar-api" />
       <path value="$PROJECT_DIR$/src-php/vendor/n2n/n2n-log4php" />
-      <path value="$PROJECT_DIR$/src-php/vendor/n2n/n2n-reflection" />
-      <path value="$PROJECT_DIR$/src-php/vendor/n2n/n2n-impl-web-dispatch" />
-      <path value="$PROJECT_DIR$/src-php/vendor/n2n/n2n-test" />
-      <path value="$PROJECT_DIR$/src-php/vendor/n2n/n2n-util" />
-      <path value="$PROJECT_DIR$/src-php/vendor/n2n/n2n" />
-      <path value="$PROJECT_DIR$/src-php/vendor/n2n/n2n-impl-web-ui" />
+      <path value="$PROJECT_DIR$/src-php/vendor/n2n/n2n-cache" />
       <path value="$PROJECT_DIR$/src-php/vendor/n2n/phpbob" />
-      <path value="$PROJECT_DIR$/src-php/vendor/n2n/n2n-web" />
-      <path value="$PROJECT_DIR$/src-php/vendor/phar-io/manifest" />
-      <path value="$PROJECT_DIR$/src-php/vendor/n2n/n2n-impl-persistence-meta" />
-      <path value="$PROJECT_DIR$/src-php/vendor/phar-io/version" />
-      <path value="$PROJECT_DIR$/src-php/vendor/n2n/hangar-api" />
-      <path value="$PROJECT_DIR$/src-php/vendor/n2n/n2n-persistence" />
-      <path value="$PROJECT_DIR$/src-php/vendor/n2n/n2n-context" />
       <path value="$PROJECT_DIR$/src-php/vendor/n2n/n2n-impl-persistence-orm" />
+      <path value="$PROJECT_DIR$/src-php/vendor/n2n/n2n-spec-dbo" />
+      <path value="$PROJECT_DIR$/src-php/vendor/n2n/n2n-context" />
+      <path value="$PROJECT_DIR$/src-php/vendor/n2n/n2n-impl-web-dispatch" />
       <path value="$PROJECT_DIR$/src-php/vendor/n2n/n2n-l10n" />
       <path value="$PROJECT_DIR$/src-php/vendor/doctrine/instantiator" />
-      <path value="$PROJECT_DIR$/src-php/vendor/theseer/tokenizer" />
-      <path value="$PROJECT_DIR$/src-php/vendor/sebastian/cli-parser" />
-      <path value="$PROJECT_DIR$/src-php/vendor/sebastian/code-unit" />
-      <path value="$PROJECT_DIR$/src-php/vendor/sebastian/code-unit-reverse-lookup" />
-      <path value="$PROJECT_DIR$/src-php/vendor/sebastian/comparator" />
-      <path value="$PROJECT_DIR$/src-php/vendor/sebastian/complexity" />
-      <path value="$PROJECT_DIR$/src-php/vendor/sebastian/diff" />
       <path value="$PROJECT_DIR$/src-php/vendor/phpunit/php-code-coverage" />
-      <path value="$PROJECT_DIR$/src-php/vendor/sebastian/environment" />
       <path value="$PROJECT_DIR$/src-php/vendor/phpunit/php-file-iterator" />
-      <path value="$PROJECT_DIR$/src-php/vendor/sebastian/exporter" />
       <path value="$PROJECT_DIR$/src-php/vendor/phpunit/php-invoker" />
-      <path value="$PROJECT_DIR$/src-php/vendor/sebastian/global-state" />
       <path value="$PROJECT_DIR$/src-php/vendor/phpunit/php-text-template" />
-      <path value="$PROJECT_DIR$/src-php/vendor/sebastian/lines-of-code" />
+      <path value="$PROJECT_DIR$/src-php/vendor/phpunit/phpunit" />
       <path value="$PROJECT_DIR$/src-php/vendor/phpunit/php-timer" />
+      <path value="$PROJECT_DIR$/src-php/vendor/sebastian/type" />
+      <path value="$PROJECT_DIR$/src-php/vendor/psr/simple-cache" />
+      <path value="$PROJECT_DIR$/src-php/vendor/sebastian/diff" />
+      <path value="$PROJECT_DIR$/src-php/vendor/psr/http-message" />
+      <path value="$PROJECT_DIR$/src-php/vendor/sebastian/lines-of-code" />
+      <path value="$PROJECT_DIR$/src-php/vendor/psr/cache" />
       <path value="$PROJECT_DIR$/src-php/vendor/sebastian/object-enumerator" />
-      <path value="$PROJECT_DIR$/src-php/vendor/phpunit/phpunit" />
-      <path value="$PROJECT_DIR$/src-php/vendor/sebastian/object-reflector" />
+      <path value="$PROJECT_DIR$/src-php/vendor/psr/http-factory" />
+      <path value="$PROJECT_DIR$/src-php/vendor/sebastian/comparator" />
+      <path value="$PROJECT_DIR$/src-php/vendor/psr/container" />
+      <path value="$PROJECT_DIR$/src-php/vendor/sebastian/code-unit" />
+      <path value="$PROJECT_DIR$/src-php/vendor/sebastian/exporter" />
+      <path value="$PROJECT_DIR$/src-php/vendor/sebastian/cli-parser" />
+      <path value="$PROJECT_DIR$/src-php/vendor/sebastian/version" />
+      <path value="$PROJECT_DIR$/src-php/vendor/sebastian/complexity" />
       <path value="$PROJECT_DIR$/src-php/vendor/sebastian/recursion-context" />
-      <path value="$PROJECT_DIR$/src-php/vendor/n2n/n2n-batch" />
+      <path value="$PROJECT_DIR$/src-php/vendor/phar-io/version" />
       <path value="$PROJECT_DIR$/src-php/vendor/sebastian/resource-operations" />
-      <path value="$PROJECT_DIR$/src-php/vendor/sebastian/type" />
-      <path value="$PROJECT_DIR$/src-php/vendor/sebastian/version" />
-      <path value="$PROJECT_DIR$/src-php/vendor/psr/container" />
-      <path value="$PROJECT_DIR$/src-php/vendor/psr/http-factory" />
-      <path value="$PROJECT_DIR$/src-php/vendor/psr/http-message" />
+      <path value="$PROJECT_DIR$/src-php/vendor/phar-io/manifest" />
+      <path value="$PROJECT_DIR$/src-php/vendor/sebastian/environment" />
+      <path value="$PROJECT_DIR$/src-php/vendor/sebastian/global-state" />
+      <path value="$PROJECT_DIR$/src-php/vendor/sebastian/code-unit-reverse-lookup" />
+      <path value="$PROJECT_DIR$/src-php/vendor/sebastian/object-reflector" />
     </include_path>
   </component>
-  <component name="PhpProjectSharedConfiguration" php_language_level="8.1" />
+  <component name="PhpProjectSharedConfiguration" php_language_level="8.2">
+    <option name="suggestChangeDefaultLanguageLevel" value="false" />
+  </component>
   <component name="PhpStanOptionsConfiguration">
     <option name="transferred" value="true" />
   </component>

Diferenças do arquivo suprimidas por serem muito extensas
+ 452 - 173
composer.lock


+ 6 - 0
src-angl/src/app/components/article-detail/types.ts

@@ -0,0 +1,6 @@
+export interface Article {
+	id: number;
+	name: string;
+	category: string;
+	text: null | string;
+}

Diferenças do arquivo suprimidas por serem muito extensas
+ 5 - 0
src-angl/src/assets/logo.svg


+ 24 - 14
src-php/app/internship/bo/Article.php

@@ -1,11 +1,18 @@
 <?php
 namespace internship\bo;
 
+use n2n\persistence\orm\annotation\AnnoManyToOne;
+use n2n\persistence\orm\annotation\AnnoOneToMany;
+use n2n\persistence\orm\attribute\ManyToOne;
+use n2n\reflection\annotation\AnnoInit;
 use n2n\reflection\ObjectAdapter;
 
 class Article extends ObjectAdapter implements \JsonSerializable {
+    private static function _annos(AnnoInit $ai): void{
+        $ai->p('category', new AnnoManyToOne(Category::getClass()));
+    }
 	private int $id;
-	private string $categoryName;
+    private Category $category;
 	private string $text;
 	private string $title;
 
@@ -23,19 +30,22 @@ class Article extends ObjectAdapter implements \JsonSerializable {
 		$this->id = $id;
 	}
 
-	/**
-	 * @return string
-	 */
-	public function getCategoryName(): string {
-		return $this->categoryName;
-	}
+    /**
+     * @return Category
+     */
+    public function getCategory(): Category
+    {
+        return $this->category;
+    }
+
+    /**
+     * @param Category $category
+     */
+    public function setCategory(Category $category): void
+    {
+        $this->category = $category;
+    }
 
-	/**
-	 * @param string $categoryName
-	 */
-	public function setCategoryName(string $categoryName): void {
-		$this->categoryName = $categoryName;
-	}
 
 	/**
 	 * @return string
@@ -69,7 +79,7 @@ class Article extends ObjectAdapter implements \JsonSerializable {
         return [
             'id' => $this->id,
             'title' => $this->title,
-            'categoryName' => $this->categoryName,
+            'categoryName' => $this->category->getName(),
             'text' => $this->text
         ];
     }

+ 90 - 0
src-php/app/internship/bo/Category.php

@@ -0,0 +1,90 @@
+<?php
+
+namespace internship\bo;
+
+use ArrayObject;
+use n2n\persistence\orm\annotation\AnnoOneToMany;
+use n2n\reflection\annotation\AnnoInit;
+use n2n\reflection\ObjectAdapter;
+
+class Category extends ObjectAdapter implements \JsonSerializable {
+
+    private static function _annos(AnnoInit $ai) {
+        $ai->p('articles', new AnnoOneToMany(Article::getClass(), 'category'));
+    }
+
+    private int $id;
+
+    private ArrayObject $articles;
+    private string $name;
+    private string $text;
+
+    /**
+     * @return int
+     */
+    public function getId(): int {
+        return $this->id;
+    }
+
+    /**
+     * @param int $id
+     */
+    public function setId(int $id): void {
+        $this->id = $id;
+    }
+
+    /**
+     * @return string
+     */
+    public function getName(): string {
+        return $this->name;
+    }
+
+    /**
+     * @param string $name
+     */
+    public function setName(string $name): void {
+        $this->name = $name;
+    }
+
+    /**
+     * @return string
+     */
+    public function getText(): string {
+        return $this->text;
+    }
+
+    /**
+     * @param string $text
+     */
+    public function setText(string $text): void {
+        $this->text = $text;
+    }
+
+    /**
+     * @return ArrayObject
+     */
+    public function getArticles(): ArrayObject
+    {
+        return $this->articles;
+    }
+
+    /**
+     * @param ArrayObject $articles
+     */
+    public function setArticles(ArrayObject $articles): void
+    {
+        $this->articles = $articles;
+    }
+
+    function jsonSerialize(): mixed {
+        return [
+            'id' => $this->id,
+            'title' => $this->name,
+            'text' => $this->text
+        ];
+    }
+
+
+
+}

+ 184 - 69
src-php/app/internship/controller/ArticleController.php

@@ -1,20 +1,51 @@
 <?php
 namespace internship\controller;
 
+
+
+use internship\model\CategoryDao;
+use n2n\web\http\BadRequestException;
 use n2n\web\http\controller\ControllerAdapter;
 use internship\model\ArticleDao;
 use n2n\context\attribute\Inject;
 use n2n\web\http\controller\ParamBody;
 use internship\bo\Article;
+use internship\bo\Category;
+use n2n\web\http\ForbiddenException;
 use n2n\web\http\PageNotFoundException;
+use n2n\web\http\StatusException;
+use n2n\web\http\annotation\AnnoPut;
 
 /**
  * REST Controller
  * https://dev.n2n.rocks/de/n2n/docs/rest
  */
+
 class ArticleController extends ControllerAdapter {
+
+//	private static function _annos(AnnoInit $ai): void {
+//		$ai->m('putDoArticle', new AnnoPut());
+//	}
 	#[Inject]
 	private ArticleDao $articleDao;
+    #[Inject]
+    private CategoryDao $categoryDao;
+
+
+	function prepare(): void {
+		if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
+			header('Access-Control-Allow-Origin: *');
+			header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
+			header('Access-Control-Allow-Headers: Content-Type, Authorization');
+			header('Access-Control-Max-Age: 86400'); // cache preflight
+			http_response_code(204); // No Content
+			exit;
+		}
+		$this->getResponse()->setHeader("Access-Control-Allow-Headers: X-Requested-With, Content-Type,Accept, Origin");
+		$this->getResponse()->setHeader("Access-Control-Allow-Origin: http://localhost:4200");
+		$this->getResponse()->setHeader('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, HEAD, OPTIONS');
+	}
+
 
 	/**
 	 * Gibt den {@see Article} mit der entsprechenden id im JSON Format zurück.
@@ -41,90 +72,174 @@ class ArticleController extends ControllerAdapter {
 	 *
 	 * @param int $articleId
 	 * @return void
+     * @throws ForbiddenException if article 1 is queried (for testing).
 	 * @throws PageNotFoundException if Article could not be found.
 	 */
 	function getDoArticle(int $articleId): void {
-
+        $article = $this->articleDao->getArticleById($articleId);
+        if ($article === null) {
+            throw new PageNotFoundException('Article does not exist');
+        }
+        $this->sendJson($article,true);
 	}
 
-	/**
-	 * Diese Methode kannst du im Browser testen. Pfad: localhost/[ordner name vom projekt]/src-php/public/api/articles
-	 *
-	 * <ul>
-	 * 	<li>
- 	 *		Implementiere und nutze die Methode {@see ArticleDao::getArticles()}, um Artikel aus der Datenbank zu lesen
-	 * 		und gebe diese anschliessend im JSON Format zurück.
-	 *
-	 * 		Article-Objekte kannst du einfach {@see $this->sendJson()} übergeben, um einen validen JSON-Response zu
-	 * 		generieren.
-	 * 	</li>
-	 * 	<li>
-	 * 		Wird ein $categoryName übergeben, gebe nur die Artikel aus, die über diesen Kategorienamen verfügen.
-	 * 	</li>
-	 * </ul>
-	 *
-	 * @param string|null $categoryName
-	 * @return void
-	 */
+    /**
+     * Diese Methode kannst du im Browser testen. Pfad: localhost/[ordner name vom projekt]/src-php/public/api/articles
+     *
+     * <ul>
+     *    <li>
+     *        Implementiere und nutze die Methode {@see ArticleDao::getArticles()}, um Artikel aus der Datenbank zu lesen
+     *        und gebe diese anschliessend im JSON Format zurück.
+     *
+     *        Article-Objekte kannst du einfach {@see $this->sendJson()} übergeben, um einen validen JSON-Response zu
+     *        generieren.
+     *    </li>
+     *    <li>
+     *        Wird ein $categoryName übergeben, gebe nur die Artikel aus, die über diesen Kategorienamen verfügen.
+     *    </li>
+     * </ul>
+     *
+     * @param null | string $categoryName
+     * @return void
+     * @throws PageNotFoundException if category could not be found.
+     */
 	function getDoArticles(string $categoryName = null): void {
+        if ($categoryName === null) {
+            $articles = $this->articleDao->getArticles();
+            $this->sendJson($articles);
+            return;
+        }
+        $articles = $this->articleDao->getArticlesByCategoryName($categoryName);
+        if (empty($articles)) {
+            throw new PageNotFoundException('category name was not found!');
+        }
+        $this->sendJson($articles);
 	}
 
-	/**
-	 * Speichere einen Artikel.
-	 *
-	 * <ul>
-	 * 	<li>
-	 * 		Der Kategoriename darf nur 'international','national' oder 'sport' sein.
-	 * 		Validiere dies und schmeisse im Fehlerfall eine: {@see BadRequestException}
-	 * 	</li>
-	 * 	<li>
-	 *		Mache eine neue Entity {@see Article} und befülle sie mit den übergebenen Daten.
-	 * 	</li>
-	 *  <li>
-	 *		Implementiere eine neue Methode im {@see ArticleDao} und bennene sie "saveArticle".
-	 * 	 </li>
-	 * </ul>
-	 *
-	 * Nenne die Methode {@see saveArticle(Article $article)}
-	 *
-	 * @return void
-	 */
+    /**
+     * Speichere einen Artikel. Es wird die id (fremdschlüssel) der kategorie übergeben.
+     * Dieses wird in den namen der kategorie aufgelöst
+     *
+     * <ul>
+     *    <li>
+     *        Der Kategoriename darf nur 'international','national' oder 'sport' sein.
+     *        Validiere dies und schmeisse im Fehlerfall eine: {@see BadRequestException}
+     *    </li>
+     *    <li>
+     *        Mache eine neue Entity {@see Article} und befülle sie mit den übergebenen Daten.
+     *    </li>
+     *  <li>
+     *        Implementiere eine neue Methode im {@see ArticleDao} und bennene sie "saveArticle".
+     *     </li>
+     * </ul>
+     *
+     * Nenne die Methode {@see saveArticle(Article $article)}
+     *
+     *
+     * @param ParamBody $body
+     * @return void
+     * @throws BadRequestException if category is not 'Travel', 'Health' oder 'Finance'
+     * @throws StatusException
+     */
 	function postDoArticle(ParamBody $body): void {
-		$body->parseJson();
+        $httpData = $body->parseJsonToHttpData();
+        $title = $httpData->reqString('title');
+        $text = $httpData->optString('text','',true);
+
+        $categoryId = $httpData->reqInt('categoryId');
+        $category = $this->categoryDao->getCategoryById($categoryId);
+
+        if ($category === null) {
+            throw new BadRequestException('category id has no corresponding category!');
+        }
+
+        $article = new Article();
+        $article->setCategory($category);
+        $article->setTitle($title);
+        $article->setText($text );
+
+
+        $this->beginTransaction();
+        $this->articleDao->saveArticle($article);
+        $this->commit();
+
+        $this->sendJson($article);
+//        echo 'article saved successfully.';
 	}
 
-	/**
-	 * Editiere einen Artikel.
-	 *
-	 * <ul>
-	 * 	<li>
-	 * 		Der Kategoriename darf nur 'international','national' oder 'sport' sein.
-	 * 		Validiere dies und schmeisse im Fehlerfall eine: {@see BadRequestException}
-	 * 	</li>
-	 * 	<li>
-	 *		Finde den dazugehörigen {@see Article} und passe die Daten mit denjenigen Daten von gestern ab.
-	 * 	</li>
-	 *  <li>
-	 *		Implementiere eine neue Methode im {@see ArticleDao} und nenne die Methode {@see saveArticle(Article $article)}.
-	 * 	 </li>
-	 * </ul>
-	 *
-	 * @return void
-	 */
+    /**
+     * Editiere einen Artikel.
+     *
+     * <ul>
+     *    <li>
+     *        Der Kategoriename darf nur 'international','national' oder 'sport' sein.
+     *        Validiere dies und schmeisse im Fehlerfall eine: {@see BadRequestException}
+     *    </li>
+     *    <li>
+     *        Finde den dazugehörigen {@see Article} und passe die Daten mit denjenigen Daten von gestern ab.
+     *    </li>
+     *  <li>
+     *        Implementiere eine neue Methode im {@see ArticleDao} und nenne die Methode {@see saveArticle(Article $article)}.
+     *     </li>
+     * </ul>
+     *
+     * @param int $articleId
+     * @param ParamBody $body
+     * @return void
+     * @throws BadRequestException if category is not 'Travel', 'Health' oder 'Finance'
+     * @throws PageNotFoundException
+     * @throws StatusException
+     */
 	function putDoArticle(int $articleId, ParamBody $body): void {
 
+        $this->beginTransaction();
+        $article = $this->articleDao->getArticleById($articleId);
+        if ($article === null) {
+            throw new PageNotFoundException('article does not exist');
+        }
+
+       $httpData = $body->parseJsonToHttpData();
+        $title = $httpData->optString('title');
+        $text = $httpData->optString('text');
+        $categoryId = $httpData->reqInt('categoryId');
+        $category = $this->categoryDao->getCategoryById($categoryId);
+        if ($category === null) {
+            throw new PageNotFoundException('category id has no corresponding category!');
+        }
+
+        if ($title !== null) {
+            $article->setTitle($title);
+        }
+        if ($text !== null) {
+            $article->setText($text );
+        }
+        $article->setCategory($category);
+//        $this->articleDao->saveArticle($article);
+        $this->commit();
+        $this->sendJson($article);
+//        echo 'article with id: '.$articleId.' edited successfully.';
 	}
 
-	/**
-	 * Löscht den {@see Article} mit der dazugehörigen Id.
-	 *
-	 * <ul>
-	 * 	<li>Mache eine neue Methode im {@see ArticleDao} und benenne sie "removeArticle(int articleId) </li>
-	 * <ul>
-	 *
-	 * @return void
-	 */
+    /**
+     * Löscht den {@see Article} mit der dazugehörigen Id.
+     *
+     * <ul>
+     *    <li>Mache eine neue Methode im {@see ArticleDao} und benenne sie "removeArticle(int articleId) </li>
+     * <ul>
+     *
+     * @param int $articleId
+     * @return void
+     * @throws PageNotFoundException if the article does not exist
+     */
 	function deleteDoArticle(int $articleId): void {
-
+        $this->beginTransaction();
+		$article = $this->articleDao->getArticleById($articleId);
+		if ($article === null) {
+			throw new PageNotFoundException('The article you are trying to delete does not exist.');
+		}
+        $this->articleDao->removeArticle($article);
+        $this->commit();
+        $this->sendJson($article);
+//        echo 'article with id ' .$articleId. ' was removed.';
 	}
 }

+ 151 - 0
src-php/app/internship/controller/CategoryController.php

@@ -0,0 +1,151 @@
+<?php
+
+namespace internship\controller;
+
+use internship\bo\Category;
+use internship\model\CategoryDao;
+use n2n\context\attribute\Inject;
+use n2n\web\http\BadRequestException;
+use n2n\web\http\controller\ControllerAdapter;
+use n2n\web\http\controller\ParamBody;
+use n2n\web\http\PageNotFoundException;
+use n2n\web\http\StatusException;
+
+/**
+ * REST Controller
+ * https://dev.n2n.rocks/de/n2n/docs/rest
+ */
+class CategoryController extends ControllerAdapter {
+    #[Inject]
+    private CategoryDao $categoryDao;
+
+	function prepare(): void {
+		if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
+			header('Access-Control-Allow-Origin: *');
+			header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
+			header('Access-Control-Allow-Headers: Content-Type, Authorization');
+			header('Access-Control-Max-Age: 86400'); // cache preflight
+			http_response_code(204); // No Content
+			exit;
+		}
+		$this->getResponse()->setHeader("Access-Control-Allow-Headers: X-Requested-With, Content-Type,Accept, Origin");
+		$this->getResponse()->setHeader("Access-Control-Allow-Origin: http://localhost:4200");
+		$this->getResponse()->setHeader('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, HEAD, OPTIONS');
+	}
+    /**
+     * gibt die {@see Category} mit der entsprechenden id im JSON format zurück.
+     *
+     * @param int $categoryId
+     * @return void
+     * @throws PageNotFoundException if the category could not be found.
+     */
+    function getDoCategory(int $categoryId): void {
+        $category = $this->categoryDao->getCategoryById($categoryId);
+        if ($category === null) {
+            throw new PageNotFoundException('category does not exist');
+        }
+        $this->sendJson($category,true);
+    }
+
+    /**
+     * Gibt alle {@see Category} im JSON Format zurück.
+     *
+     * @return void
+     * @throws PageNotFoundException if category could not be found.
+     */
+    function getDoCategories(): void {
+        $categories = $this->categoryDao->getCategories();
+        $this->sendJson($categories);
+    }
+
+    /**
+     * Speichere eine {@see Category}.
+     *
+     * @param ParamBody $body
+     * @throws StatusException
+     * @throws BadRequestException if category is not 'Travel', 'Health' oder 'Finance'
+     */
+
+    function postDoCategory(ParamBody $body) :void {
+        $httpData = $body->parseJsonToHttpData();
+        $text = $httpData->reqString('text');
+        $categoryName = $httpData->reqString('category_name');
+
+        $uniqueCategories = $this->categoryDao->getCategories();
+        $uniqueCategoryNames = array_column($uniqueCategories,'name');
+        if(in_array($categoryName,$uniqueCategoryNames)) {
+            throw new BadRequestException('Category Name already exists');
+        }
+
+        $category = new Category();
+        $category->setName($categoryName);
+        $category->setText($text);
+
+        if ($category->getName() != 'Travel'
+                && $category->getName() != 'Finance'
+                && $category->getName() != 'Health') {
+            throw new BadRequestException('Category name not supported.');
+        }
+
+        $this->beginTransaction();
+        $this->categoryDao->saveCategory($category);
+        $this->commit();
+        echo 'Category saved successfully';
+    }
+
+    /**
+     * Editiere eine {@see Category}.
+     *
+     * @param int $categoryId
+     * @param ParamBody $body
+     * @throws BadRequestException if category is not 'Travel', 'Health' oder 'Finance'
+     * @throws StatusException
+     */
+
+    function putDoCategory(int $categoryId, ParamBody $body): void {
+        $httpData = $body->parseJsonToHttpData();
+
+        $name = $httpData->optString('category_name');
+        $text = $httpData->optString('text');
+        $category = $this->categoryDao->getCategoryById($categoryId);
+
+        if($category === null) {
+            throw new BadRequestException('The category you are trying to edit does not exist!');
+        }
+
+        if ($category->getName() != 'Travel'
+            && $category->getName() != 'Finance'
+            && $category->getName() != 'Health') {
+            throw new BadRequestException('Category name not supported');
+        }
+
+        if ($name) {
+            $category->setName($name);
+        }
+        if ($text) {
+            $category->setText($text);
+        }
+
+        $this->beginTransaction();
+        $this->categoryDao->saveCategory($category);
+        $this->commit();
+        echo 'category saved successfully.';
+    }
+
+    /**
+     * Löscht die {@see Category} mit der dazugehörigen Id.
+     *
+     * @return void
+     * @throws PageNotFoundException if the category does not exist
+     */
+    function deleteDoCategory(int $categoryId): void {
+        $category = $this->categoryDao->getCategoryById($categoryId);
+        if ($category === null) {
+            throw new PageNotFoundException('The category you are trying to delete does not exist.');
+        }
+        $this->beginTransaction();
+        $this->categoryDao->removeCategory($category);
+        $this->commit();
+        echo 'category with id ' .$categoryId. ' was removed.';
+    }
+}

+ 51 - 15
src-php/app/internship/model/ArticleDao.php

@@ -3,10 +3,11 @@ namespace internship\model;
 
 use n2n\persistence\orm\EntityManager;
 use internship\bo\Article;
+use internship\bo\Category;
 use n2n\context\attribute\RequestScoped;
 
 /**
- * Benutze diese Klasse um Datenbankabfragen auszuführen.
+ * Benutze diese Klasse, um Datenbankabfragen auszuführen.
  * Du findest alle Informationen zu Datenbankabfragen in der Doku:
  * https://dev.n2n.rocks/de/n2n/docs/persistence-orm#entitymanager
  */
@@ -24,25 +25,60 @@ class ArticleDao {
 	 * @return Article[]
 	 */
 	function getArticles(): array {
-
+        $criteria = $this->em->createNqlCriteria(
+                'SELECT a FROM Article a ORDER BY a.id DESC');
+        return $criteria->toQuery()->fetchArray();
 	}
 
-	/**
-	 * Gebe alle {@see Article} zurück, welche dem übergebenen Kategorienamen entsprechen.
-	 *
-	 * @return array
-	 */
+    /**
+     * Gebe den {@see Article} mit der enstprechenden ID zurück.
+     *
+     * @param int $id
+     * @return Article|null
+     */
+    function getArticleById(int $id): ?Article {
+        return $this->em->find(Article::getClass(),$id);
+    }
+
+    /**
+     * Gebe alle {@see Article} zurück, welche der übergebenen {@see Category} entsprechen.
+     *
+     * @param Category $category
+     * @return array
+     */
 	function getArticlesByCategoryName(string $categoryName): array {
+//        $criteria = $this->em->createSimpleCriteria(Article::getClass(),
+//              ['categoryName' => $categoryName]);
+//        return $criteria->toQuery()->fetchArray();
 
+//        $criteria = $this->em->createNqlCriteria(
+//                'SELECT a FROM Article a WHERE a.categoryName = :category',
+//                array('category' => $category));
+
+        $criteria = $this->em->createCriteria();
+        $criteria->select('a')
+            ->from(Article::getClass(),'a')
+            ->where(['a.category.name' => $categoryName]);
+        return $criteria->toQuery()->fetchArray();
 	}
 
-	/**
-	 * Gebe den {@see Article} mit der enstprechenden ID zurück.
-	 *
-	 * @param int $id
-	 * @return Article|null
-	 */
-	function getArticleById(int $id): ?Article {
+    /**
+     * Speichere den {@see Article} in der Datenbank.
+     *
+     * @param Article $article
+     * @return void
+     */
+    function saveArticle(Article $article): void {
+        $this->em->persist($article);
+    }
 
-	}
+    /**
+     * Lösche den {@see Article} aus der Datenbank.
+     *
+     * @param Article $article
+     * @return void
+     */
+    function removeArticle(Article $article): void {
+        $this->em->remove($article);
+    }
 }

+ 71 - 0
src-php/app/internship/model/CategoryDao.php

@@ -0,0 +1,71 @@
+<?php
+
+namespace internship\model;
+
+use internship\bo\Category;
+use n2n\persistence\orm\EntityManager;
+use internship\bo\Article;
+use n2n\context\attribute\RequestScoped;
+
+#[RequestScoped]
+class CategoryDao {
+
+    private EntityManager $em;
+
+    private function _init(EntityManager $em): void {
+        $this->em = $em;
+    }
+
+    /**
+     * Gebe alle {@see Category}-Objekte, nach id aufsteigend sortiert, zurück.
+     *
+     * @return Category[]
+     */
+    function getCategories(): array {
+        $criteria = $this->em->createNqlCriteria(
+            'SELECT c FROM Category c ORDER BY c.id DESC');
+        return $criteria->toQuery()->fetchArray();
+    }
+
+    /**
+     * Gebe die {@see Category} mit der enstprechenden ID zurück.
+     *
+     * @param int $id
+     * @return Category|null
+     */
+    function getCategoryById(int $id): ?Category {
+        return $this->em->find(Category::getClass(),$id);
+    }
+
+    /**
+     * Gebe alle {@see Category} zurück, welche dem übergebenen Kategorienamen entsprechen.
+     *
+     * @param string $categoryName
+     * @return array
+     */
+    function getCategoryByCategoryName(string $categoryName): array {
+        $criteria = $this->em->createSimpleCriteria(Category::getClass(),
+              ['category_name' => $categoryName]);
+        return $criteria->toQuery()->fetchArray();
+    }
+
+    /**
+     * Speichere die {@see Category} in der Datenbank.
+     *
+     * @param Category $category
+     * @return void
+     */
+    function saveCategory(Category $category): void {
+        $this->em->persist($category);
+    }
+
+    /**
+     * Lösche die {@see Category} aus der Datenbank.
+     *
+     * @param Category $category
+     * @return void
+     */
+    function removeCategory(Category $category): void {
+        $this->em->remove($category);
+    }
+}

+ 2 - 1
src-php/public/assets/.gitignore

@@ -1,3 +1,4 @@
 /n2n-impl-web-ui
 /n2n-impl-web-dispatch
-/n2n
+/n2n
+/n2n-web

+ 212 - 5
src-php/test/internship/controller/ArticleControllerTest.php

@@ -2,9 +2,13 @@
 
 namespace internship\controller;
 
+use internship\bo\Article;
+use n2n\web\http\BadRequestException;
+use n2n\web\http\PageNotFoundException;
+use n2n\web\http\StatusException;
 use PHPUnit\Framework\TestCase;
 use n2n\test\TestEnv;
-use internship\test\ArticleTestEnv;
+use internship\test\InternshipTestEnv;
 use util\GeneralTestEnv;
 
 
@@ -14,21 +18,65 @@ class ArticleControllerTest extends TestCase {
 	private int $article2Id;
 	private int $article3Id;
 
+    private int $category1Id;
+
+    /**
+     * @return void
+     */
 	function setUp(): void {
 		GeneralTestEnv::tearDown();
 
 		$tx = TestEnv::createTransaction();
-		$article1 = ArticleTestEnv::setUpArticle('Title 1', 'Loren ipsum 1', 'teaser');
-		$article2 = ArticleTestEnv::setUpArticle('Title 2', 'Loren ipsum 2', 'news');
-		$article3 = ArticleTestEnv::setUpArticle('Title 3', 'Loren ipsum 3', 'news');
+        $category1 = InternshipTestEnv::setUpCategory('category1');
+        $category2 = InternshipTestEnv::setUpCategory('category2');
+		$article1 = InternshipTestEnv::setUpArticle('Title 1', 'Loren ipsum 1', $category1);
+		$article2 = InternshipTestEnv::setUpArticle('Title 2', 'Loren ipsum 2', $category1);
+		$article3 = InternshipTestEnv::setUpArticle('Title 3', 'Loren ipsum 3', $category2);
 		$tx->commit();
 
 		$this->article1Id = $article1->getId();
 		$this->article2Id = $article2->getId();
 		$this->article3Id = $article3->getId();
+
+        $this->category1Id = $category1->getId();
 	}
 
-	function testGetDoArticles() {
+    /**
+     * Get one article by id.
+     *
+     * @return void
+     * @throws StatusException
+     */
+    function testGetDoArticle(): void {
+        $responseJson = TestEnv::http()->newRequest()
+            ->get(['api', 'article','1'])
+            ->exec();
+
+        $response = $responseJson->parseJson();
+        $this->assertEquals('Title 1',$response['title']);
+        $this->assertEquals('Loren ipsum 1',$response['text']);
+        $this->assertEquals('category1',$response['categoryName']);
+    }
+
+    /**
+     * @return void
+     * @throws StatusException
+     */
+    function testGetDoArticleExpectExceptionBecauseWrongArticleID(): void {
+
+        $this->expectException(PageNotFoundException::class);
+        $responseJson = TestEnv::http()->newRequest()
+            ->get(['api', 'article','15'])
+            ->exec();
+    }
+
+    /**
+     * Get all articles.
+     *
+     * @return void
+     * @throws StatusException
+     */
+    function testGetDoArticles(): void {
 		$response = TestEnv::http()->newRequest()
 				->get(['api', 'articles'])
 				->exec();
@@ -41,4 +89,163 @@ class ArticleControllerTest extends TestCase {
 		$this->assertEquals('Title 1', $articleStructs[2]['title']);
 	}
 
+    /**
+     * Exception because category does not exist
+     *
+     * @return void
+     * @throws StatusException
+     */
+    function testGetDoArticlesExpectExceptionBecauseWrongCategoryName(): void {
+        $this->expectException(PageNotFoundException::class);
+
+        TestEnv::http()->newRequest()
+            ->get(['api', 'articles','category3'])
+            ->exec();
+    }
+
+    /**
+     * @return void
+     * @throws StatusException
+     */
+    function testPostDoArticle(): void {
+        $responseJson = TestEnv::http()->newRequest()
+            ->post(['api', 'article'])
+            ->bodyJson(['categoryId' => 1, 'title' => 'All the Travel',
+                    'text' => 'hello world'])
+            ->exec();
+        $article =  $responseJson->parseJson();
+        $this->assertEquals('All the Travel', $article['title']);
+
+        $transaction = TestEnv::createTransaction(true);
+        $correctArticle = TestEnv::tem()->find(Article::class,4);
+        $this->assertEquals('All the Travel', $correctArticle->getTitle());
+        $transaction->commit();
+    }
+
+    /**
+     * When osting an {@see Article} without text, the text should be set to ''.
+     *
+     * @throws StatusException
+     */
+    function testPostDoArticleWithoutText() {
+        $response = TestEnv::http()->newRequest()
+            ->post(['api','article'])
+            ->body('{"categoryId": 1, "title": "All the Travel"}')
+            ->exec();
+
+
+        $this->assertEquals('200',$response->getStatus());
+        $transaction = TestEnv::createTransaction(true);
+        $article = TestEnv::tem()->find(Article::class,4);
+        $transaction->commit();
+        $this->assertEquals('',$article->getText());
+    }
+
+    /**
+     * @throws StatusException
+     */
+    function testPostDoArticleExpectExceptionBecauseCategoryDoesNotExist() {
+        $this->expectException(BadRequestException::class);
+        TestEnv::http()->newRequest()
+            ->post(['api','article'])
+            ->body('{"categoryId": 3, "title": "All the Travel", "text": "hello world"}')
+            ->exec();
+    }
+
+    /**
+     * Test: update the name of $article3. Then check if the name no longer matches the old one.
+     *
+     * @return void
+     * @throws StatusException
+     */
+    function testPutDoArticle(): void {
+        $response = TestEnv::http()->newRequest()
+            ->put(['api', 'article','3'])
+            ->bodyJson(['categoryId' => $this->category1Id , 'title'=> 'New Title','text'=>'Hello'])
+            ->exec();
+
+        $articleJson = $response->parseJson();
+        $this->assertEquals('New Title', $articleJson['title']);
+        $this->assertEquals('Hello', $articleJson['text']);
+        $this->assertEquals('category1', $articleJson['categoryName']);
+
+
+        $tx = TestEnv::createTransaction(true);
+        $article = TestEnv::tem()->find(Article::class, 3);
+        $this->assertEquals(3, TestEnv::temUtil()->count(Article::class));
+        $this->assertEquals('New Title', $article->getTitle());
+        $this->assertEquals(3, $article->getId());
+        $tx->commit();
+
+    }
+
+    /**
+     * @return void
+     * @throws StatusException
+     */
+    function testPutDoArticleExpectExceptionBecauseWrongArticleId(): void {
+        $this->expectException(PageNotFoundException::class);
+        $response = TestEnv::http()->newRequest()
+                ->put(['api', 'article','15'])
+                ->bodyJson(['categoryId'=> $this->category1Id , 'title'=> 'New Title','text'=>'Hello'])
+                ->exec();
+    }
+
+    /**
+     * @return void
+     * @throws StatusException
+     */
+    function testPutDoArticleExpectExceptionBecauseWrongCategoryId(): void {
+        $this->expectException(PageNotFoundException::class);
+        $response = TestEnv::http()->newRequest()
+                ->put(['api', 'article','3'])
+                ->bodyJson(['categoryId'=> 15 , 'title'=> 'New Title','text'=>'Hello'])
+                ->exec();
+    }
+
+    /**
+     * @return void
+     * @throws StatusException
+     */
+    function testPutDoArticleExpectExceptionBecauseNoCategoryId(): void {
+        $this->expectException(StatusException::class);
+//        $this->expectExceptionCode('0');
+        $this->expectExceptionMessage('Missing property: categoryId');
+        $response = TestEnv::http()->newRequest()
+                ->put(['api', 'article','3'])
+                ->bodyJson(['title'=> 'New Title','text'=>'Hello'])
+                ->exec();
+    }
+
+    /**
+     * Delete $article3 then check if article count is 2.
+     *
+     * @return void
+     * @throws StatusException
+     */
+    function testDeleteDoArticles () {
+        $response = TestEnv::http()->newRequest()
+                ->delete(['api','article','3'])
+                ->exec();
+        $this->assertEquals(200,$response->getStatus());
+        $transaction = TestEnv::createTransaction(true);
+        $this->assertEquals(2, TestEnv::temUtil()->count(Article::class));
+        $this->assertEquals(null, TestEnv::tem()->find(Article::class,3));
+        $transaction->commit();
+    }
+
+    /**
+     * Delete request with an articleId that does not exist
+     *
+     * @throws StatusException
+     */
+    function testDeleteDoArticlesExpectExceptionBecauseWrongArticleId () {
+
+        $this->expectException(PageNotFoundException::class);
+        TestEnv::http()->newRequest()
+				->delete(['api','article','15'])
+				->exec();
+
+    }
+
 }

+ 18 - 0
src-php/test/internship/controller/CategoryControllerTest.php

@@ -0,0 +1,18 @@
+<?php
+
+namespace internship\controller;
+
+use n2n\web\http\StatusException;
+use PHPUnit\Framework\TestCase;
+use n2n\test\TestEnv;
+use internship\test\InternshipTestEnv;
+use util\GeneralTestEnv;
+
+//class CategoryControllerTest extends TestCase {
+//
+//    private int $category1Id;
+//    private int $category2Id;
+//    private int $category3Id;
+//
+//
+//}

+ 0 - 20
src-php/test/internship/test/ArticleTestEnv.php

@@ -1,20 +0,0 @@
-<?php
-namespace internship\test;
-
-use n2n\test\TestEnv;
-use internship\bo\Article;
-
-class ArticleTestEnv  {
-
-	static function setUpArticle(string $title, string $text, string $categoryName): Article {
-		$article = new Article();
-		$article->setTitle($title);
-		$article->setText($text);
-		$article->setCategoryName($categoryName);
-
-		TestEnv::em()->persist($article);
-		TestEnv::em()->flush();
-
-		return $article;
-	}
- }

+ 33 - 0
src-php/test/internship/test/InternshipTestEnv.php

@@ -0,0 +1,33 @@
+<?php
+namespace internship\test;
+
+use internship\model\CategoryDao;
+use n2n\test\TestEnv;
+use internship\bo\Article;
+use internship\bo\Category;
+
+class InternshipTestEnv  {
+
+    static function setUpCategory(string $name): Category {
+        $category = new Category();
+        $category->setName($name);
+        $category->setText(uniqid());
+        TestEnv::em()->persist($category);
+        TestEnv::em()->flush();
+
+        return $category;
+    }
+
+	static function setUpArticle(string $title, string $text, Category $category): Article {
+        $article = new Article();
+		$article->setTitle($title);
+		$article->setText($text);
+        $article->setCategory($category);
+
+		TestEnv::em()->persist($article);
+		TestEnv::em()->flush();
+
+		return $article;
+	}
+
+ }

+ 11 - 2
src-php/var/bak/backup.sql

@@ -5,10 +5,19 @@
 DROP TABLE IF EXISTS `article`;
 CREATE TABLE `article` (
         `id` INT NOT NULL AUTO_INCREMENT,
-        `category_name` VARCHAR(255) NOT NULL ,
+        `category_id` INT(11) NOT NULL ,
         `title` VARCHAR(255) NOT NULL ,
         `text` TEXT NOT NULL,
         PRIMARY KEY (`id`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci ;
 INSERT INTO article VALUES (1, 'sport', 'Formula 1 Rennen endet im Drama', 'Das gestrige Rennen endete im Gerängel, Fans zofften sich weil Lewis Hamilton eine Bananenschale auf der Rennstrecke so positionierte, dass Max Verstappen darauf ausgerutscht und aus dem Rennen ausgeschieden ist.');
-INSERT INTO article VALUES (2, 'international', 'Kein Regen mehr in den Regenwäldern', 'Aus unbekannten und mysteriösen Gründen regnet es schon seit mehreren Monaten in keinem Regenwald auf dieser Erde mehr. Darum mussten leider die Regenwaldmeisterschaften in diesem Jahr ausfallen.');
+INSERT INTO article VALUES (2, 'international', 'Kein Regen mehr in den Regenwäldern', 'Aus unbekannten und mysteriösen Gründen regnet es schon seit mehreren Monaten in keinem Regenwald auf dieser Erde mehr. Darum mussten leider die Regenwaldmeisterschaften in diesem Jahr ausfallen.');
+
+
+DROP TABLE IF EXISTS `category`;
+CREATE TABLE `category` (
+       `id` INT NOT NULL AUTO_INCREMENT,
+       `name` VARCHAR(255) NOT NULL ,
+       `text` TEXT NOT NULL,
+       PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci ;

+ 2 - 1
src-php/var/etc/.gitignore

@@ -2,4 +2,5 @@
 /n2n-impl-web-dispatch
 /n2n
 /n2n-web
-/n2n-impl-persistence-orm
+/n2n-impl-persistence-orm
+/n2n-persistence

+ 4 - 1
src-php/var/etc/internship/app.ini

@@ -1,5 +1,8 @@
 [routing]
 controllers[/api] = "internship\controller\ArticleController"
+controllers[/api/cc] = "internship\controller\CategoryController"
+controllers[/bo] = "internship\controller\HelloController"
 
 [orm]
-entities[] = "internship\bo\Article"
+entities[] = "internship\bo\Article"
+entities[] = "internship\bo\Category"

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff