feat: add jakarta-todo-jsp implementation

This commit is contained in:
Patryk Hegenberg 2025-03-20 17:53:54 +01:00
parent 1737be597c
commit 3072446df0
26 changed files with 1830 additions and 0 deletions

6
FyneApp.toml Normal file
View file

@ -0,0 +1,6 @@
[Default]
Icon = "Icon.png"
Name = "JavaWebStarter"
ID = "com.hegenberg.app"
Version = "0.0.1"
Build = 1

BIN
Icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -0,0 +1,62 @@
# # Dockerfile für OpenLiberty
# FROM icr.io/appcafe/open-liberty:23.0.0.4-kernel-slim-java17-openj9-ubi
# COPY --chown=1001:0 /src/main/liberty/config /config
#
# # PostgreSQL JDBC-Treiber laden
# # RUN mkdir -p ./lib && \
# # curl -o ./lib/postgresql.jar https://jdbc.postgresql.org/download/postgresql-42.6.0.jar
#
# RUN 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
#
# # # Arbeitsverzeichnis setzen
# # WORKDIR /opt/ol/wlp/usr/servers/defaultServer
# #
# # # Notwendige Dateien kopieren
# # COPY target/todo-app.war ./apps/
# # # COPY server.xml ./
# #
# # # PostgreSQL JDBC-Treiber laden
# # RUN mkdir -p ./lib && \
# # curl -o ./lib/postgresql.jar https://jdbc.postgresql.org/download/postgresql-42.6.0.jar
# #
# # # OpenLiberty starten
# # CMD ["/opt/ol/wlp/bin/server", "run", "defaultServer"]
# FROM icr.io/appcafe/open-liberty:23.0.0.4-kernel-slim-java17-openj9-ubi
#
# # COPY --chown=1001:0 /src/main/liberty/config /config
# #
# # COPY --chown=1001:0 /src/main/liberty/config/server.xml /opt/ol/wlp/usr/servers/defaultServer/
#
# RUN chmod 644 /opt/ol/wlp/usr/servers/defaultServer/server.xml
#
#
# # 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
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,50 @@
version: "3.8"
services:
postgres:
image: postgres:15
networks:
- my_network
container_name: postgres_db
restart: always
environment:
POSTGRES_DB: todo_db
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 10
openliberty:
build: .
container_name: openliberty_app
networks:
- my_network
restart: always
ports:
- "9080:9080"
- "9443:9443"
depends_on:
- postgres
environment:
- POSTGRES_HOST=postgres
- POSTGRES_PORT=5432
- POSTGRES_DB=todo_db
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
volumes:
- ./src/main/liberty/config/server.xml:/config/server.xml
# - ./src/main/liberty/config/server.xml:/opt/ol/wlp/usr/servers/defaultServer/server.xml
volumes:
postgres_data:
networks:
my_network:
driver: bridge

View file

@ -0,0 +1,17 @@
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)
);

View file

