From d605c3bd7d885dd7e25a765aa20038944e9598cc Mon Sep 17 00:00:00 2001 From: Patryk Hegenberg Date: Sun, 23 Mar 2025 00:35:03 +0100 Subject: [PATCH] feat: add jakarta rest todo implementation with devcontainer setup --- .../.devcontainer/.dockerignore | 8 ++ .../.devcontainer/.gitignore | 27 ++++ .../.devcontainer/Dockerfile | 12 ++ .../.devcontainer/Dockerfile.dev | 14 ++ .../.devcontainer/devcontainer.json | 14 ++ .../.devcontainer/docker-compose.yml | 60 ++++++++ .../jakarta-rest-todo/.devcontainer/init.sql | 19 +++ cmd/jws/projects/jakarta-rest-todo/pom.xml | 112 +++++++++++++++ .../main/java/com/todoapp/dao/TodoDAO.java | 135 ++++++++++++++++++ .../main/java/com/todoapp/dao/UserDAO.java | 124 ++++++++++++++++ .../java/com/todoapp/dto/LoginRequest.java | 23 +++ .../com/todoapp/dto/RegistrationRequest.java | 32 +++++ .../src/main/java/com/todoapp/model/Todo.java | 109 ++++++++++++++ .../src/main/java/com/todoapp/model/User.java | 101 +++++++++++++ .../java/com/todoapp/rest/AuthResource.java | 95 ++++++++++++ .../com/todoapp/rest/TodoApplication.java | 9 ++ .../java/com/todoapp/rest/TodoResource.java | 130 +++++++++++++++++ .../security/BasicAuthenticationFilter.java | 104 ++++++++++++++ .../java/com/todoapp/security/CORSFilter.java | 20 +++ .../java/com/todoapp/util/HibernateUtil.java | 41 ++++++ .../src/main/liberty/config/server.xml | 39 +++++ .../main/resources/META-INF/persistence.xml | 20 +++ .../src/main/resources/hibernate.cfg.xml | 26 ++++ .../src/main/webapp/WEB-INF/web.xml | 12 ++ 24 files changed, 1286 insertions(+) create mode 100644 cmd/jws/projects/jakarta-rest-todo/.devcontainer/.dockerignore create mode 100644 cmd/jws/projects/jakarta-rest-todo/.devcontainer/.gitignore create mode 100644 cmd/jws/projects/jakarta-rest-todo/.devcontainer/Dockerfile create mode 100644 cmd/jws/projects/jakarta-rest-todo/.devcontainer/Dockerfile.dev create mode 100644 cmd/jws/projects/jakarta-rest-todo/.devcontainer/devcontainer.json create mode 100644 cmd/jws/projects/jakarta-rest-todo/.devcontainer/docker-compose.yml create mode 100644 cmd/jws/projects/jakarta-rest-todo/.devcontainer/init.sql create mode 100644 cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/dao/TodoDAO.java create mode 100644 cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/dao/UserDAO.java create mode 100644 cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/dto/LoginRequest.java create mode 100644 cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/dto/RegistrationRequest.java create mode 100644 cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/model/Todo.java create mode 100644 cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/model/User.java create mode 100644 cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/rest/AuthResource.java create mode 100644 cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/rest/TodoApplication.java create mode 100644 cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/rest/TodoResource.java create mode 100644 cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/security/BasicAuthenticationFilter.java create mode 100644 cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/security/CORSFilter.java create mode 100644 cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/util/HibernateUtil.java create mode 100644 cmd/jws/projects/jakarta-rest-todo/src/main/liberty/config/server.xml create mode 100644 cmd/jws/projects/jakarta-rest-todo/src/main/resources/META-INF/persistence.xml create mode 100644 cmd/jws/projects/jakarta-rest-todo/src/main/resources/hibernate.cfg.xml create mode 100644 cmd/jws/projects/jakarta-rest-todo/src/main/webapp/WEB-INF/web.xml diff --git a/cmd/jws/projects/jakarta-rest-todo/.devcontainer/.dockerignore b/cmd/jws/projects/jakarta-rest-todo/.devcontainer/.dockerignore new file mode 100644 index 0000000..017b280 --- /dev/null +++ b/cmd/jws/projects/jakarta-rest-todo/.devcontainer/.dockerignore @@ -0,0 +1,8 @@ +.git +.github +.settings +target +*.iml +.idea +.vscode +node_modules diff --git a/cmd/jws/projects/jakarta-rest-todo/.devcontainer/.gitignore b/cmd/jws/projects/jakarta-rest-todo/.devcontainer/.gitignore new file mode 100644 index 0000000..82b069a --- /dev/null +++ b/cmd/jws/projects/jakarta-rest-todo/.devcontainer/.gitignore @@ -0,0 +1,27 @@ +# Maven +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +# IDE-spezifische Dateien +.idea/ +*.iws +*.iml +*.ipr +.vscode/ +.settings/ +.classpath +.project +.factorypath + +# Temporäre Dateien +*.log +*.tmp +*.temp + +# Lokale Konfigurationsdateien +src/main/resources/application-local.properties + +# Devcontainer Volumen +.devcontainer/postgres-data/ diff --git a/cmd/jws/projects/jakarta-rest-todo/.devcontainer/Dockerfile b/cmd/jws/projects/jakarta-rest-todo/.devcontainer/Dockerfile new file mode 100644 index 0000000..e9d615d --- /dev/null +++ b/cmd/jws/projects/jakarta-rest-todo/.devcontainer/Dockerfile @@ -0,0 +1,12 @@ +FROM icr.io/appcafe/open-liberty:23.0.0.4-kernel-slim-java17-openj9-ubi + +COPY --chown=1001:0 src/main/liberty/config /config +# # Verzeichnis erstellen und PostgreSQL JDBC-Treiber herunterladen +RUN mkdir -p /config/lib && \ + curl -o /config/lib/postgresql.jar https://jdbc.postgresql.org/download/postgresql-42.6.0.jar + +RUN features.sh + +#COPY --chown=1001:0 target/*.war /config/apps + +RUN configure.sh diff --git a/cmd/jws/projects/jakarta-rest-todo/.devcontainer/Dockerfile.dev b/cmd/jws/projects/jakarta-rest-todo/.devcontainer/Dockerfile.dev new file mode 100644 index 0000000..412eb57 --- /dev/null +++ b/cmd/jws/projects/jakarta-rest-todo/.devcontainer/Dockerfile.dev @@ -0,0 +1,14 @@ +FROM mcr.microsoft.com/devcontainers/java:1-21-bullseye + +#ARG INSTALL_MAVEN="true" +#ARG MAVEN_VERSION="" + +#ARG INSTALL_GRADLE="false" +#ARG GRADLE_VERSION="" + +#RUN if [ "${INSTALL_MAVEN}" = "true" ]; then su vscode -c "umask 0002 && . /usr/local/sdkman/bin/sdkman-init.sh && sdk install maven \"${MAVEN_VERSION}\""; fi \ +# && if [ "${INSTALL_GRADLE}" = "true" ]; then su vscode -c "umask 0002 && . /usr/local/sdkman/bin/sdkman-init.sh && sdk install gradle \"${GRADLE_VERSION}\""; fi + +# Hier kannst du zusätzliche Tools oder Bibliotheken installieren, die du für die Entwicklung benötigst. +# Zum Beispiel den PostgreSQL JDBC-Treiber, wenn du ihn für die Entwicklung benötigst: +RUN apt-get update && apt-get install -y curl && curl -o /tmp/postgresql.jar https://jdbc.postgresql.org/download/postgresql-42.6.0.jar && mkdir -p /usr/local/lib/ && mv /tmp/postgresql.jar /usr/local/lib/postgresql.jar diff --git a/cmd/jws/projects/jakarta-rest-todo/.devcontainer/devcontainer.json b/cmd/jws/projects/jakarta-rest-todo/.devcontainer/devcontainer.json new file mode 100644 index 0000000..a1901d5 --- /dev/null +++ b/cmd/jws/projects/jakarta-rest-todo/.devcontainer/devcontainer.json @@ -0,0 +1,14 @@ +{ + "name": "Java & PostgreSQL", + "dockerComposeFile": "docker-compose.yml", + "service": "app", + "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", + "forwardPorts": [9080, 5432, 9433], + "postCreateCommand": "mvn clean install -DskipTests", + "remoteUser": "vscode", + "features": { + "ghcr.io/devcontainers/features/docker-in-docker:2": {"version": "latest"}, + "ghcr.io/devcontainers-extra/features/maven-sdkman:2": {"version": "latest"}, + "ghcr.io/devcontainers/features/git:1": {"version": "latest"} +} +} diff --git a/cmd/jws/projects/jakarta-rest-todo/.devcontainer/docker-compose.yml b/cmd/jws/projects/jakarta-rest-todo/.devcontainer/docker-compose.yml new file mode 100644 index 0000000..3141d45 --- /dev/null +++ b/cmd/jws/projects/jakarta-rest-todo/.devcontainer/docker-compose.yml @@ -0,0 +1,60 @@ +services: + app: + container_name: javadev + build: + context: ../ + dockerfile: .devcontainer/Dockerfile.dev + volumes: + - ../..:/workspaces:cached + - ./app:/app + environment: + POSTGRES_HOST: postgresdb + POSTGRES_PORT: 5432 + POSTGRES_DB: postgres + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + networks: + - my_network + command: sleep infinity + + openliberty: + container_name: openliberty_app + build: + context: ../ + dockerfile: .devcontainer/Dockerfile + ports: + - "9080:9080" + - "9443:9443" + environment: + POSTGRES_HOST: postgresdb + POSTGRES_PORT: 5432 + POSTGRES_DB: todo_db + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + volumes: + - ./app:/config/dropins + depends_on: + - db + networks: + - my_network + + db: + container_name: postgresdb + image: postgres:latest + restart: unless-stopped + volumes: + - postgres-data:/var/lib/postgresql/data + - ./init.sql:/docker-entrypoint-initdb.d/init.sql + environment: + POSTGRES_PASSWORD: postgres + POSTGRES_USER: postgres + POSTGRES_DB: todo_db + networks: + - my_network + +volumes: + postgres-data: + +networks: + my_network: + driver: bridge diff --git a/cmd/jws/projects/jakarta-rest-todo/.devcontainer/init.sql b/cmd/jws/projects/jakarta-rest-todo/.devcontainer/init.sql new file mode 100644 index 0000000..818eadb --- /dev/null +++ b/cmd/jws/projects/jakarta-rest-todo/.devcontainer/init.sql @@ -0,0 +1,19 @@ +CREATE TABLE users ( + id SERIAL PRIMARY KEY, + username VARCHAR(255) NOT NULL UNIQUE, + password VARCHAR(255) NOT NULL, + email VARCHAR(255) NOT NULL +); + +CREATE TABLE todos ( + id SERIAL PRIMARY KEY, + title VARCHAR(255) NOT NULL, + description VARCHAR(1000), + target_date DATE, + completed BOOLEAN, + user_id BIGINT NOT NULL, + FOREIGN KEY (user_id) REFERENCES users(id) +); + +INSERT INTO users (username, password, email) VALUES +('test', '$2a$12$nt7xQKNDKZhxQFZGD5Wy0.Uh0wdPtWDgwfnWnPLgBWnQDGGkNLKBi', 'test@example.com'); diff --git a/cmd/jws/projects/jakarta-rest-todo/pom.xml b/cmd/jws/projects/jakarta-rest-todo/pom.xml index e69de29..e0eee0f 100644 --- a/cmd/jws/projects/jakarta-rest-todo/pom.xml +++ b/cmd/jws/projects/jakarta-rest-todo/pom.xml @@ -0,0 +1,112 @@ + + + 4.0.0 + + com.todoapp + todo-app + 1.0-SNAPSHOT + war + + + UTF-8 + 17 + 17 + 10.0.0 + 6.2.5.Final + 42.6.0 + + + + + + jakarta.platform + jakarta.jakartaee-api + ${jakartaee.version} + provided + + + + + org.hibernate.orm + hibernate-core + ${hibernate.version} + + + + + org.postgresql + postgresql + ${postgresql.version} + + + + + jakarta.servlet.jsp.jstl + jakarta.servlet.jsp.jstl-api + 3.0.0 + + + org.glassfish.web + jakarta.servlet.jsp.jstl + 3.0.1 + + + + + jakarta.ws.rs + jakarta.ws.rs-api + 3.1.0 + provided + + + + + jakarta.json.bind + jakarta.json.bind-api + 3.0.0 + provided + + + + + jakarta.json + jakarta.json-api + 2.1.0 + provided + + + org.hibernate + hibernate-core + 6.3.1.Final + + + + at.favre.lib + bcrypt + 0.10.2 + + + + + todo-app + + + org.apache.maven.plugins + maven-compiler-plugin + 3.10.1 + + + org.apache.maven.plugins + maven-war-plugin + 3.3.2 + + + io.openliberty.tools + liberty-maven-plugin + 3.7.1 + + + + diff --git a/cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/dao/TodoDAO.java b/cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/dao/TodoDAO.java new file mode 100644 index 0000000..45d5557 --- /dev/null +++ b/cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/dao/TodoDAO.java @@ -0,0 +1,135 @@ +package com.todoapp.dao; + +import com.todoapp.model.Todo; +import com.todoapp.model.User; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.hibernate.Transaction; +import org.hibernate.query.Query; +import java.util.List; + +@ApplicationScoped +public class TodoDAO { + + @Inject + private SessionFactory sessionFactory; + + @Inject + private UserDAO userDAO; + + public Todo saveTodo(Todo todo, String username) { + Transaction transaction = null; + try (Session session = sessionFactory.openSession()) { + transaction = session.beginTransaction(); + + // Benutzer aus der Datenbank holen + User user = userDAO.getUserByUsername(username); + if (user == null) { + throw new IllegalArgumentException("User not found"); + } + + todo.setUser(user); + session.persist(todo); + transaction.commit(); + return todo; + } catch (Exception e) { + if (transaction != null) { + transaction.rollback(); + } + e.printStackTrace(); + throw new RuntimeException("Error saving todo", e); + } + } + + public Todo updateTodo(Todo todo) { + Transaction transaction = null; + try (Session session = sessionFactory.openSession()) { + transaction = session.beginTransaction(); + Todo updatedTodo = session.merge(todo); + transaction.commit(); + return updatedTodo; + } catch (Exception e) { + if (transaction != null) { + transaction.rollback(); + } + e.printStackTrace(); + throw new RuntimeException("Error updating todo", e); + } + } + + public boolean deleteTodo(Long id) { + Transaction transaction = null; + try (Session session = sessionFactory.openSession()) { + transaction = session.beginTransaction(); + Todo todo = session.get(Todo.class, id); + if (todo != null) { + session.remove(todo); + transaction.commit(); + return true; + } + return false; + } catch (Exception e) { + if (transaction != null) { + transaction.rollback(); + } + e.printStackTrace(); + throw new RuntimeException("Error deleting todo", e); + } + } + + public Todo getTodoById(Long id) { + try (Session session = sessionFactory.openSession()) { + return session.get(Todo.class, id); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("Error getting todo by ID", e); + } + } + + public List getAllTodos() { + try (Session session = sessionFactory.openSession()) { + return session.createQuery("FROM Todo", Todo.class).list(); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("Error getting all todos", e); + } + } + + public List getTodosByUsername(String username) { + try (Session session = sessionFactory.openSession()) { + Query query = session.createQuery( + "FROM Todo t WHERE t.user.username = :username ORDER BY t.completed, t.targetDate", Todo.class); + query.setParameter("username", username); + return query.list(); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("Error getting todos by username", e); + } + } + + public List getCompletedTodosByUsername(String username) { + try (Session session = sessionFactory.openSession()) { + Query query = session.createQuery( + "FROM Todo t WHERE t.user.username = :username AND t.completed = true", Todo.class); + query.setParameter("username", username); + return query.list(); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("Error getting completed todos", e); + } + } + + public List getIncompleteTodosByUsername(String username) { + try (Session session = sessionFactory.openSession()) { + Query query = session.createQuery( + "FROM Todo t WHERE t.user.username = :username AND t.completed = false ORDER BY t.targetDate", Todo.class); + query.setParameter("username", username); + return query.list(); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("Error getting incomplete todos", e); + } + } +} diff --git a/cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/dao/UserDAO.java b/cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/dao/UserDAO.java new file mode 100644 index 0000000..3893bb7 --- /dev/null +++ b/cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/dao/UserDAO.java @@ -0,0 +1,124 @@ +package com.todoapp.dao; + +import com.todoapp.model.User; +import com.todoapp.util.HibernateUtil; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.hibernate.Transaction; +import org.hibernate.query.Query; +import java.util.List; +import java.util.Optional; + +@ApplicationScoped +public class UserDAO { + + @Inject + private SessionFactory sessionFactory; + + public User saveUser(User user) { + Transaction transaction = null; + try (Session session = sessionFactory.openSession()) { + transaction = session.beginTransaction(); + session.persist(user); + transaction.commit(); + return user; + } catch (Exception e) { + if (transaction != null) { + transaction.rollback(); + } + e.printStackTrace(); + throw new RuntimeException("Error saving user", e); + } + } + + public User updateUser(User user) { + Transaction transaction = null; + try (Session session = sessionFactory.openSession()) { + transaction = session.beginTransaction(); + User updatedUser = session.merge(user); + transaction.commit(); + return updatedUser; + } catch (Exception e) { + if (transaction != null) { + transaction.rollback(); + } + e.printStackTrace(); + throw new RuntimeException("Error updating user", e); + } + } + + public boolean deleteUser(Long id) { + Transaction transaction = null; + try (Session session = sessionFactory.openSession()) { + transaction = session.beginTransaction(); + User user = session.get(User.class, id); + if (user != null) { + session.remove(user); + transaction.commit(); + return true; + } + return false; + } catch (Exception e) { + if (transaction != null) { + transaction.rollback(); + } + e.printStackTrace(); + throw new RuntimeException("Error deleting user", e); + } + } + + public User getUserById(Long id) { + try (Session session = sessionFactory.openSession()) { + return session.get(User.class, id); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("Error getting user by ID", e); + } + } + + public User getUserByUsername(String username) { + try (Session session = sessionFactory.openSession()) { + Query query = session.createQuery("FROM User WHERE username = :username", User.class); + query.setParameter("username", username); + return query.uniqueResult(); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("Error getting user by username", e); + } + } + + public List getAllUsers() { + try (Session session = sessionFactory.openSession()) { + return session.createQuery("FROM User", User.class).list(); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("Error getting all users", e); + } + } + + public boolean usernameExists(String username) { + try (Session session = sessionFactory.openSession()) { + Query query = session.createQuery( + "SELECT COUNT(u) FROM User u WHERE u.username = :username", Long.class); + query.setParameter("username", username); + return query.uniqueResult() > 0; + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("Error checking if username exists", e); + } + } + + public boolean emailExists(String email) { + try (Session session = sessionFactory.openSession()) { + Query query = session.createQuery( + "SELECT COUNT(u) FROM User u WHERE u.email = :email", Long.class); + query.setParameter("email", email); + return query.uniqueResult() > 0; + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("Error checking if email exists", e); + } + } +} diff --git a/cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/dto/LoginRequest.java b/cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/dto/LoginRequest.java new file mode 100644 index 0000000..5608a6e --- /dev/null +++ b/cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/dto/LoginRequest.java @@ -0,0 +1,23 @@ +package com.todoapp.dto; + +public class LoginRequest { + private String username; + private String password; + + // Getter und Setter + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } +} diff --git a/cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/dto/RegistrationRequest.java b/cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/dto/RegistrationRequest.java new file mode 100644 index 0000000..04561ec --- /dev/null +++ b/cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/dto/RegistrationRequest.java @@ -0,0 +1,32 @@ +package com.todoapp.dto; + +public class RegistrationRequest { + private String username; + private String password; + private String email; + + // Getter und Setter + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } +} diff --git a/cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/model/Todo.java b/cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/model/Todo.java new file mode 100644 index 0000000..18560e4 --- /dev/null +++ b/cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/model/Todo.java @@ -0,0 +1,109 @@ +package com.todoapp.model; + +import jakarta.persistence.*; +import jakarta.json.bind.annotation.JsonbTransient; +import java.io.Serializable; +import java.util.Date; + +@Entity +@Table(name = "todos") +public class Todo implements Serializable { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String title; + + @Column(length = 1000) + private String description; + + @Column(name = "target_date") + @Temporal(TemporalType.DATE) + private Date targetDate; + + @Column + private boolean completed; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + @JsonbTransient // Verhindert zirkuläre Referenzen + private User user; + + // Konstruktoren + public Todo() { + } + + public Todo(String title, String description, Date targetDate, boolean completed) { + this.title = title; + this.description = description; + this.targetDate = targetDate; + this.completed = completed; + } + + // Getter und Setter + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Date getTargetDate() { + return targetDate; + } + + public void setTargetDate(Date targetDate) { + this.targetDate = targetDate; + } + + public boolean isCompleted() { + return completed; + } + + public void setCompleted(boolean completed) { + this.completed = completed; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + // Zusätzliche Methode für REST-Antworten + public Long getUserId() { + return user != null ? user.getId() : null; + } + + @Override + public String toString() { + return "Todo{" + + "id=" + id + + ", title='" + title + '\'' + + ", description='" + description + '\'' + + ", targetDate=" + targetDate + + ", completed=" + completed + + '}'; + } +} diff --git a/cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/model/User.java b/cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/model/User.java new file mode 100644 index 0000000..e530617 --- /dev/null +++ b/cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/model/User.java @@ -0,0 +1,101 @@ +package com.todoapp.model; + +import jakarta.persistence.*; +import jakarta.json.bind.annotation.JsonbTransient; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +@Entity +@Table(name = "users") +public class User implements Serializable { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, unique = true) + private String username; + + @Column(nullable = false) + @JsonbTransient // Verhindert, dass das Passwort in JSON serialisiert wird + private String password; + + @Column(nullable = false) + private String email; + + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) + @JsonbTransient // Verhindert zirkuläre Referenzen bei der JSON-Serialisierung + private List todos = new ArrayList<>(); + + // Konstruktoren + public User() { + } + + public User(String username, String password, String email) { + this.username = username; + this.password = password; + this.email = email; + } + + // Getter und Setter + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public List getTodos() { + return todos; + } + + public void setTodos(List todos) { + this.todos = todos; + } + + // Hilfsmethoden + public void addTodo(Todo todo) { + todos.add(todo); + todo.setUser(this); + } + + public void removeTodo(Todo todo) { + todos.remove(todo); + todo.setUser(null); + } + + @Override + public String toString() { + return "User{" + + "id=" + id + + ", username='" + username + '\'' + + ", email='" + email + '\'' + + '}'; + } +} diff --git a/cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/rest/AuthResource.java b/cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/rest/AuthResource.java new file mode 100644 index 0000000..1e8a901 --- /dev/null +++ b/cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/rest/AuthResource.java @@ -0,0 +1,95 @@ +package com.todoapp.rest; + +import com.todoapp.dao.UserDAO; +import com.todoapp.model.User; +import com.todoapp.dto.LoginRequest; +import com.todoapp.dto.RegistrationRequest; + +import jakarta.annotation.security.RolesAllowed; +import jakarta.inject.Inject; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import at.favre.lib.crypto.bcrypt.BCrypt; +import java.util.Map; + +@Path("/auth") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class AuthResource { + + @Inject + private UserDAO userDAO; + + @POST + @Path("/register") + public Response register(RegistrationRequest request) { + if (request.getUsername() == null || request.getPassword() == null || request.getEmail() == null) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(Map.of("error", "Username, password, and email are required")) + .build(); + } + + if (userDAO.usernameExists(request.getUsername())) { + System.out.println(userDAO.usernameExists(request.getUsername())); + return Response.status(Response.Status.CONFLICT) + .entity(Map.of("error", "Username already exists")) + .build(); + } + + if (userDAO.emailExists(request.getEmail())) { + return Response.status(Response.Status.CONFLICT) + .entity(Map.of("error", "Email already exists")) + .build(); + } + + User user = new User(); + user.setUsername(request.getUsername()); + user.setEmail(request.getEmail()); + + String hashedPassword = BCrypt.withDefaults() + .hashToString(12, request.getPassword().toCharArray()); + user.setPassword(hashedPassword); + + userDAO.saveUser(user); + + return Response.status(Response.Status.CREATED) + .entity(Map.of("message", "User registered successfully")) + .build(); + } + + @POST + @RolesAllowed("user") + @Path("/login") + public Response login(LoginRequest request) { + // Validierung + if (request.getUsername() == null || request.getPassword() == null) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(Map.of("error", "Username and password are required")) + .build(); + } + + // Benutzer suchen + User user = userDAO.getUserByUsername(request.getUsername()); + if (user == null) { + return Response.status(Response.Status.UNAUTHORIZED) + .entity(Map.of("error", "Invalid credentials")) + .build(); + } + + // Passwort überprüfen + BCrypt.Result result = BCrypt.verifyer() + .verify(request.getPassword().toCharArray(), user.getPassword()); + if (!result.verified) { + return Response.status(Response.Status.UNAUTHORIZED) + .entity(Map.of("error", "Invalid credentials")) + .build(); + } + + // Erfolgreiche Anmeldung + return Response.ok(Map.of( + "message", "Login successful", + "username", user.getUsername(), + "email", user.getEmail())).build(); + } +} diff --git a/cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/rest/TodoApplication.java b/cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/rest/TodoApplication.java new file mode 100644 index 0000000..6ab8ce0 --- /dev/null +++ b/cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/rest/TodoApplication.java @@ -0,0 +1,9 @@ +package com.todoapp.rest; + +import jakarta.ws.rs.ApplicationPath; +import jakarta.ws.rs.core.Application; + +@ApplicationPath("/api") +public class TodoApplication extends Application { + // Die leere Klasse ist ausreichend, um den REST-Endpunkt zu definieren +} diff --git a/cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/rest/TodoResource.java b/cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/rest/TodoResource.java new file mode 100644 index 0000000..9e0575f --- /dev/null +++ b/cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/rest/TodoResource.java @@ -0,0 +1,130 @@ +package com.todoapp.rest; + +import com.todoapp.dao.TodoDAO; +import com.todoapp.model.Todo; +import jakarta.inject.Inject; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.SecurityContext; +import java.util.List; + +@Path("/todos") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class TodoResource { + + @Inject + private TodoDAO todoDAO; + + @GET + public Response getAllTodos(@Context SecurityContext securityContext) { + String username = securityContext.getUserPrincipal().getName(); + List todos = todoDAO.getTodosByUsername(username); + return Response.ok(todos).build(); + } + + @GET + @Path("/{id}") + public Response getTodoById(@PathParam("id") Long id, @Context SecurityContext securityContext) { + String username = securityContext.getUserPrincipal().getName(); + Todo todo = todoDAO.getTodoById(id); + + if (todo == null) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + + // Sicherheitscheck: Nutzer darf nur eigene Todos sehen + if (!todo.getUser().getUsername().equals(username)) { + return Response.status(Response.Status.FORBIDDEN).build(); + } + + return Response.ok(todo).build(); + } + + @GET + @Path("/completed") + public Response getCompletedTodos(@Context SecurityContext securityContext) { + String username = securityContext.getUserPrincipal().getName(); + List todos = todoDAO.getCompletedTodosByUsername(username); + return Response.ok(todos).build(); + } + + @GET + @Path("/incomplete") + public Response getIncompleteTodos(@Context SecurityContext securityContext) { + String username = securityContext.getUserPrincipal().getName(); + List todos = todoDAO.getIncompleteTodosByUsername(username); + return Response.ok(todos).build(); + } + + @POST + public Response createTodo(Todo todo, @Context SecurityContext securityContext) { + String username = securityContext.getUserPrincipal().getName(); + todo = todoDAO.saveTodo(todo, username); + return Response.status(Response.Status.CREATED).entity(todo).build(); + } + + @PUT + @Path("/{id}") + public Response updateTodo(@PathParam("id") Long id, Todo updatedTodo, @Context SecurityContext securityContext) { + String username = securityContext.getUserPrincipal().getName(); + Todo existingTodo = todoDAO.getTodoById(id); + + if (existingTodo == null) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + + // Sicherheitscheck: Nutzer darf nur eigene Todos aktualisieren + if (!existingTodo.getUser().getUsername().equals(username)) { + return Response.status(Response.Status.FORBIDDEN).build(); + } + + // ID und Benutzer beibehalten + updatedTodo.setId(id); + updatedTodo.setUser(existingTodo.getUser()); + + todoDAO.updateTodo(updatedTodo); + return Response.ok(updatedTodo).build(); + } + + @DELETE + @Path("/{id}") + public Response deleteTodo(@PathParam("id") Long id, @Context SecurityContext securityContext) { + String username = securityContext.getUserPrincipal().getName(); + Todo todo = todoDAO.getTodoById(id); + + if (todo == null) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + + // Sicherheitscheck: Nutzer darf nur eigene Todos löschen + if (!todo.getUser().getUsername().equals(username)) { + return Response.status(Response.Status.FORBIDDEN).build(); + } + + boolean deleted = todoDAO.deleteTodo(id); + return deleted ? Response.noContent().build() : Response.status(Response.Status.NOT_FOUND).build(); + } + + @PUT + @Path("/{id}/complete") + public Response markTodoComplete(@PathParam("id") Long id, @Context SecurityContext securityContext) { + String username = securityContext.getUserPrincipal().getName(); + Todo todo = todoDAO.getTodoById(id); + + if (todo == null) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + + // Sicherheitscheck: Nutzer darf nur eigene Todos aktualisieren + if (!todo.getUser().getUsername().equals(username)) { + return Response.status(Response.Status.FORBIDDEN).build(); + } + + todo.setCompleted(true); + todoDAO.updateTodo(todo); + return Response.ok(todo).build(); + } +} diff --git a/cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/security/BasicAuthenticationFilter.java b/cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/security/BasicAuthenticationFilter.java new file mode 100644 index 0000000..6d4f104 --- /dev/null +++ b/cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/security/BasicAuthenticationFilter.java @@ -0,0 +1,104 @@ +package com.todoapp.security; + +import com.todoapp.dao.UserDAO; +import com.todoapp.model.User; +import jakarta.annotation.Priority; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.Priorities; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerRequestFilter; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.SecurityContext; +import jakarta.ws.rs.ext.Provider; +import java.io.IOException; +import java.security.Principal; +import java.util.Base64; +import at.favre.lib.crypto.bcrypt.BCrypt; + +@Provider +@Priority(Priorities.AUTHENTICATION) +@ApplicationScoped +public class BasicAuthenticationFilter implements ContainerRequestFilter { + + @Inject + private UserDAO userDAO; + + @Override + public void filter(ContainerRequestContext requestContext) throws IOException { + System.out.println("BasicAuthenticationFilter triggered for path: " + requestContext.getUriInfo().getPath()); + String path = requestContext.getUriInfo().getPath(); + if (path.endsWith("auth/register") || path.endsWith("auth/login")) { + return; + } + + String authHeader = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION); + if (authHeader == null || !authHeader.startsWith("Basic ")) { + abortWithUnauthorized(requestContext); + return; + } + + String[] credentials = extractCredentials(authHeader); + if (credentials.length != 2) { + abortWithUnauthorized(requestContext); + return; + } + + String username = credentials[0]; + String password = credentials[1]; + + User user = userDAO.getUserByUsername(username); + if (user == null) { + abortWithUnauthorized(requestContext); + return; + } + + BCrypt.Result result = BCrypt.verifyer() + .verify(password.toCharArray(), user.getPassword()); + if (!result.verified) { + abortWithUnauthorized(requestContext); + return; + } + + final SecurityContext currentSecurityContext = requestContext.getSecurityContext(); + requestContext.setSecurityContext(new SecurityContext() { + @Override + public Principal getUserPrincipal() { + return () -> username; + } + + @Override + public boolean isUserInRole(String role) { + return true; + } + + @Override + public boolean isSecure() { + return currentSecurityContext.isSecure(); + } + + @Override + public String getAuthenticationScheme() { + return "BASIC"; + } + }); + System.out.println("SecurityContext gesetzt für User: " + username); + System.out.println("User in Rolle user: " + requestContext.getSecurityContext().isUserInRole("user")); + + } + + private String[] extractCredentials(String authHeader) { + String base64Credentials = authHeader.substring("Basic ".length()).trim(); + byte[] decoded = Base64.getDecoder().decode(base64Credentials); + String credentials = new String(decoded); + return credentials.split(":", 2); + } + + private void abortWithUnauthorized(ContainerRequestContext requestContext) { + requestContext.abortWith(Response + .status(Response.Status.UNAUTHORIZED) + .header(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"TodoApp\"") + .build()); + } +} diff --git a/cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/security/CORSFilter.java b/cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/security/CORSFilter.java new file mode 100644 index 0000000..b08586a --- /dev/null +++ b/cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/security/CORSFilter.java @@ -0,0 +1,20 @@ +package com.todoapp.security; + +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerResponseContext; +import jakarta.ws.rs.container.ContainerResponseFilter; +import jakarta.ws.rs.ext.Provider; +import java.io.IOException; + +@Provider +public class CORSFilter implements ContainerResponseFilter { + + @Override + public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) + throws IOException { + responseContext.getHeaders().add("Access-Control-Allow-Origin", "*"); + responseContext.getHeaders().add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); + responseContext.getHeaders().add("Access-Control-Allow-Headers", "Content-Type, Authorization"); + responseContext.getHeaders().add("Access-Control-Max-Age", "86400"); + } +} diff --git a/cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/util/HibernateUtil.java b/cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/util/HibernateUtil.java new file mode 100644 index 0000000..2ddaaf2 --- /dev/null +++ b/cmd/jws/projects/jakarta-rest-todo/src/main/java/com/todoapp/util/HibernateUtil.java @@ -0,0 +1,41 @@ +package com.todoapp.util; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; +import org.hibernate.SessionFactory; +import org.hibernate.cfg.Configuration; + +@ApplicationScoped +public class HibernateUtil { + + private static SessionFactory sessionFactory; + + @Produces + @ApplicationScoped + public SessionFactory getSessionFactory() { + if (sessionFactory == null) { + try { + Configuration configuration = new Configuration(); + configuration.setProperty("hibernate.connection.driver_class", "org.postgresql.Driver"); + configuration.setProperty("hibernate.connection.url", "jdbc:postgresql://postgresdb:5432/todo_db"); + configuration.setProperty("hibernate.connection.username", "postgres"); + configuration.setProperty("hibernate.connection.password", "postgres"); + + // Entity-Klassen registrieren + configuration.addAnnotatedClass(com.todoapp.model.User.class); + configuration.addAnnotatedClass(com.todoapp.model.Todo.class); + + sessionFactory = configuration.buildSessionFactory(); + } catch (Exception e) { + e.printStackTrace(); + } + } + return sessionFactory; + } + + public static void shutdown() { + if (sessionFactory != null) { + sessionFactory.close(); + } + } +} diff --git a/cmd/jws/projects/jakarta-rest-todo/src/main/liberty/config/server.xml b/cmd/jws/projects/jakarta-rest-todo/src/main/liberty/config/server.xml new file mode 100644 index 0000000..f281c16 --- /dev/null +++ b/cmd/jws/projects/jakarta-rest-todo/src/main/liberty/config/server.xml @@ -0,0 +1,39 @@ + + + + + servlet-6.0 + pages-3.1 + jdbc-4.2 + jndi-1.0 + monitor-1.0 + restfulWS-3.1 + jsonb-3.0 + jsonp-2.1 + + transportSecurity-1.0 + + + + + + + + + + + + + + + + + + + diff --git a/cmd/jws/projects/jakarta-rest-todo/src/main/resources/META-INF/persistence.xml b/cmd/jws/projects/jakarta-rest-todo/src/main/resources/META-INF/persistence.xml new file mode 100644 index 0000000..1e6b316 --- /dev/null +++ b/cmd/jws/projects/jakarta-rest-todo/src/main/resources/META-INF/persistence.xml @@ -0,0 +1,20 @@ + + + + org.hibernate.jpa.HibernatePersistenceProvider + com.todoapp.model.User + com.todoapp.model.Todo + + + + + + + + + + + diff --git a/cmd/jws/projects/jakarta-rest-todo/src/main/resources/hibernate.cfg.xml b/cmd/jws/projects/jakarta-rest-todo/src/main/resources/hibernate.cfg.xml new file mode 100644 index 0000000..33bdd20 --- /dev/null +++ b/cmd/jws/projects/jakarta-rest-todo/src/main/resources/hibernate.cfg.xml @@ -0,0 +1,26 @@ + + + + + + jdbc/PostgresDataSource + + + org.hibernate.dialect.PostgreSQLDialect + + + true + + + thread + + + update + + + + + + diff --git a/cmd/jws/projects/jakarta-rest-todo/src/main/webapp/WEB-INF/web.xml b/cmd/jws/projects/jakarta-rest-todo/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..80ab99b --- /dev/null +++ b/cmd/jws/projects/jakarta-rest-todo/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,12 @@ + + + + Todo Application + + + 30 + +