feat: add jakarta rest todo implementation with devcontainer setup

This commit is contained in:
Patryk Hegenberg 2025-03-23 00:35:03 +01:00
parent b75d5b2d11
commit d605c3bd7d
24 changed files with 1286 additions and 0 deletions

View file

@ -0,0 +1,8 @@
.git
.github
.settings
target
*.iml
.idea
.vscode
node_modules

View file

@ -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/

View file

@ -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

View file

@ -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

View file

@ -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"}
}
}

View file

@ -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

View file

@ -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');

View file

@ -0,0 +1,112 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.todoapp</groupId>
<artifactId>todo-app</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<jakartaee.version>10.0.0</jakartaee.version>
<hibernate.version>6.2.5.Final</hibernate.version>
<postgresql.version>42.6.0</postgresql.version>
</properties>
<dependencies>
<!-- Jakarta EE 10 API -->
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-api</artifactId>
<version>${jakartaee.version}</version>
<scope>provided</scope>
</dependency>
<!-- Hibernate Core -->
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate.version}</version>
</dependency>
<!-- PostgreSQL JDBC Driver -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>${postgresql.version}</version>
</dependency>
<!-- JSTL -->
<dependency>
<groupId>jakarta.servlet.jsp.jstl</groupId>
<artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>jakarta.servlet.jsp.jstl</artifactId>
<version>3.0.1</version>
</dependency>
<!-- JAX-RS -->
<dependency>
<groupId>jakarta.ws.rs</groupId>
<artifactId>jakarta.ws.rs-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- JSON-Binding -->
<dependency>
<groupId>jakarta.json.bind</groupId>
<artifactId>jakarta.json.bind-api</artifactId>
<version>3.0.0</version>
<scope>provided</scope>
</dependency>
<!-- JSON-Processing -->
<dependency>
<groupId>jakarta.json</groupId>
<artifactId>jakarta.json-api</artifactId>
<version>2.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>6.3.1.Final</version> <!-- Stelle sicher, dass du eine aktuelle Version benutzt -->
</dependency>
<!-- Mindestens zum Passwort-Hashing -->
<dependency>
<groupId>at.favre.lib</groupId>
<artifactId>bcrypt</artifactId>
<version>0.10.2</version>
</dependency>
</dependencies>
<build>
<finalName>todo-app</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.3.2</version>
</plugin>
<plugin>
<groupId>io.openliberty.tools</groupId>
<artifactId>liberty-maven-plugin</artifactId>
<version>3.7.1</version>
</plugin>
</plugins>
</build>
</project>

View file

@ -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<Todo> 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<Todo> getTodosByUsername(String username) {
try (Session session = sessionFactory.openSession()) {
Query<Todo> 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<Todo> getCompletedTodosByUsername(String username) {
try (Session session = sessionFactory.openSession()) {
Query<Todo> 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<Todo> getIncompleteTodosByUsername(String username) {
try (Session session = sessionFactory.openSession()) {
Query<Todo> 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);
}
}
}

View file

@ -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<User> 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<User> 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<Long> 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<Long> 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);
}
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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 +
'}';
}
}

View file

@ -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<Todo> 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<Todo> getTodos() {
return todos;
}
public void setTodos(List<Todo> 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 + '\'' +
'}';
}
}

View file

@ -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();
}
}

View file

@ -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
}

View file

@ -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<Todo> 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<Todo> todos = todoDAO.getCompletedTodosByUsername(username);
return Response.ok(todos).build();
}
@GET
@Path("/incomplete")
public Response getIncompleteTodos(@Context SecurityContext securityContext) {
String username = securityContext.getUserPrincipal().getName();
List<Todo> 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();
}
}

View file

@ -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());
}
}

View file

@ -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");
}
}

View file

@ -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();
}
}
}

View file

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<server description="OpenLiberty Todo App Server">
<featureManager>
<feature>servlet-6.0</feature>
<feature>pages-3.1</feature>
<feature>jdbc-4.2</feature>
<feature>jndi-1.0</feature>
<feature>monitor-1.0</feature>
<feature>restfulWS-3.1</feature>
<feature>jsonb-3.0</feature>
<feature>jsonp-2.1</feature>
<!-- <feature>appSecurity-5.0</feature> -->
<feature>transportSecurity-1.0</feature>
</featureManager>
<httpEndpoint id="defaultHttpEndpoint"
httpPort="9080"
httpsPort="9443" />
<applicationManager autoExpand="true" />
<dataSource id="PostgresDataSource" jndiName="jdbc/PostgresDataSource">
<jdbcDriver libraryRef="PostgresLib" />
<properties.postgresql serverName="${POSTGRES_HOST}"
portNumber="${POSTGRES_PORT}"
databaseName="${POSTGRES_DB}"
user="${POSTGRES_USER}"
password="${POSTGRES_PASSWORD}" />
</dataSource>
<library id="PostgresLib">
<fileset dir="${server.config.dir}/lib" includes="postgresql-*.jar" />
</library>
<webApplication location="todo-app.war" contextRoot="/todo-app" />
</server>

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="https://jakarta.ee/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence https://jakarta.ee/xml/ns/persistence/persistence_3_0.xsd"
version="3.0">
<persistence-unit name="todoPU" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<class>com.todoapp.model.User</class>
<class>com.todoapp.model.Todo</class>
<properties>
<property name="jakarta.persistence.jdbc.driver" value="org.postgresql.Driver"/>
<property name="jakarta.persistence.jdbc.url" value="jdbc:postgresql://postgresdb:5432/todo_db"/>
<property name="jakarta.persistence.jdbc.user" value="postgres"/>
<property name="jakarta.persistence.jdbc.password" value="postgres"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect"/>
<property name="hibernate.hbm2ddl.auto" value="update"/>
<property name="hibernate.show_sql" value="true"/>
</properties>
</persistence-unit>
</persistence>

View file

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- JNDI connection settings -->
<property name="hibernate.connection.datasource">jdbc/PostgresDataSource</property>
<!-- Select our SQL dialect -->
<property name="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</property>
<!-- Echo the SQL to stdout -->
<property name="hibernate.show_sql">true</property>
<!-- Set the current session context -->
<property name="hibernate.current_session_context_class">thread</property>
<!-- Drop and re-create the database schema on startup -->
<property name="hibernate.hbm2ddl.auto">update</property>
<!-- Entity mapping -->
<mapping class="com.todoapp.model.User"/>
<mapping class="com.todoapp.model.Todo"/>
</session-factory>
</hibernate-configuration>

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">
<display-name>Todo Application</display-name>
<session-config>
<session-timeout>30</session-timeout>
</session-config>
</web-app>