@ -0,0 +1,89 @@
<?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>
<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,33 @@
<?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>
</featureManager>
<httpEndpoint id="defaultHttpEndpoint"
httpPort="9080"
httpsPort="9443" />
<applicationManager autoExpand="true" />
<dataSource id="PostgresDataSource">
<jdbcDriver libraryRef="PostgresLib" />
<properties.postgresql serverName="postgres"
portNumber="5432"
databaseName="todo_db"
user="postgres"
password="postgres" />
</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,111 @@
package com.todoapp.dao;
import com.todoapp.model.Todo;
import com.todoapp.util.HibernateUtil;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.query.Query;
import java.util.List;
public class TodoDAO {
public void saveTodo(Todo todo) {
Transaction transaction = null;
try (Session session = HibernateUtil.getSessionFactory().openSession()) {
transaction = session.beginTransaction();
session.persist(todo);
transaction.commit();
} catch (Exception e) {
if (transaction != null) {
transaction.rollback();
}
e.printStackTrace();
}
}
public void updateTodo(Todo todo) {
Transaction transaction = null;
try (Session session = HibernateUtil.getSessionFactory().openSession()) {
transaction = session.beginTransaction();
session.merge(todo);
transaction.commit();
} catch (Exception e) {
if (transaction != null) {
transaction.rollback();
}
e.printStackTrace();
}
}
public void deleteTodo(Long id) {
Transaction transaction = null;
try (Session session = HibernateUtil.getSessionFactory().openSession()) {
transaction = session.beginTransaction();
Todo todo = session.get(Todo.class, id);
if (todo != null) {
session.remove(todo);
}
transaction.commit();
} catch (Exception e) {
if (transaction != null) {
transaction.rollback();
}
e.printStackTrace();
}
}
public Todo getTodoById(Long id) {
try (Session session = HibernateUtil.getSessionFactory().openSession()) {
return session.get(Todo.class, id);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public List<Todo> getAllTodos() {
try (Session session = HibernateUtil.getSessionFactory().openSession()) {
return session.createQuery("FROM Todo", Todo.class).list();
} catch (Exception e) {
e.printStackTrace();
return List.of();
}
}
public List<Todo> getTodosByUser(Long userId) {
try (Session session = HibernateUtil.getSessionFactory().openSession()) {
Query<Todo> query = session.createQuery(
"FROM Todo t WHERE t.user.id = :userId ORDER BY t.completed, t.targetDate", Todo.class);
query.setParameter("userId", userId);
return query.list();
} catch (Exception e) {
e.printStackTrace();
return List.of();
}
}
public List<Todo> getCompletedTodosByUser(Long userId) {
try (Session session = HibernateUtil.getSessionFactory().openSession()) {
Query<Todo> query = session.createQuery(
"FROM Todo t WHERE t.user.id = :userId AND t.completed = true", Todo.class);
query.setParameter("userId", userId);
return query.list();
} catch (Exception e) {
e.printStackTrace();
return List.of();
}
}
public List<Todo> getIncompleteTodosByUser(Long userId) {
try (Session session = HibernateUtil.getSessionFactory().openSession()) {
Query<Todo> query = session.createQuery(
"FROM Todo t WHERE t.user.id = :userId AND t.completed = false ORDER BY t.targetDate", Todo.class);
query.setParameter("userId", userId);
return query.list();
} catch (Exception e) {
e.printStackTrace();
return List.of();
}
}
}

View file

@ -0,0 +1,112 @@
package com.todoapp.dao;
import com.todoapp.model.User;
import com.todoapp.util.HibernateUtil;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.query.Query;
import java.util.List;
import java.util.Optional;
public class UserDAO {
public void saveUser(User user) {
Transaction transaction = null;
try (Session session = HibernateUtil.getSessionFactory().openSession()) {
transaction = session.beginTransaction();
session.persist(user);
transaction.commit();
} catch (Exception e) {
if (transaction != null) {
transaction.rollback();
}
e.printStackTrace();
}
}
public void updateUser(User user) {
Transaction transaction = null;
try (Session session = HibernateUtil.getSessionFactory().openSession()) {
transaction = session.beginTransaction();
session.merge(user);
transaction.commit();
} catch (Exception e) {
if (transaction != null) {
transaction.rollback();
}
e.printStackTrace();
}
}
public void deleteUser(Long id) {
Transaction transaction = null;
try (Session session = HibernateUtil.getSessionFactory().openSession()) {
transaction = session.beginTransaction();
User user = session.get(User.class, id);
if (user != null) {
session.remove(user);
}
transaction.commit();
} catch (Exception e) {
if (transaction != null) {
transaction.rollback();
}
e.printStackTrace();
}
}
public User getUserById(Long id) {
try (Session session = HibernateUtil.getSessionFactory().openSession()) {
return session.get(User.class, id);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public Optional<User> getUserByUsername(String username) {
try (Session session = HibernateUtil.getSessionFactory().openSession()) {
Query<User> query = session.createQuery("FROM User WHERE username = :username", User.class);
query.setParameter("username", username);
User user = query.uniqueResult();
return Optional.ofNullable(user);
} catch (Exception e) {
e.printStackTrace();
return Optional.empty();
}
}
public List<User> getAllUsers() {
try (Session session = HibernateUtil.getSessionFactory().openSession()) {
return session.createQuery("FROM User", User.class).list();
} catch (Exception e) {
e.printStackTrace();
return List.of();
}
}
public boolean usernameExists(String username) {
try (Session session = HibernateUtil.getSessionFactory().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();
return false;
}
}
public boolean emailExists(String email) {
try (Session session = HibernateUtil.getSessionFactory().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();
return false;
}
}
}

View file

@ -0,0 +1,99 @@
package com.todoapp.model;
import jakarta.persistence.*;
import java.io.Serializable;
import java.time.LocalDate;
@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")
private LocalDate targetDate;
private boolean completed;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User user;
// Konstruktoren
public Todo() {
}
public Todo(String title, String description, LocalDate 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 LocalDate getTargetDate() {
return targetDate;
}
public void setTargetDate(LocalDate 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;
}
@Override
public String toString() {
return "Todo{" +
"id=" + id +
", title='" + title + '\'' +
", targetDate=" + targetDate +
", completed=" + completed +
'}';
}
}

View file

@ -0,0 +1,98 @@
package com.todoapp.model;
import jakarta.persistence.*;
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)
private String password;
@Column(nullable = false)
private String email;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
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,154 @@
package com.todoapp.servlet;
import at.favre.lib.crypto.bcrypt.BCrypt;
import com.todoapp.dao.UserDAO;
import com.todoapp.model.User;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import java.io.IOException;
import java.util.Optional;
@WebServlet("/auth/*")
public class AuthServlet extends HttpServlet {
private final UserDAO userDAO = new UserDAO();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String action = request.getPathInfo();
if (action == null) {
action = "";
}
switch (action) {
case "/register":
showRegisterForm(request, response);
break;
case "/logout":
logout(request, response);
break;
default:
showLoginForm(request, response);
break;
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String action = request.getPathInfo();
if (action == null) {
action = "";
}
switch (action) {
case "/register":
registerUser(request, response);
break;
case "/login":
loginUser(request, response);
break;
default:
response.sendRedirect(request.getContextPath() + "/auth/login");
break;
}
}
private void showLoginForm(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.getRequestDispatcher("/login.jsp").forward(request, response);
}
private void showRegisterForm(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.getRequestDispatcher("/register.jsp").forward(request, response);
}
private void registerUser(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
String username = request.getParameter("username");
String password = request.getParameter("password");
String confirmPassword = request.getParameter("confirmPassword");
String email = request.getParameter("email");
if (username == null || password == null || email == null || username.trim().isEmpty() || password.trim().isEmpty()
|| email.trim().isEmpty()) {
request.setAttribute("errorMessage", "Alle Felder müssen ausgefüllt werden");
request.getRequestDispatcher("/register.jsp").forward(request, response);
return;
}
if (!password.equals(confirmPassword)) {
request.setAttribute("errorMessage", "Passwörter stimmen nicht überein");
request.getRequestDispatcher("/register.jsp").forward(request, response);
return;
}
if (userDAO.usernameExists(username)) {
request.setAttribute("errorMessage", "Benutzername ist bereits vergeben");
request.getRequestDispatcher("/register.jsp").forward(request, response);
return;
}
if (userDAO.emailExists(email)) {
request.setAttribute("errorMessage", "E-Mail-Adresse ist bereits registriert");
request.getRequestDispatcher("/register.jsp").forward(request, response);
return;
}
// Hash password
String hashedPassword = BCrypt.withDefaults().hashToString(12, password.toCharArray());
User user = new User(username, hashedPassword, email);
userDAO.saveUser(user);
request.setAttribute("successMessage", "Registrierung erfolgreich. Bitte melden Sie sich an.");
request.getRequestDispatcher("/login.jsp").forward(request, response);
}
private void loginUser(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
String username = request.getParameter("username");
String password = request.getParameter("password");
if (username == null || password == null || username.trim().isEmpty() || password.trim().isEmpty()) {
request.setAttribute("errorMessage", "Benutzername und Passwort sind erforderlich");
request.getRequestDispatcher("/login.jsp").forward(request, response);
return;
}
Optional<User> optionalUser = userDAO.getUserByUsername(username);
if (optionalUser.isPresent()) {
User user = optionalUser.get();
BCrypt.Result result = BCrypt.verifyer().verify(password.toCharArray(), user.getPassword());
if (result.verified) {
HttpSession session = request.getSession();
session.setAttribute("user", user);
session.setAttribute("userId", user.getId());
session.setAttribute("username", user.getUsername());
response.sendRedirect(request.getContextPath() + "/todos");
return;
}
}
request.setAttribute("errorMessage", "Ungültiger Benutzername oder Passwort");
request.getRequestDispatcher("/login.jsp").forward(request, response);
}
private void logout(HttpServletRequest request, HttpServletResponse response) throws IOException {
HttpSession session = request.getSession(false);
if (session != null) {
session.invalidate();
}
response.sendRedirect(request.getContextPath() + "/auth/login");
}
}

View file

@ -0,0 +1,182 @@
package com.todoapp.servlet;
import com.todoapp.dao.TodoDAO;
import com.todoapp.dao.UserDAO;
import com.todoapp.model.Todo;
import com.todoapp.model.User;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import java.io.IOException;
import java.time.LocalDate;
import java.util.List;
@WebServlet("/todos/*")
public class TodoServlet extends HttpServlet {
private final TodoDAO todoDAO = new TodoDAO();
private final UserDAO userDAO = new UserDAO();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
HttpSession session = request.getSession(false);
if (session == null || session.getAttribute("userId") == null) {
response.sendRedirect(request.getContextPath() + "/auth/login");
return;
}
String action = request.getPathInfo();
if (action == null) {
action = "/list";
}
switch (action) {
case "/new":
showNewForm(request, response);
break;
case "/edit":
showEditForm(request, response);
break;
case "/delete":
deleteTodo(request, response);
break;
case "/complete":
completeTodo(request, response);
break;
default:
listTodos(request, response);
break;
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
HttpSession session = request.getSession(false);
if (session == null || session.getAttribute("userId") == null) {
response.sendRedirect(request.getContextPath() + "/auth/login");
return;
}
String action = request.getPathInfo();
if (action == null) {
action = "/insert";
}
switch (action) {
case "/insert":
insertTodo(request, response);
break;
case "/update":
updateTodo(request, response);
break;
default:
response.sendRedirect(request.getContextPath() + "/todos");
break;
}
}
private void listTodos(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Long userId = (Long) request.getSession().getAttribute("userId");
List<Todo> todos = todoDAO.getTodosByUser(userId);
request.setAttribute("todos", todos);
request.getRequestDispatcher("/todo-list.jsp").forward(request, response);
}
private void showNewForm(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.getRequestDispatcher("/todo-form.jsp").forward(request, response);
}
private void showEditForm(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Long userId = (Long) request.getSession().getAttribute("userId");
Long todoId = Long.parseLong(request.getParameter("id"));
Todo todo = todoDAO.getTodoById(todoId);
if (todo == null || !todo.getUser().getId().equals(userId)) {
response.sendRedirect(request.getContextPath() + "/todos");
return;
}
request.setAttribute("todo", todo);
request.getRequestDispatcher("/todo-form.jsp").forward(request, response);
}
private void insertTodo(HttpServletRequest request, HttpServletResponse response) throws IOException {
Long userId = (Long) request.getSession().getAttribute("userId");
User user = userDAO.getUserById(userId);
if (user == null) {
response.sendRedirect(request.getContextPath() + "/auth/login");
return;
}
String title = request.getParameter("title");
String description = request.getParameter("description");
LocalDate targetDate = LocalDate.parse(request.getParameter("targetDate"));
Todo todo = new Todo(title, description, targetDate, false);
todo.setUser(user);
todoDAO.saveTodo(todo);
response.sendRedirect(request.getContextPath() + "/todos");
}
private void updateTodo(HttpServletRequest request, HttpServletResponse response) throws IOException {
Long userId = (Long) request.getSession().getAttribute("userId");
Long todoId = Long.parseLong(request.getParameter("id"));
Todo todo = todoDAO.getTodoById(todoId);
if (todo == null || !todo.getUser().getId().equals(userId)) {
response.sendRedirect(request.getContextPath() + "/todos");
return;
}
String title = request.getParameter("title");
String description = request.getParameter("description");
LocalDate targetDate = LocalDate.parse(request.getParameter("targetDate"));
todo.setTitle(title);
todo.setDescription(description);
todo.setTargetDate(targetDate);
todoDAO.updateTodo(todo);
response.sendRedirect(request.getContextPath() + "/todos");
}
private void deleteTodo(HttpServletRequest request, HttpServletResponse response) throws IOException {
Long userId = (Long) request.getSession().getAttribute("userId");
Long todoId = Long.parseLong(request.getParameter("id"));
Todo todo = todoDAO.getTodoById(todoId);
if (todo != null && todo.getUser().getId().equals(userId)) {
todoDAO.deleteTodo(todoId);
}
response.sendRedirect(request.getContextPath() + "/todos");
}
private void completeTodo(HttpServletRequest request, HttpServletResponse response) throws IOException {
Long userId = (Long) request.getSession().getAttribute("userId");
Long todoId = Long.parseLong(request.getParameter("id"));
Todo todo = todoDAO.getTodoById(todoId);
if (todo != null && todo.getUser().getId().equals(userId)) {
todo.setCompleted(!todo.isCompleted());
todoDAO.updateTodo(todo);
}
response.sendRedirect(request.getContextPath() + "/todos");
}
}

View file

@ -0,0 +1,73 @@
package com.todoapp.servlet;
import com.todoapp.dao.UserDAO;
import com.todoapp.model.User;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
@WebServlet("/users")
public class UserServlet extends HttpServlet {
private final UserDAO userDAO = new UserDAO();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String idParam = request.getParameter("id");
if (idParam != null) {
Long id = Long.parseLong(idParam);
User user = userDAO.getUserById(id);
request.setAttribute("user", user);
} else {
List<User> users = userDAO.getAllUsers();
request.setAttribute("users", users);
}
request.getRequestDispatcher("/user-list.jsp").forward(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String username = request.getParameter("username");
String password = request.getParameter("password");
String email = request.getParameter("email");
if (username == null || password == null || email == null || userDAO.usernameExists(username)) {
response.sendRedirect("error.jsp?message=Invalid%20input%20or%20username%20exists");
return;
}
User user = new User(username, password, email);
userDAO.saveUser(user);
response.sendRedirect("users");
}
@Override
protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Long id = Long.parseLong(request.getParameter("id"));
String username = request.getParameter("username");
String password = request.getParameter("password");
String email = request.getParameter("email");
Optional<User> userOptional = userDAO.getUserByUsername(username);
if (userOptional.isPresent() && !userOptional.get().getId().equals(id)) {
response.sendRedirect("error.jsp?message=Username%20already%20exists");
return;
}
User user = new User(username, password, email);
user.setId(id);
userDAO.updateUser(user);
response.sendRedirect("users");
}
@Override
protected void doDelete(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Long id = Long.parseLong(request.getParameter("id"));
userDAO.deleteUser(id);
response.sendRedirect("users");
}
}

View file

@ -0,0 +1,44 @@
package com.todoapp.util;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import java.io.IOException;
@WebFilter(urlPatterns = { "/todos/*", "/users/*" })
public class AuthFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// Initialisierung
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
HttpSession session = httpRequest.getSession(false);
boolean isLoggedIn = (session != null && session.getAttribute("userId") != null);
boolean isLoginRequest = httpRequest.getRequestURI().contains("/auth/login");
boolean isRegisterRequest = httpRequest.getRequestURI().contains("/auth/register");
boolean isResourceRequest = httpRequest.getRequestURI().contains("/css/") ||
httpRequest.getRequestURI().contains("/js/");
if (isLoggedIn || isLoginRequest || isRegisterRequest || isResourceRequest) {
chain.doFilter(request, response);
} else {
httpResponse.sendRedirect(httpRequest.getContextPath() + "/auth/login");
}
}
@Override
public void destroy() {
// Aufräumen
}
}

