diff --git a/.idea/dataSources.local.xml b/.idea/dataSources.local.xml
new file mode 100644
index 0000000..cdc31de
--- /dev/null
+++ b/.idea/dataSources.local.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+ "
+
+
+ master_key
+ no-auth
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml
new file mode 100644
index 0000000..3f17fd0
--- /dev/null
+++ b/.idea/dataSources.xml
@@ -0,0 +1,12 @@
+
+
+
+
+ sqlite.xerial
+ true
+ org.sqlite.JDBC
+ jdbc:sqlite:$PROJECT_DIR$/db/articles
+ $ProjectFileDir$
+
+
+
\ No newline at end of file
diff --git a/.idea/dataSources/315cb5c9-2b0f-435b-b602-59823b160908.xml b/.idea/dataSources/315cb5c9-2b0f-435b-b602-59823b160908.xml
new file mode 100644
index 0000000..4fccad1
--- /dev/null
+++ b/.idea/dataSources/315cb5c9-2b0f-435b-b602-59823b160908.xml
@@ -0,0 +1,1833 @@
+
+
+
+
+ 3.51.1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+ window
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+ 1
+ 1
+
+
+
+
+ 1
+ 1
+
+
+ 1
+ 1
+
+
+ 1
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ window
+
+
+ window
+
+
+ window
+
+
+
+
+
+ 1
+ 1
+
+
+ 1
+ 1
+
+
+ 1
+
+
+ window
+
+
+
+ 1
+
+
+ window
+
+
+ 1
+
+
+ 1
+ 1
+
+
+
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ window
+
+
+ window
+
+
+ 1
+
+
+ 1
+
+
+ 1
+ 1
+
+
+ 1
+
+
+ 1
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+ 1
+
+
+ 1
+ window
+
+
+ 1
+ window
+
+
+ 1
+ 1
+
+
+ 1
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+ 1
+
+
+ 1
+ 1
+
+
+ 1
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+ 1
+
+
+ 1
+ 1
+
+
+ 1
+ window
+
+
+ 1
+ window
+
+
+ 1
+ 1
+
+
+ 1
+ 1
+
+
+ 1
+
+
+ 1
+ 1
+
+
+ 1
+ 1
+
+
+ 1
+ 1
+
+
+ 1
+ 1
+
+
+ window
+
+
+ window
+
+
+ window
+
+
+
+ window
+
+
+ window
+
+
+ window
+
+
+ window
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ aggregate
+
+
+ 1
+
+
+ 1
+
+
+
+
+
+ 1
+ 1
+
+
+ window
+
+
+ aggregate
+
+
+ 1
+ 1
+
+
+ window
+
+
+ 1
+
+
+ aggregate
+
+
+ window
+
+
+ window
+
+
+ 1
+
+
+ 1
+
+
+
+
+
+
+
+ window
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+ 1
+
+
+
+ 1
+
+
+ 1
+
+
+
+
+ window
+
+
+ 1
+
+
+ 1
+
+
+
+
+
+ 1
+
+
+ 1
+
+
+ window
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+
+
+ 1
+
+
+
+ aggregate
+
+
+
+ 1
+ 1
+
+
+ window
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ window
+
+
+ 1
+
+
+ 1
+
+
+ 1
+ 1
+
+
+ 1
+
+
+ window
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ aggregate
+
+
+ aggregate
+
+
+ 1
+
+
+ 1
+ 2026-06-05.07:13:27
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ R
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ R
+
+
+ R
+
+
+ R
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ R
+
+
+ R
+
+
+ R
+
+
+ R
+
+
+ R
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ R
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ R
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ R
+
+
+ R
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ R
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ R
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ R
+
+
+ R
+
+
+ R
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ R
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ R
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ R
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ R
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ R
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ 3
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ 2
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+ R
+
+
+ 1
+
+
+
+ 1
+ TEXT|0s
+
+
+ 2
+ TEXT|0s
+
+
+ 3
+ TEXT|0s
+
+
+ 4
+ INT|0s
+
+
+ 5
+ TEXT|0s
+
+
+
\ No newline at end of file
diff --git a/.idea/dataSources/315cb5c9-2b0f-435b-b602-59823b160908/storage_v2/_src_/schema/main.uQUzAA.meta b/.idea/dataSources/315cb5c9-2b0f-435b-b602-59823b160908/storage_v2/_src_/schema/main.uQUzAA.meta
new file mode 100644
index 0000000..8dab49c
--- /dev/null
+++ b/.idea/dataSources/315cb5c9-2b0f-435b-b602-59823b160908/storage_v2/_src_/schema/main.uQUzAA.meta
@@ -0,0 +1,2 @@
+#n:main
+! [0, 0, null, null, -2147483648, -2147483648]
diff --git a/db/.htaccess b/db/.htaccess
new file mode 100644
index 0000000..437a11a
--- /dev/null
+++ b/db/.htaccess
@@ -0,0 +1,3 @@
+
+ deny from all
+
\ No newline at end of file
diff --git a/php/model/ArticleManager.php b/php/model/ArticleManager.php
index 8962aa3..5ce70da 100644
--- a/php/model/ArticleManager.php
+++ b/php/model/ArticleManager.php
@@ -1,5 +1,6 @@
getArticle(1) == null ){
diff --git a/php/model/DatabaseArticleManager.php b/php/model/DatabaseArticleManager.php
new file mode 100644
index 0000000..1697071
--- /dev/null
+++ b/php/model/DatabaseArticleManager.php
@@ -0,0 +1,313 @@
+getConnection();
+
+ $db->exec("
+ CREATE TABLE articles (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ title TEXT,
+ content TEXT,
+ author TEXT,
+ category TEXT,
+ tags TEXT,
+ created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+ );");
+ unset($db);
+ } catch (PDOException $e) {
+ throw new InternalServerErrorException($e->getMessage());
+ }
+ }
+ }
+
+ /**
+ * Baut die Verbindung zur Datenbank auf.
+ * @throws InternalServerErrorException
+ */
+ private function getConnection()
+ {
+ try {
+ $user = 'root';
+ $pw = null;
+ $dsn = 'sqlite:' . __DIR__ . '/../../db/articles.db';
+ return new PDO($dsn, $user, $pw);
+ } catch (PDOException $e) {
+ throw new InternalServerErrorException($e->getMessage());
+ }
+ }
+
+ /**
+ * Gibt die DatabaseArticleManager-Instanz zurück.
+ * @return DatabaseArticleManager
+ */
+ public static function getInstance()
+ {
+ if (self::$instance == null) {
+ self::$instance = new DatabaseArticleManager();
+ }
+ return self::$instance;
+ }
+
+ public function addArticle($title, $content, $author, $category, $tags)
+ {
+ try {
+ $db = $this->getConnection();
+
+ $sql = "INSERT INTO articles (title, content, author, category, tags)
+ VALUES (:title, :content, :author, :category, :tags);";
+
+ $command = $db->prepare($sql);
+ if (!$command) {
+ throw new InternalServerErrorException("internal_error");
+ }
+
+ // Verknüpft die übergebenen Parameter exakt mit den SQL-Platzhaltern
+ $success = $command->execute([
+ ":title" => $title,
+ ":content" => $content,
+ ":author" => $author,
+ ":category" => $category,
+ ":tags" => $tags
+ ]);
+
+ if (!$success) {
+ throw new InternalServerErrorException("internal_error");
+ }
+
+ return intval($db->lastInsertId());
+
+ } catch (PDOException $e) {
+ throw new InternalServerErrorException($e->getMessage());
+ }
+ }
+
+ public function updateArticle($id, $article, $author)
+ {
+ if (empty($article)) {
+ throw new InternalServerErrorException("internal_error");
+ }
+
+ // Berechtigungsprüfung analog zur lokalen Implementierung:
+ if ($article->getAuthor() !== $author) {
+ throw new UnauthorizedAccessException("unauthorized_access");
+ }
+
+ try {
+ $db = $this->getConnection();
+
+ $sql = "UPDATE articles
+ SET title = :title, content = :content, author = :author, category = :category, tags = :tags
+ WHERE id = :id;";
+
+ $command = $db->prepare($sql);
+ if (!$command) {
+ throw new InternalServerErrorException("internal_error");
+ }
+
+ $success = $command->execute([
+ ":id" => $id,
+ ":title" => $article->getTitle(),
+ ":content" => $article->getContent(),
+ ":author" => $author,
+ ":category" => $article->getCategory(),
+ ":tags" => $article->getTags()
+ ]);
+
+ // rowCount() prüft, ob eine Zeile mit dieser ID existierte und geändert werden konnte
+ if (!$success || $command->rowCount() === 0) {
+ // Falls die ID nicht existiert, prüfen wir, ob sie überhaupt da ist
+ if (!$this->getArticle($id)) {
+ throw new NotFoundException("missing_id");
+ }
+ }
+ } catch (PDOException $e) {
+ throw new InternalServerErrorException("internal_error");
+ }
+ }
+
+ public function deleteArticle($id, $author)
+ {
+ $article = getArticle($id);
+ if (empty($article)) {
+ throw new NotFoundException("not_found_article");
+ }
+
+ // Berechtigungsprüfung:
+ if ($article->getAuthor() !== $author) {
+ throw new UnauthorizedAccessException("unauthorized_access");
+ }
+
+ try {
+ $db = $this->getConnection();
+ $sql = "DELETE FROM articles WHERE id = :id;";
+
+ $command = $db->prepare($sql);
+ if (!$command) {
+ throw new InternalServerErrorException("internal_error");
+ }
+
+ if (!$command->execute([":id" => $id])) {
+ throw new InternalServerErrorException("internal_error");
+ }
+ } catch (PDOException $exc) {
+ throw new InternalServerErrorException("internal_error");
+ }
+ }
+
+ public function getArticle($id)
+ {
+ try {
+ $db = $this->getConnection();
+ $sql = "SELECT * FROM articles WHERE id = :id;";
+
+ $command = $db->prepare($sql);
+ if (!$command) {
+ throw new InternalServerErrorException("internal_error");
+ }
+
+ $command->execute([":id" => $id]);
+ $row = $command->fetch(PDO::FETCH_ASSOC);
+
+ if ($row) {
+ return new Article(
+ intval($row['id']),
+ $row['title'],
+ $row['content'],
+ $row['author'],
+ $row['category'],
+ $row['tags'],
+ $row['created']
+ );
+ }
+
+ return null;
+ } catch (PDOException $e) {
+ throw new InternalServerErrorException("internal_error");
+ }
+ }
+
+ public function getAllArticles()
+ {
+ try {
+ $db = $this->getConnection();
+ $sql = "SELECT * FROM articles;";
+
+ $command = $db->query($sql);
+ if (!$command) {
+ throw new InternalServerErrorException("internal_error");
+ }
+
+ $rows = $command->fetchAll(PDO::FETCH_ASSOC);
+ $articles = [];
+
+ foreach ($rows as $row) {
+ $articles[] = new Article(
+ intval($row['id']),
+ $row['title'],
+ $row['content'],
+ $row['author'],
+ $row['category'],
+ $row['tags'],
+ $row['created']
+ );
+ }
+
+ return $articles;
+ } catch (PDOException $e) {
+ throw new InternalServerErrorException("internal_error");
+ }
+ }
+
+ public function getArticlesByAuthor($author)
+ {
+ try {
+ $db = $this->getConnection();
+ $sql = "SELECT * FROM articles WHERE author = :author;";
+
+ $command = $db->prepare($sql);
+ if (!$command) {
+ throw new InternalServerErrorException("internal_error");
+ }
+
+ $command->execute([":author" => $author]);
+ $rows = $command->fetchAll(PDO::FETCH_ASSOC);
+ $filteredArticles = [];
+
+ foreach ($rows as $row) {
+ $filteredArticles[] = new Article(
+ intval($row['id']),
+ $row['title'],
+ $row['content'],
+ $row['author'],
+ $row['category'],
+ $row['tags'],
+ $row['created']
+ );
+ }
+
+ return $filteredArticles;
+ } catch (PDOException $e) {
+ throw new InternalServerErrorException("internal_error");
+ }
+ }
+
+ public function getArticlesByCategory($category)
+ {
+ try {
+ $db = $this->getConnection();
+ $sql = "SELECT * FROM articles WHERE category = :category;";
+
+ $command = $db->prepare($sql);
+ if (!$command) {
+ throw new InternalServerErrorException("internal_error");
+ }
+
+ $command->execute([":category" => $category]);
+ $rows = $command->fetchAll(PDO::FETCH_ASSOC);
+ $filteredArticles = [];
+
+ foreach ($rows as $row) {
+ $filteredArticles[] = new Article(
+ intval($row['id']),
+ $row['title'],
+ $row['content'],
+ $row['author'],
+ $row['category'],
+ $row['tags'],
+ $row['created']
+ );
+ }
+
+ return $filteredArticles;
+ } catch (PDOException $exc) {
+ throw new InternalServerErrorException("internal_error");
+ }
+ }
+
+ public function search(string $keyword): array
+ {
+ // TODO: implement search()
+ return [];
+ }
+
+}
\ No newline at end of file
diff --git a/php/model/DatabaseUserManager.php b/php/model/DatabaseUserManager.php
new file mode 100644
index 0000000..cd27d99
--- /dev/null
+++ b/php/model/DatabaseUserManager.php
@@ -0,0 +1,26 @@
+