View file

@ -0,0 +1,109 @@
//package com.todoapp.util;
//
//import org.hibernate.SessionFactory;
//import org.hibernate.boot.Metadata;
//import org.hibernate.boot.MetadataSources;
//import org.hibernate.boot.registry.StandardServiceRegistry;
//import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
//import org.hibernate.cfg.Configuration;
//
//public class HibernateUtil {
// private static StandardServiceRegistry registry;
// private static SessionFactory sessionFactory;
//
// public static SessionFactory getSessionFactory() {
// if (sessionFactory == null) {
// try {
// // Create registry
// registry = new StandardServiceRegistryBuilder()
// .configure()
// .build();
//
// // Create MetadataSources
// MetadataSources sources = new MetadataSources(registry);
//
// // Create Metadata
// Metadata metadata = sources.getMetadataBuilder().build();
//
// // Create SessionFactory
// sessionFactory = metadata.getSessionFactoryBuilder().build();
//
// } catch (Exception e) {
// e.printStackTrace();
// if (registry != null) {
// StandardServiceRegistryBuilder.destroy(registry);
// }
// }
// }
// return sessionFactory;
// }
//
// public static void shutdown() {
// if (registry != null) {
// StandardServiceRegistryBuilder.destroy(registry);
// }
// }
//}
package com.todoapp.util;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;
public class HibernateUtil {
private static SessionFactory sessionFactory;
public static 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://postgres: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 SessionFactory getSessionFactory() {
// if (sessionFactory == null) {
// try {
// // Create Configuration
// Configuration configuration = new Configuration();
//
// // Set JNDI DataSource
// Context ctx = new InitialContext();
// DataSource ds = (DataSource) ctx.lookup("jdbc/PostgresDataSource");
// configuration.setProperty("hibernate.connection.datasource",
// "jdbc/PostgresDataSource");
//
// //// Load hibernate.cfg.xml
// // configuration.configure("hibernate.cfg.xml");
//
// // Build SessionFactory
// sessionFactory = configuration.buildSessionFactory();
//
// } catch (Exception e) {
// e.printStackTrace();
// }
// }
// return sessionFactory;
// }
public static void shutdown() {
if (sessionFactory != null) {
sessionFactory.close();
}
}
}

View file

@ -0,0 +1,34 @@
<?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>
</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://postgres: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,27 @@
<?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>
<welcome-file-list>
<welcome-file>auth/login</welcome-file>
</welcome-file-list>
<error-page>
<error-code>404</error-code>
<location>/error.jsp</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/error.jsp</location>
</error-page>
<session-config>
<session-timeout>30</session-timeout>
</session-config>
</web-app>

View file

@ -0,0 +1,246 @@
/* Allgemeine Stile */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: Arial, sans-serif;
line-height: 1.6;
background-color: #f4f4f4;
color: #333;
}
.container {
max-width: 900px;
margin: 0 auto;
padding: 20px;
}
h1 {
margin-bottom: 20px;
text-align: center;
color: #333;
}
/* Header und Navigation */
header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid #ddd;
}
.user-info {
display: flex;
align-items: center;
gap: 20px;
}
.logout-btn {
background-color: #f44336;
color: white;
padding: 5px 10px;
border-radius: 4px;
text-decoration: none;
}
.logout-btn:hover {
background-color: #d32f2f;
}
/* Formulare */
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.form-group input,
.form-group textarea {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
.form-group button {
background-color: #4caf50;
color: white;
padding: 10px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
.form-group button:hover {
background-color: #45a049;
}
/* Alerts */
.alert {
padding: 10px;
margin-bottom: 15px;
border-radius: 4px;
}
.alert-danger {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.alert-success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
/* Links */
a {
color: #007bff;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
/* Buttons */
.btn {
display: inline-block;
padding: 6px 12px;
margin-bottom: 0;
font-size: 14px;
font-weight: 400;
line-height: 1.42857143;
text-align: center;
white-space: nowrap;
vertical-align: middle;
cursor: pointer;
border: 1px solid transparent;
border-radius: 4px;
text-decoration: none;
}
.btn:hover {
text-decoration: none;
}
.add-btn {
background-color: #4caf50;
color: white;
}
.add-btn:hover {
background-color: #45a049;
}
.edit-btn {
background-color: #2196f3;
color: white;
}
.edit-btn:hover {
background-color: #0b7dda;
}
.delete-btn {
background-color: #f44336;
color: white;
}
.delete-btn:hover {
background-color: #d32f2f;
}
.complete-btn {
background-color: #ff9800;
color: white;
}
.complete-btn:hover {
background-color: #e68a00;
}
.cancel-btn {
background-color: #9e9e9e;
color: white;
}
.cancel-btn:hover {
background-color: #7e7e7e;
}
.save-btn {
background-color: #4caf50;
color: white;
}
.save-btn:hover {
background-color: #45a049;
}
/* Todo-Liste */
.todo-actions {
margin-bottom: 20px;
text-align: right;
}
.todo-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
}
.todo-table th,
.todo-table td {
padding: 10px;
text-align: left;
border-bottom: 1px solid #ddd;
}
.todo-table th {
background-color: #f2f2f2;
}
.todo-table tr:hover {
background-color: #f5f5f5;
}
.todo-table tr.completed {
background-color: #f0f0f0;
color: #666;
}
.status {
padding: 3px 8px;
border-radius: 12px;
font-size: 12px;
}
.status.completed {
background-color: #4caf50;
color: white;
}
.status.pending {
background-color: #ff9800;
color: white;
}
.no-todos {
text-align: center;
padding: 20px;
background-color: #f9f9f9;
border-radius: 4px;
margin-top: 20px;
}

View file

@ -0,0 +1,23 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" isErrorPage="true" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Fehler</title>
<link rel="stylesheet" href="${pageContext.request.contextPath}/css/style.css">
</head>
<body>
<div class="container">
<h1>Oops! Es ist ein Fehler aufgetreten</h1>
<div class="alert alert-danger">
<p>Etwas ist schiefgelaufen. Bitte versuchen Sie es später erneut.</p>
<p>Fehlercode: ${pageContext.errorData.statusCode}</p>
</div>
<div class="form-group">
<a href="${pageContext.request.contextPath}/todos" class="btn">Zurück zur Startseite</a>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,45 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Login</title>
<link rel="stylesheet" href="${pageContext.request.contextPath}/css/style.css">
</head>
<body>
<div class="container">
<h1>Login</h1>
<c:if test="${not empty errorMessage}">
<div class="alert alert-danger">
<p>${errorMessage}</p>
</div>
</c:if>
<c:if test="${not empty successMessage}">
<div class="alert alert-success">
<p>${successMessage}</p>
</div>
</c:if>
<form action="${pageContext.request.contextPath}/auth/login" method="post">
<div class="form-group">
<label for="username">Benutzername:</label>
<input type="text" id="username" name="username" required>
</div>
<div class="form-group">
<label for="password">Passwort:</label>
<input type="password" id="password" name="password" required>
</div>
<div class="form-group">
<button type="submit">Anmelden</button>
</div>
</form>
<p>Noch keinen Account? <a href="${pageContext.request.contextPath}/auth/register">Registrieren</a></p>
</div>
</body>
</html>

View file

@ -0,0 +1,49 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Registrierung</title>
<link rel="stylesheet" href="${pageContext.request.contextPath}/css/style.css">
</head>
<body>
<div class="container">
<h1>Registrierung</h1>
<c:if test="${not empty errorMessage}">
<div class="alert alert-danger">
<p>${errorMessage}</p>
</div>
</c:if>
<form action="${pageContext.request.contextPath}/auth/register" method="post">
<div class="form-group">
<label for="username">Benutzername:</label>
<input type="text" id="username" name="username" required>
</div>
<div class="form-group">
<label for="email">E-Mail:</label>
<input type="email" id="email" name="email" required>
</div>
<div class="form-group">
<label for="password">Passwort:</label>
<input type="password" id="password" name="password" required>
</div>
<div class="form-group">
<label for="confirmPassword">Passwort bestätigen:</label>
<input type="password" id="confirmPassword" name="confirmPassword" required>
</div>
<div class="form-group">
<button type="submit">Registrieren</button>
</div>
</form>
<p>Bereits registriert? <a href="${pageContext.request.contextPath}/auth/login">Anmelden</a></p>
</div>
</body>
</html>

View file

@ -0,0 +1,41 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>${todo != null ? 'Aufgabe bearbeiten' : 'Neue Aufgabe'}</title>
<link rel="stylesheet" href="${pageContext.request.contextPath}/css/style.css">
</head>
<body>
<div class="container">
<h1>${todo != null ? 'Aufgabe bearbeiten' : 'Neue Aufgabe'}</h1>
<form action="${pageContext.request.contextPath}${todo != null ? '/todos/update' : '/todos/insert'}" method="post">
<c:if test="${todo != null}">
<input type="hidden" name="id" value="${todo.id}">
</c:if>
<div class="form-group">
<label for="title">Titel:</label>
<input type="text" id="title" name="title" value="${todo != null ? todo.title : ''}" required>
</div>
<div class="form-group">
<label for="description">Beschreibung:</label>
<textarea id="description" name="description" rows="5">${todo != null ? todo.description : ''}</textarea>
</div>
<div class="form-group">
<label for="targetDate">Fälligkeitsdatum:</label>
<input type="date" id="targetDate" name="targetDate" value="${todo != null ? todo.targetDate : ''}" required>
</div>
<div class="form-group">
<button type="submit" class="btn save-btn">Speichern</button>
<a href="${pageContext.request.contextPath}/todos" class="btn cancel-btn">Abbrechen</a>
</div>
</form>
</div>
</body>
</html>

View file

@ -0,0 +1,80 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Meine Aufgaben</title>
<link rel="stylesheet" href="${pageContext.request.contextPath}/css/style.css">
</head>
<body>
<div class="container">
<header>
<h1>Meine Aufgaben</h1>
<div class="user-info">
<span>Hallo, ${username}!</span>
<a href="${pageContext.request.contextPath}/auth/logout" class="logout-btn">Abmelden</a>
</div>
</header>
<div class="todo-actions">
<a href="${pageContext.request.contextPath}/todos/new" class="btn add-btn">Neue Aufgabe</a>
</div>
<c:choose>
<c:when test="${empty todos}">
<div class="no-todos">
<p>Keine Aufgaben vorhanden. Erstellen Sie eine neue Aufgabe!</p>
</div>
</c:when>
<c:otherwise>
<table class="todo-table">
<thead>
<tr>
<th>Titel</th>
<th>Beschreibung</th>
<th>Fälligkeitsdatum</th>
<th>Status</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
<c:forEach var="todo" items="${todos}">
<tr class="${todo.completed ? 'completed' : ''}">
<td>${todo.title}</td>
<td>${todo.description}</td>
<td>${todo.targetDate}</td>
<td>
<c:choose>
<c:when test="${todo.completed}">
<span class="status completed">Erledigt</span>
</c:when>
<c:otherwise>
<span class="status pending">Offen</span>
</c:otherwise>
</c:choose>
</td>
<td>
<a href="${pageContext.request.contextPath}/todos/edit?id=${todo.id}" class="btn edit-btn">Bearbeiten</a>
<a href="${pageContext.request.contextPath}/todos/delete?id=${todo.id}" class="btn delete-btn" onclick="return confirm('Möchten Sie diese Aufgabe wirklich löschen?')">Löschen</a>
<a href="${pageContext.request.contextPath}/todos/complete?id=${todo.id}" class="btn complete-btn">
<c:choose>
<c:when test="${todo.completed}">
Als offen markieren
</c:when>
<c:otherwise>
Als erledigt markieren
</c:otherwise>
</c:choose>
</a>
</td>
</tr>
</c:forEach>
</tbody>
</table>
</c:otherwise>
</c:choose>
</div>
</body>
</html>