diff --git a/src/main/java/de/igslandstuhl/database/Application.java b/src/main/java/de/igslandstuhl/database/Application.java index 4f5e59c..c78aa7f 100644 --- a/src/main/java/de/igslandstuhl/database/Application.java +++ b/src/main/java/de/igslandstuhl/database/Application.java @@ -10,6 +10,7 @@ import de.igslandstuhl.database.holidays.Holiday; import de.igslandstuhl.database.server.Server; import de.igslandstuhl.database.server.commands.Command; +import de.igslandstuhl.database.server.webserver.PostRequestHandler; import de.igslandstuhl.database.utils.CommandLineUtils; /** @@ -94,6 +95,7 @@ public static void main(String[] args) throws Exception { Server.getInstance().getConnection().createTables(); Holiday.setupCurrentSchoolYear(); + PostRequestHandler.registerHandlers(); if (getInstance().runsWebServer()) { Server.getInstance().getWebServer().start(); diff --git a/src/main/java/de/igslandstuhl/database/Registry.java b/src/main/java/de/igslandstuhl/database/Registry.java index 17b1b7c..2ec4389 100644 --- a/src/main/java/de/igslandstuhl/database/Registry.java +++ b/src/main/java/de/igslandstuhl/database/Registry.java @@ -6,12 +6,23 @@ import java.util.stream.Stream; import de.igslandstuhl.database.server.commands.Command; +import de.igslandstuhl.database.server.webserver.requests.APIPostRequest; +import de.igslandstuhl.database.server.webserver.requests.GetRequest; +import de.igslandstuhl.database.server.webserver.requests.HttpHandler; public class Registry implements Closeable { private static final Registry COMMAND_REGISTRY = new Registry<>(); + private static final Registry> POST_HANDLER_REGISTRY = new Registry<>(); + private static final Registry> GET_HANDLER_REGISTRY = new Registry<>(); public static Registry commandRegistry() { return COMMAND_REGISTRY; } + public static Registry> postRequestHandlerRegistry() { + return POST_HANDLER_REGISTRY; + } + public static Registry> getRequestHandlerRegistry() { + return GET_HANDLER_REGISTRY; + } private final Map objects = new HashMap<>(); diff --git a/src/main/java/de/igslandstuhl/database/api/APIObject.java b/src/main/java/de/igslandstuhl/database/api/APIObject.java new file mode 100644 index 0000000..9dbac05 --- /dev/null +++ b/src/main/java/de/igslandstuhl/database/api/APIObject.java @@ -0,0 +1,5 @@ +package de.igslandstuhl.database.api; + +public interface APIObject { + public String toJSON(); +} diff --git a/src/main/java/de/igslandstuhl/database/api/GraduationLevel.java b/src/main/java/de/igslandstuhl/database/api/GraduationLevel.java index d643db6..adf3c47 100644 --- a/src/main/java/de/igslandstuhl/database/api/GraduationLevel.java +++ b/src/main/java/de/igslandstuhl/database/api/GraduationLevel.java @@ -1,6 +1,6 @@ package de.igslandstuhl.database.api; -public enum GraduationLevel { +public enum GraduationLevel implements APIObject { LEVEL0 (0, "Neustarter"), LEVEL1 (1, "Starter"), LEVEL2 (2, "Durchstarter"), @@ -46,4 +46,9 @@ public static GraduationLevel of(int level) { public static GraduationLevel initialValue() { return LEVEL1; } + + @Override + public String toJSON() { + return String.valueOf(level); + } } diff --git a/src/main/java/de/igslandstuhl/database/api/Room.java b/src/main/java/de/igslandstuhl/database/api/Room.java index 577b27c..79732b4 100644 --- a/src/main/java/de/igslandstuhl/database/api/Room.java +++ b/src/main/java/de/igslandstuhl/database/api/Room.java @@ -13,7 +13,7 @@ * Represents a room in the system. * Each room has a label and a minimum level required for students to access it. */ -public class Room { +public class Room implements APIObject { private static final String[] SQL_FIELDS = {"label", "minimum_level"}; /** * A map to store all rooms, keyed by their label. @@ -229,5 +229,9 @@ public boolean equals(Object obj) { return false; return true; } + @Override + public String toJSON() { + return "{\"label\": \""+label+ "\", \"minimumLevel\": \"" + minimumLevel + "\"}"; + } } diff --git a/src/main/java/de/igslandstuhl/database/api/SchoolClass.java b/src/main/java/de/igslandstuhl/database/api/SchoolClass.java index c9b9d52..7139014 100644 --- a/src/main/java/de/igslandstuhl/database/api/SchoolClass.java +++ b/src/main/java/de/igslandstuhl/database/api/SchoolClass.java @@ -13,7 +13,7 @@ * Represents a school class with its associated subjects and students. * Provides methods to fetch subjects and students from the database. */ -public class SchoolClass { +public class SchoolClass implements APIObject { /** * SQL fields for the SchoolClass table. * Used for database queries to retrieve class information. @@ -321,5 +321,10 @@ public boolean equals(Object obj) { return false; return true; } + + @Override + public String toJSON() { + return "{\"id\": " + id + ", \"label\": \"" + label + "\", \"grade\": " + grade + "}"; + } } diff --git a/src/main/java/de/igslandstuhl/database/api/SchoolYear.java b/src/main/java/de/igslandstuhl/database/api/SchoolYear.java index 8b2bc25..56d1002 100644 --- a/src/main/java/de/igslandstuhl/database/api/SchoolYear.java +++ b/src/main/java/de/igslandstuhl/database/api/SchoolYear.java @@ -9,7 +9,7 @@ * Represents a school year with its associated properties and methods to manage it. * Provides functionality to retrieve, add, and update school years in the database. */ -public class SchoolYear { +public class SchoolYear implements APIObject { /** * SQL fields for the SchoolYear table. * Used for database queries to retrieve school year information. @@ -240,4 +240,9 @@ public boolean equals(Object obj) { return false; return true; } + + @Override + public String toJSON() { + return "{\"id\":" + id + ",\"label\":\"" + label + "\",\"weekCount\":" + weekCount + ",\"currentWeek\":" + currentWeek + "}"; + } } \ No newline at end of file diff --git a/src/main/java/de/igslandstuhl/database/api/SpecialTask.java b/src/main/java/de/igslandstuhl/database/api/SpecialTask.java index 77ee74a..c199912 100644 --- a/src/main/java/de/igslandstuhl/database/api/SpecialTask.java +++ b/src/main/java/de/igslandstuhl/database/api/SpecialTask.java @@ -50,6 +50,16 @@ public String toString() { '}'; } + @Override + public String toJSON() { + return "{" + + "\"id\":" + getId() + + ", \"name\": \"" + getName() + '"' + + ", \"ratio\":" + ratio + + ", \"subject\": \"" + subject.getName() + '"' + + '}'; + } + /** * Creates a SpecialTask object from SQL query result fields. * This method is used to convert the result of a database query into a SpecialTask object. diff --git a/src/main/java/de/igslandstuhl/database/api/Student.java b/src/main/java/de/igslandstuhl/database/api/Student.java index c5e25f7..b84c93c 100644 --- a/src/main/java/de/igslandstuhl/database/api/Student.java +++ b/src/main/java/de/igslandstuhl/database/api/Student.java @@ -389,6 +389,7 @@ public Set getCurrentRequests(Subject subject) { * Adds a subject request for this student. * @param subjectId the subject ID * @param type the request type + * @deprecated Use addSubjectRequest(Subject, SubjectRequest) instead */ public void addSubjectRequest(int subjectId, String type) { currentRequests.computeIfPresent(subjectId, (key, value) -> { @@ -397,10 +398,19 @@ public void addSubjectRequest(int subjectId, String type) { }); currentRequests.computeIfAbsent(subjectId, key -> new HashSet<>()).add(SubjectRequest.fromGermanTranslation(type)); } + /** + * Adds a subject request for this student. + * @param subject the corresponding subject + * @param subjectRequest the request + */ + public void addSubjectRequest(Subject subject, SubjectRequest subjectRequest) { + addSubjectRequest(subject.getId(), subjectRequest.getGermanTranslation()); + } /** * Removes a subject request for this student. * @param subjectId the subject ID * @param type the request type to remove + * @deprecated Use removeSubjectRequest(Subject, SubjectRequest) instead */ public void removeSubjectRequest(int subjectId, String type) { currentRequests.computeIfPresent(subjectId, (key, value) -> { @@ -408,6 +418,14 @@ public void removeSubjectRequest(int subjectId, String type) { return value; }); } + /** + * Removes a subject request from this student. + * @param subject the corresponding subject + * @param subjectRequest the request + */ + public void removeSubjectRequest(Subject subject, SubjectRequest subjectRequest) { + removeSubjectRequest(subject.getId(), subjectRequest.getGermanTranslation()); + } public void beginTask(Task task) throws SQLException { if (task == null) { diff --git a/src/main/java/de/igslandstuhl/database/api/Subject.java b/src/main/java/de/igslandstuhl/database/api/Subject.java index 09362b9..cffbc48 100644 --- a/src/main/java/de/igslandstuhl/database/api/Subject.java +++ b/src/main/java/de/igslandstuhl/database/api/Subject.java @@ -16,7 +16,7 @@ * Represents a subject in the student database. * Subjects can be added to grades and have associated topics. */ -public class Subject { +public class Subject implements APIObject { /** * A map to cache subjects by their unique identifier. * This helps avoid repeated database queries for the same subject. @@ -282,5 +282,10 @@ public boolean equals(Object obj) { return false; return true; } + + @Override + public String toJSON() { + return "{\"id\": " + id + ", \"name\": \"" + name + "\"}"; + } } diff --git a/src/main/java/de/igslandstuhl/database/api/SubjectRequest.java b/src/main/java/de/igslandstuhl/database/api/SubjectRequest.java index 0ba650f..5ec53e8 100644 --- a/src/main/java/de/igslandstuhl/database/api/SubjectRequest.java +++ b/src/main/java/de/igslandstuhl/database/api/SubjectRequest.java @@ -1,6 +1,6 @@ package de.igslandstuhl.database.api; -public enum SubjectRequest { +public enum SubjectRequest implements APIObject { HELP ("hilfe"), PARTNER ("partner"), EXPERIMENT ("betreuung"), @@ -23,4 +23,9 @@ public static SubjectRequest fromGermanTranslation(String translation) { } throw new IllegalArgumentException("No matching SubjectRequest for translation: " + translation); } + + @Override + public String toJSON() { + return '"' + germanTranslation + '"'; + } } diff --git a/src/main/java/de/igslandstuhl/database/api/Task.java b/src/main/java/de/igslandstuhl/database/api/Task.java index 8f2b158..b4188c8 100644 --- a/src/main/java/de/igslandstuhl/database/api/Task.java +++ b/src/main/java/de/igslandstuhl/database/api/Task.java @@ -16,7 +16,7 @@ * Represents a task in the student database. * Tasks are associated with topics and have different levels of difficulty. */ -public class Task { +public class Task implements APIObject { public static final int STATUS_NOT_STARTED = 0; public static final int STATUS_IN_PROGRESS = 1; public static final int STATUS_COMPLETED = 2; @@ -285,5 +285,10 @@ public static Task fromSerialized(Topic topic, String serialized) throws SQLExce TaskLevel level = TaskLevel.get(Integer.parseInt(parts[1])); return addTask(topic, name, level); } + + @Override + public String toJSON() { + return "{\"id\": " + id + ", \"topic\": " + topic + ", \"name\": \"" + name + "\", \"niveau\": " + niveau + ", \"number\": \"" + getNumber() + "\", \"ratio\": " + getRatio() + "}"; + } } diff --git a/src/main/java/de/igslandstuhl/database/api/TaskLevel.java b/src/main/java/de/igslandstuhl/database/api/TaskLevel.java index e26afe4..d04e1f3 100644 --- a/src/main/java/de/igslandstuhl/database/api/TaskLevel.java +++ b/src/main/java/de/igslandstuhl/database/api/TaskLevel.java @@ -5,7 +5,7 @@ * Each level has a specific ratio that indicates the proportion of the progress * that can be achieved at that level. */ -public enum TaskLevel { +public enum TaskLevel implements APIObject { LEVEL1 (1, "Niveau 1"), LEVEL2 (2, "Niveau 2"), LEVEL3 (3, "Niveau 3"), @@ -73,4 +73,9 @@ public double getRatio() { public String toString() { return this == SPECIAL ? "Special" : String.valueOf(number); } + + @Override + public String toJSON() { + return this == SPECIAL ? "\"Special\"" : String.valueOf(number); + } } diff --git a/src/main/java/de/igslandstuhl/database/api/Topic.java b/src/main/java/de/igslandstuhl/database/api/Topic.java index 3947c8f..e56d764 100644 --- a/src/main/java/de/igslandstuhl/database/api/Topic.java +++ b/src/main/java/de/igslandstuhl/database/api/Topic.java @@ -15,7 +15,7 @@ * Represents a topic in the student database. * Topics are associated with subjects and contain tasks of varying difficulty levels. */ -public class Topic { +public class Topic implements APIObject { /** * SQL fields for the Topic table. * Used for database queries to retrieve topic information. @@ -300,6 +300,11 @@ public String toString() { return "{\"id\":" + id + ", \"name\": \"" + name + "\", \"subject\": " + subject + ", \"ratio\": " + ratio + ", \"grade\": " + grade + ", \"tasks\": " + getTaskIds() + ", \"number\": " + number + "}"; } + @Override + public String toJSON() { + return "{\"id\":" + id + ", \"name\": \"" + name + "\", \"subject\": " + subject + ", \"ratio\": " + ratio + ", \"grade\": " + grade + + ", \"tasks\": " + getTaskIds() + ", \"number\": " + number + "}"; + } /** * Adds a new topic to the database. * This method executes a secure SQL process to insert a new topic with the provided parameters. diff --git a/src/main/java/de/igslandstuhl/database/api/User.java b/src/main/java/de/igslandstuhl/database/api/User.java index a8b2967..a86b371 100644 --- a/src/main/java/de/igslandstuhl/database/api/User.java +++ b/src/main/java/de/igslandstuhl/database/api/User.java @@ -11,7 +11,7 @@ * Abstract class representing a user in the system. * This class provides methods to check user roles, retrieve password hashes, and convert user data to JSON format. */ -public abstract class User { +public abstract class User implements APIObject { public static final User ANONYMOUS = new User() { @Override public boolean isTeacher() { diff --git a/src/main/java/de/igslandstuhl/database/api/results/GenerationResult.java b/src/main/java/de/igslandstuhl/database/api/results/GenerationResult.java index 473aa2e..e47ab60 100644 --- a/src/main/java/de/igslandstuhl/database/api/results/GenerationResult.java +++ b/src/main/java/de/igslandstuhl/database/api/results/GenerationResult.java @@ -1,6 +1,6 @@ package de.igslandstuhl.database.api.results; -public class GenerationResult { +public abstract class GenerationResult { private final T entity; private final String password; @@ -16,4 +16,6 @@ public T getEntity() { public String getPassword() { return password; } + + public abstract String toCSVRow(); } diff --git a/src/main/java/de/igslandstuhl/database/api/results/StudentGenerationResult.java b/src/main/java/de/igslandstuhl/database/api/results/StudentGenerationResult.java index 1316ae8..10ae64e 100644 --- a/src/main/java/de/igslandstuhl/database/api/results/StudentGenerationResult.java +++ b/src/main/java/de/igslandstuhl/database/api/results/StudentGenerationResult.java @@ -9,4 +9,12 @@ public StudentGenerationResult(Student student, String password) { public Student getStudent() { return getEntity(); } + @Override + public String toCSVRow() { + return new StringBuilder().append(this.getStudent().getId()).append(",") + .append(this.getStudent().getFirstName()).append(",") + .append(this.getStudent().getLastName()).append(",") + .append(this.getStudent().getEmail()).append(",") + .append(this.getPassword()).toString(); + } } \ No newline at end of file diff --git a/src/main/java/de/igslandstuhl/database/api/results/TeacherGenerationResult.java b/src/main/java/de/igslandstuhl/database/api/results/TeacherGenerationResult.java index b90f191..57e69fe 100644 --- a/src/main/java/de/igslandstuhl/database/api/results/TeacherGenerationResult.java +++ b/src/main/java/de/igslandstuhl/database/api/results/TeacherGenerationResult.java @@ -23,4 +23,13 @@ public String getLastName() { public String getEmail() { return getTeacher().getEmail(); } + + @Override + public String toCSVRow() { + return new StringBuilder().append(this.getId()).append(",") + .append(this.getFirstName()).append(",") + .append(this.getLastName()).append(",") + .append(this.getEmail()).append(",") + .append(this.getPassword()).toString(); + } } diff --git a/src/main/java/de/igslandstuhl/database/server/WebServer.java b/src/main/java/de/igslandstuhl/database/server/WebServer.java index c520951..d68223e 100644 --- a/src/main/java/de/igslandstuhl/database/server/WebServer.java +++ b/src/main/java/de/igslandstuhl/database/server/WebServer.java @@ -22,13 +22,14 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import de.igslandstuhl.database.server.webserver.GetRequest; -import de.igslandstuhl.database.server.webserver.GetResponse; import de.igslandstuhl.database.server.webserver.HttpHeader; -import de.igslandstuhl.database.server.webserver.PostRequest; import de.igslandstuhl.database.server.webserver.PostRequestHandler; -import de.igslandstuhl.database.server.webserver.PostResponse; import de.igslandstuhl.database.server.webserver.SessionManager; +import de.igslandstuhl.database.server.webserver.requests.GetRequest; +import de.igslandstuhl.database.server.webserver.requests.PostRequest; +import de.igslandstuhl.database.server.webserver.responses.GetResponse; +import de.igslandstuhl.database.server.webserver.responses.HttpResponse; +import de.igslandstuhl.database.server.webserver.responses.PostResponse; /** * A simple HTTPS web server that handles various requests related to student data. @@ -152,7 +153,7 @@ void handlePost(String headerString, InputStream in, PrintStream out) throws IOE body = URLDecoder.decode(raw, bodyCharset.name()); } PostRequest parsedRequest = new PostRequest(postHeader, body, clientIp, secure); - PostResponse response = Server.getInstance().getWebServer().getSessionManager().validateSession(parsedRequest) ? PostRequestHandler.getInstance().handlePostRequest(parsedRequest) : PostResponse.forbidden("Forbidden: session manipulation or ratelimit", parsedRequest); + HttpResponse response = Server.getInstance().getWebServer().getSessionManager().validateSession(parsedRequest) ? PostRequestHandler.getInstance().handlePostRequest(parsedRequest) : PostResponse.forbidden("Forbidden: session manipulation or ratelimit", parsedRequest); response.respond(out); } diff --git a/src/main/java/de/igslandstuhl/database/server/webserver/AccessLevel.java b/src/main/java/de/igslandstuhl/database/server/webserver/AccessLevel.java new file mode 100644 index 0000000..9119790 --- /dev/null +++ b/src/main/java/de/igslandstuhl/database/server/webserver/AccessLevel.java @@ -0,0 +1,17 @@ +package de.igslandstuhl.database.server.webserver; + +import de.igslandstuhl.database.api.User; + +public enum AccessLevel { + PUBLIC, USER, STUDENT, TEACHER, ADMIN, NONE; + + public boolean hasAccess(User user) { + if (this == PUBLIC) return true; + else if (user == null || user == User.ANONYMOUS) return false; + else if (this == USER || this == STUDENT) return true; + else if (user.isStudent()) return false; + else if (this == TEACHER) return true; + else if (user.isTeacher()) return false; + else return this == ADMIN; + } +} diff --git a/src/main/java/de/igslandstuhl/database/server/webserver/ContentType.java b/src/main/java/de/igslandstuhl/database/server/webserver/ContentType.java index 862e228..b8cc431 100644 --- a/src/main/java/de/igslandstuhl/database/server/webserver/ContentType.java +++ b/src/main/java/de/igslandstuhl/database/server/webserver/ContentType.java @@ -11,7 +11,8 @@ public enum ContentType { JAVASCRIPT ("text/javascript"), CSS ("text/css"), PNG ("image/png"), - JSON ("text/json") + JSON ("text/json"), + CSV ("text/csv") ; /** * The name of the content type, used in HTTP headers. diff --git a/src/main/java/de/igslandstuhl/database/server/webserver/PostRequestHandler.java b/src/main/java/de/igslandstuhl/database/server/webserver/PostRequestHandler.java index 9893241..12d5f33 100644 --- a/src/main/java/de/igslandstuhl/database/server/webserver/PostRequestHandler.java +++ b/src/main/java/de/igslandstuhl/database/server/webserver/PostRequestHandler.java @@ -5,18 +5,23 @@ import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.sql.SQLException; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; +import java.util.function.Function; import org.owasp.html.PolicyFactory; import org.owasp.html.Sanitizers; +import com.google.gson.reflect.TypeToken; + import de.igslandstuhl.database.Application; +import de.igslandstuhl.database.Registry; +import de.igslandstuhl.database.api.APIObject; import de.igslandstuhl.database.api.Room; import de.igslandstuhl.database.api.SchoolClass; -import de.igslandstuhl.database.api.SerializationException; import de.igslandstuhl.database.api.Student; import de.igslandstuhl.database.api.Subject; import de.igslandstuhl.database.api.SubjectRequest; @@ -24,9 +29,15 @@ import de.igslandstuhl.database.api.Teacher; import de.igslandstuhl.database.api.Topic; import de.igslandstuhl.database.api.User; -import de.igslandstuhl.database.api.results.StudentGenerationResult; -import de.igslandstuhl.database.api.results.TeacherGenerationResult; +import de.igslandstuhl.database.api.results.GenerationResult; import de.igslandstuhl.database.server.Server; +import de.igslandstuhl.database.server.webserver.requests.APIPostRequest; +import de.igslandstuhl.database.server.webserver.requests.HttpHandler; +import de.igslandstuhl.database.server.webserver.requests.PostRequest; +import de.igslandstuhl.database.server.webserver.responses.HttpResponse; +import de.igslandstuhl.database.server.webserver.responses.PostResponse; +import de.igslandstuhl.database.utils.JSONUtils; +import de.igslandstuhl.database.utils.ThrowingConsumer; public class PostRequestHandler { /** @@ -61,109 +72,20 @@ private PostRequestHandler() { * @param out * @throws IOException */ - public PostResponse handlePostRequest(PostRequest request) throws IOException { + public HttpResponse handlePostRequest(PostRequest request) throws IOException { String path = request.getPath(); - switch (path) { - case "/login": - return handleLogin(request); - case "/subject-request": - return handleSubjectRequest(request); - case "/current-topic": - return handleCurrentTopic(request); - case "/change-current-topic": - return handleChangeCurrentTopic(request); - case "/tasks": - return handleTasks(request); - case "/update-room": - return handleUpdateRoom(request); - case "/begin-task": - return handleTaskChange(request, Task.STATUS_IN_PROGRESS); - case "/complete-task": - return handleTaskChange(request, Task.STATUS_COMPLETED); - case "/cancel-task": - return handleTaskChange(request, Task.STATUS_NOT_STARTED); - case "/reopen-task": - return handleTaskChange(request, Task.STATUS_NOT_STARTED); - case "/lock-task": - return handleTaskChange(request, Task.STATUS_LOCKED); - case "/student-data": - case "/rooms": - case "/student-subjects": - return handleStudentGetData(request); - case "/teacher-classes": - case "/teacher-subjects": - return handleTeacherGetData(request); - case "/student-list": - return handleStudentList(request); - case "/add-students": - return handleAddStudents(request); - case "/add-rooms": - return handleAddRooms(request); - case "/add-teacher": - return handleAddTeacher(request); - case "/add-teachers": - return handleAddTeachers(request); - case "/teacher": - case "/add-subject-to-teacher": - return handleAddSubjectToTeacher(request); - case "/add-subject": - return handleAddSubject(request); - case "/edit-subject": - return handleEditSubject(request); - case "/delete-subject": - return handleDeleteSubject(request); - case "/class-subjects": - return handleClassSubjects(request); - case "/delete-class": - return handleDeleteClass(request); - case "/add-class": - return handleAddClass(request); - case "/edit-class": - return handleEditClass(request); - case "/add-subject-to-class": - return handleAddSubjectToClass(request); - case "/grade-list": - return handleGradeList(request); - case "/topic-list": - return handleTopicList(request); - case "/add-grade-to-subject": - return handleAddGradeToSubject(request); - case "/delete-grade-from-subject": - return handleDeleteGradeFromSubject(request); - case "/add-class-to-teacher": - return addClassToTeacher(request); - case "/lpt-file": - return handleLPTFile(request); - case "/delete-topics": - return handleDeleteTopics(request); - case "/change-graduation-level": - return handleChangeGraduationLevel(request); - case "/get-students-by-room": - return handleGetStudentsByRoom(request); - case "/search-partner": - return handleSearchPartner(request); - default: - return PostResponse.notFound("Unknown POST request path: " + path, request); - } - } + HttpHandler handler = Registry.postRequestHandlerRegistry().get(path); + if (handler == null) return PostResponse.notFound("Unknown post request path: " + path, request); - private Student getCurrentStudent(PostRequest request) { - User user = Server.getInstance().getWebServer().getSessionManager().getSessionUser(request); - if (user == null || user == User.ANONYMOUS) { - return null; // User is not logged in - } - if (user instanceof Student student) { - return student; - } else if ((user.isTeacher() || user.isAdmin()) && request.getJson().containsKey("studentId")) { - return Student.get(((Number) request.getJson().get("studentId")).intValue()); - } - return null; + APIPostRequest rq = APIPostRequest.fromPostRequest(request); + + return handler.handleHttpRequest(rq); } - private String prepare(String webInput) { + private static String prepare(String webInput) { return prepare(webInput, true); } - private String prepare(String webInput, boolean sanitize) { + private static String prepare(String webInput, boolean sanitize) { try { webInput = URLDecoder.decode(webInput, StandardCharsets.UTF_8.name()); } catch (UnsupportedEncodingException e) { @@ -181,1093 +103,299 @@ private String prepare(String webInput, boolean sanitize) { } return webInput; } - - /** - * Handles a POST request for subject requests, parsing the request body and updating the student's subject requests. - * Subject requests can be requests for help, a partner etc. - * Subject requests are expected to be in JSON format with fields for subjectId and type. - * If the request is successful, it responds with a 200 OK status and a message. - * If the request is invalid or the user is not logged in, it responds with an appropriate error status. - * - * @param request The parsed POST request containing the subject ID. - * @throws IOException If an I/O error occurs while reading or writing. - */ - private PostResponse handleSubjectRequest(PostRequest request) throws IOException { - int contentLength = request.getContentLength(); - if (contentLength <= 0) { - return PostResponse.badRequest("Missing or invalid Content-Length!", request); - } - Map json = request.getJson(); - // Parse JSON body for subjectId and type using Gson - int subjectId = ((Number) json.get("subjectId")).intValue(); - String type = (String) json.get("type"); - - Student student = getCurrentStudent(request); - boolean remove = json.containsKey("remove") && (boolean) json.get("remove"); - PostResponse response; - if (student != null) { - if (remove) { - student.removeSubjectRequest(subjectId, type); - response = PostResponse.ok("Removed request", ContentType.TEXT_PLAIN, request); - } else { - student.addSubjectRequest(subjectId, type); - response = PostResponse.ok("Saved request", ContentType.TEXT_PLAIN, request); - } - } else { - response = PostResponse.unauthorized("Not logged in or invalid session", request); - } - return response; - } - /** - * Handles a POST request for user login, parsing the request body and validating the credentials. - * If the login is successful, it generates a session ID and stores it in the session store. - * If the login fails, it responds with a 401 Unauthorized status. - * - * @param request The parsed POST request containing the login credentials. - * @throws IOException If an I/O error occurs while reading or writing. - */ - private PostResponse handleLogin(PostRequest request) throws IOException { - int contentLength = request.getContentLength(); - - // If no Content-Length header is present or it is invalid, respond with bad request - if (contentLength <= 0) { - return PostResponse.badRequest("Missing or invalid Content-Length!", request); - } - - // Expected format: "username=user&password=pass" - Map params = request.getFormData(); - String username = prepare(params.get("username"), false); - String password = prepare(params.get("password"), false); - - // Check login credentials in the database - if (Server.getInstance().isValidUser(username, password)) { - SessionManager manager = Server.getInstance().getWebServer().getSessionManager(); - Session session = manager.getSession(request); - manager.addSessionUser(session, username); - - return PostResponse.ok("Login successful", ContentType.TEXT_PLAIN, request, session.createSessionCookie()); - } else { - return PostResponse.unauthorized("Wrong credentials!", request); - } - } - /** - * Handles a POST request for the current topic of a student in a specific subject. - * It reads the request body, extracts the subject ID, and retrieves the current topic for that subject. - * If successful, it responds with the topic details in JSON format. - * If the user is not logged in or there is no current topic, it responds with an appropriate error status. - * - * @param request The parsed POST request containing the subject ID. - * @throws IOException If an I/O error occurs while reading or writing. - */ - private PostResponse handleCurrentTopic(PostRequest request) throws IOException { - int contentLength = request.getContentLength(); - if (contentLength <= 0) { - return PostResponse.badRequest("Missing or invalid Content-Length!", request); - } - // Parse JSON body for subjectId using Gson - Map json = request.getJson(); - int subjectId = ((Number) json.get("subjectId")).intValue(); - - Student student = getCurrentStudent(request); - PostResponse response; - if (student != null) { - Subject subject = Subject.get(subjectId); - Topic topic = student.getCurrentTopic(subject); - if (topic != null) { - response = PostResponse.ok(topic.toString(), ContentType.JSON, request); - } else { - response = PostResponse.badRequest("No current topic for this subject.", request); - } - } else { - response = PostResponse.unauthorized("Not logged in or invalid session", request); - } - return response; - } - /** - * Handles a POST request to change the current topic for a student in a specific subject. - * It reads the request body, extracts the subject ID and topic ID, and updates the student's current topic. - * If successful, it responds with a 200 OK status; otherwise, it responds with an error status. - * - * @param request The parsed POST request containing the subject and topic IDs. - * @throws IOException If an I/O error occurs while reading or writing. - */ - private PostResponse handleChangeCurrentTopic(PostRequest request) throws IOException { - // Test if current user is admin or teacher - User user = Server.getInstance().getWebServer().getSessionManager().getSessionUser(request); - if (user == null || (!user.isAdmin() && !user.isTeacher())) { - return PostResponse.unauthorized("Not logged in or invalid session", request); - } - int contentLength = request.getContentLength(); - if (contentLength <= 0) { - return PostResponse.badRequest("Missing or invalid Content-Length!", request); - } - Map json = request.getJson(); - if (!json.containsKey("subjectId") || !json.containsKey("topicId")) { - return PostResponse.badRequest("Missing subjectId or topicId in request.", request); - } - int subjectId = ((Number) json.get("subjectId")).intValue(); - int topicId = ((Number) json.get("topicId")).intValue(); - - Student student = getCurrentStudent(request); - PostResponse response; - if (student != null) { - Subject subject = Subject.get(subjectId); - Topic topic = Topic.get(topicId); - if (subject != null && topic != null) { - try { - student.setCurrentTopic(subject, topic); - response = PostResponse.ok("Current topic changed successfully", ContentType.TEXT_PLAIN, request); - } catch (Exception e) { - response = PostResponse.internalServerError("Error changing topic: " + e.getMessage(), request); - } - } else { - response = PostResponse.badRequest("Subject or topic not found.", request); - } - } else { - response = PostResponse.unauthorized("Not logged in or invalid session", request); - } - return response; - } - - /** - * Handles a POST request for tasks, parsing the request body to retrieve a list of task IDs. - * It retrieves the tasks associated with those IDs and responds with their details in JSON format. - * If the user is not logged in or the request is invalid, it responds with an appropriate error status. - * - * @param request The parsed POST request containing the task IDs. - * @throws IOException If an I/O error occurs while reading or writing. - */ - private PostResponse handleTasks(PostRequest request) throws IOException { - int contentLength = request.getContentLength(); - if (contentLength <= 0) { - return PostResponse.badRequest("Fehlende oder ungültige Content-Length!", request); - } - - Map json = request.getJson(); - - // Expecting: { "ids": [1,2,3,...] } - java.util.List ids = null; - if (json.get("ids") instanceof java.util.List l) { - ids = new java.util.ArrayList<>(); - for (Object o : l) { - if (o instanceof Number n) { - ids.add(n.intValue()); - } - } - } - - Student student = getCurrentStudent(request); - PostResponse response; - if (student != null && ids != null) { - // Assuming Student has a method getTasksByIds(List ids) - // and returns a List or similar - java.util.List tasks = Task.getTasksByIds(ids); - String jsonResponse = tasks.toString(); - response = PostResponse.ok(jsonResponse, ContentType.JSON, request); - } else if (ids == null) { - response = PostResponse.badRequest("Missing or invalid 'ids' in request body.", request); - } else { - response = PostResponse.unauthorized("Not logged in or invalid session", request); - } - return response; - } - /** - * Handles a POST request to update the current room of a student. - * It reads the request body, extracts the room label, and updates the student's current room. - * If successful, it responds with a 200 OK status; otherwise, it responds with an error status. - * - * @param request The parsed POST request containing the room label. - * @throws IOException If an I/O error occurs while reading or writing. - */ - private PostResponse handleUpdateRoom(PostRequest request) throws IOException { - int contentLength = request.getContentLength(); - if (contentLength <= 0) { - return PostResponse.badRequest("Missing or invalid Content-Length!", request); - } - Map json = request.getJson(); - String roomLabel = (String) json.get("room"); - - Student student = getCurrentStudent(request); - PostResponse response; - if (student != null && roomLabel != null) { - try { - Room room = Room.getRoom(roomLabel); - if (room != null) { - student.setCurrentRoom(room); - response = PostResponse.ok("{\"status\":\"ok\"}", ContentType.JSON, request); - } else { - response = PostResponse.badRequest("Room not found.", request); - } - } catch (Exception e) { - response = PostResponse.internalServerError(e.getMessage(), request); - } - } else { - response = PostResponse.unauthorized("Not logged in or invalid session", request); - } - return response; - } - private PostResponse handleStudentGetData(PostRequest request) { - String path = request.getPath(); - if (path.equals("/student-data")) { - path = "/mydata"; - } else if (path.equals("/student-subjects")) { - path = "/mysubjects"; - } - - int studentID = ((Number) request.getJson().get("studentId")).intValue(); - Student student = Student.get(studentID); - User user = Server.getInstance().getWebServer().getSessionManager().getSessionUser(request); - if (student == null) { - return PostResponse.notFound("Student with ID " + studentID + " not found.", request); - } else if (!student.hasTeacher(user.asTeacher()) && !user.isAdmin()) { - return PostResponse.forbidden("You are not allowed to access this student's data.", request); - } + private static PostResponse handleStudentGetData(APIPostRequest request) { + String path = request.getPath().replace("student-", "my"); + Student student = request.getCurrentStudent(); String email = student.getEmail(); // Email is the username for the student - - return PostResponse.getResource(WebResourceHandler.locationFromPath(path, User.getUser(email)), email, request); + return PostResponse.getResource(WebResourceHandler.locationFromPath(path, student), email, request); } - private PostResponse handleTeacherGetData(PostRequest request) { - String path = request.getPath(); - if (path.equals("/teacher-classes")) { - path = "/myclasses"; - } else if (path.equals("/teacher-subjects")) { - path = "/mysubjects"; - } - if (!Server.getInstance().getWebServer().getSessionManager().getSessionUser(request).isAdmin()) { - return PostResponse.unauthorized("Not logged in or invalid session", request); - } - - int id = ((Number) request.getJson().get("teacherId")).intValue(); - Teacher teacher = Teacher.get(id); - + private static PostResponse handleTeacherGetData(APIPostRequest request) { + String path = request.getPath().replace("teacher-", "my"); + Teacher teacher = request.getCurrentTeacher(); String email = teacher.getEmail(); // Email is the username for the teacher return PostResponse.getResource(WebResourceHandler.locationFromPath(path, User.getUser(email)), email, request); } - private PostResponse handleStudentList(PostRequest request) { - String path = request.getPath(); - if (!path.equals("/student-list")) { - return PostResponse.notFound("Unknown POST request path: " + path, request); - } - - User user = Server.getInstance().getWebServer().getSessionManager().getSessionUser(request); - if (!user.isTeacher() && !user.isAdmin()) { - return PostResponse.unauthorized("Not logged in or invalid session", request); - } - - SchoolClass schoolClass = SchoolClass.get(((Number) request.getJson().get("classId")).intValue()); - if (schoolClass == null || (user.isTeacher() && !user.asTeacher().getClassIds().contains(schoolClass.getId()))) { - return PostResponse.forbidden("You are not allowed to access this class's student list.", request); - } - // Get the list of students for given SchoolClass - java.util.List students = schoolClass.getStudents(); - StringBuilder responseBuilder = new StringBuilder("["); - for (int i = 0; i < students.size(); i++) { - Student student = students.get(i); - responseBuilder.append("{\"id\":").append(student.getId()) - .append(",\"name\":\"").append(student.getFirstName()).append(" ").append(student.getLastName()).append('"') - .append(", \"actionRequired\":").append(student.isActionRequired()) - .append(", \"graduationLevel\":").append(student.getGraduationLevel().getLevel()) - .append(", \"room\":\"").append(student.getCurrentRoom() != null ? student.getCurrentRoom().getLabel() : "None").append("\""); - if (request.getJson().containsKey("subjectId") && request.getJson().get("subjectId") instanceof Number subjectId) { - Set subjectRequests = student.getCurrentRequests().keySet().contains(subjectId.intValue()) ? student.getCurrentRequests().get(subjectId.intValue()) : Set.of(); - responseBuilder.append(", \"experiment\":").append(subjectRequests.stream().anyMatch(r -> r == SubjectRequest.EXPERIMENT)) - .append(", \"help\":").append(subjectRequests.stream().anyMatch(r -> r == SubjectRequest.HELP)) - .append(", \"test\":").append(subjectRequests.stream().anyMatch(r -> r == SubjectRequest.EXAM)) - .append(", \"partner\":").append(subjectRequests.stream().anyMatch(r -> r == SubjectRequest.PARTNER)); - } - responseBuilder.append("}"); - if (i < students.size() - 1) { - responseBuilder.append(", "); - } - } - responseBuilder.append("]"); - - return PostResponse.ok(responseBuilder.toString(), ContentType.JSON, request); - } - private PostResponse handleTaskChange(PostRequest request, int newStatus) throws IOException { - int contentLength = request.getContentLength(); - if (contentLength <= 0) { - return PostResponse.badRequest("Missing or invalid Content-Length!", request); - } - Map json = request.getJson(); - int taskId = ((Number) json.get("taskId")).intValue(); - - Student student = getCurrentStudent(request); - PostResponse response; - if (student != null) { - Task task = Task.get(taskId); - if (task != null) { - try { - student.changeTaskStatus(task, newStatus); - response = PostResponse.ok("Task status changed successfully", ContentType.TEXT_PLAIN, request); - } catch (java.sql.SQLException e) { - response = PostResponse.internalServerError("Database error: " + e.getMessage(), request); - } - } else { - response = PostResponse.notFound("Task not found", request); + private static PostResponse handleTaskChange(APIPostRequest request, int newStatus) throws IOException, SQLException { + Student student = request.getCurrentStudent(); + if (student == null) return PostResponse.unauthorized(request); + Task task = request.getTask(); + if (task == null) return PostResponse.notFound("Task not found", request);; + student.changeTaskStatus(task, newStatus); + return PostResponse.ok("Task status changed successfully", ContentType.TEXT_PLAIN, request); + } + public static void registerTaskChangeHandler(String path, AccessLevel accessLevel, int taskStatus) { + HttpHandler.registerPostRequestHandler(path, accessLevel, (rq) -> { + Task task = Task.get(rq.getInt("taskId")); + Student student = rq.getCurrentStudent(); + if (student == null) return PostResponse.unauthorized("Not logged in or invalid session", rq); + if (task == null) return PostResponse.notFound("Task not found", rq); + try { + student.changeTaskStatus(task, taskStatus); + return PostResponse.ok("Task status changed successfully", ContentType.TEXT_PLAIN, rq); + } catch (SQLException e) { + return PostResponse.internalServerError("Database error: " + e.getMessage(), rq); } - } else { - response = PostResponse.unauthorized("Not logged in or invalid session", request); - } - return response; + }); } - private PostResponse handleAddStudents(PostRequest request) throws IOException { - // Test if current user is admin - User user = Server.getInstance().getWebServer().getSessionManager().getSessionUser(request); - if (user == null || !user.isAdmin()) { - return PostResponse.unauthorized("Not logged in or invalid session", request); - } - int contentLength = request.getContentLength(); - if (contentLength <= 0) { - return PostResponse.badRequest("Missing or invalid Content-Length!", request); - } - String csv = prepare(request.getBodyAsString().replaceFirst("csv=", ""), false); + public static PostResponse handleBatchInsertJson(PostRequest rq, String key, ContentType contentType, Function, T> factory, Function, String> serializer) { try { - StudentGenerationResult[] results = Student.generateStudentsFromCSV(csv); - StringBuilder responseBuilder = new StringBuilder(); - for (int i = 0; i < results.length; i++) { - StudentGenerationResult result = results[i]; - responseBuilder.append(result.getStudent().getId()).append(",") - .append(result.getStudent().getFirstName()).append(",") - .append(result.getStudent().getLastName()).append(",") - .append(result.getStudent().getEmail()).append(",") - .append(result.getPassword()); - if (i < results.length - 1) { - responseBuilder.append("\n"); - } - } - return PostResponse.ok(responseBuilder.toString(), ContentType.JSON, request); - } catch (java.sql.SQLException e) { - return PostResponse.internalServerError("Database error: " + e.getMessage(), request); + @SuppressWarnings("unchecked") + List> rawItems = (List>) rq.getJson().get(key); + List entities = rawItems.stream().map(factory).toList(); + return PostResponse.ok(serializer.apply(entities), contentType, rq); + } catch (Exception e) { + return PostResponse.badRequest("Could not add " + key + ": " + e, rq); } } - private PostResponse handleAddRooms(PostRequest request) throws IOException { - // Test if current user is admin - User user = Server.getInstance().getWebServer().getSessionManager().getSessionUser(request); - if (user == null || !user.isAdmin()) { - return PostResponse.unauthorized("Not logged in or invalid session", request); - } - int contentLength = request.getContentLength(); - if (contentLength <= 0) { - return PostResponse.badRequest("Missing or invalid Content-Length!", request); - } - String csv = prepare(request.getBodyAsString().replaceFirst("csv=", "")); + public static PostResponse handleBatchInsertCSV(PostRequest rq, String key, ContentType contentType, Function factory, Function serializer) { try { - Room[] rooms = Room.generateRoomsFromCSV(csv); - StringBuilder responseBuilder = new StringBuilder("[\n"); - for (int i = 0; i < rooms.length; i++) { - Room room = rooms[i]; - responseBuilder.append(" {\"label\":\"").append(room.getLabel()).append('"') - .append(",\"minimumLevel\":").append(room.getMinimumLevel()).append('}'); - if (i < rooms.length - 1) { - responseBuilder.append(",\n"); - } + T[] entities = factory.apply(rq.getBodyAsString().replace("csv=", "")); + return PostResponse.ok(serializer.apply(entities), contentType, rq); + } catch (Exception e) { + return PostResponse.badRequest("Could not add " + key + ": " + e, rq); + } + } + public static String csvResult(GenerationResult[] results) { + return Arrays.stream(results).map(GenerationResult::toCSVRow).reduce("", (r1,r2) -> r1+"\n"+r2); + } + public static PostResponse handleObjectAction(APIPostRequest rq, TypeToken type, PostResponse successMessage, ThrowingConsumer handler) throws Exception { + T object = rq.getAPIObject(type); + handler.accept(object); + return successMessage; + } + public static void registerHandlers() { + HttpHandler.registerPostRequestHandler("/login", AccessLevel.PUBLIC, (rq) -> { + String username = prepare(rq.getString("username"), false); + String password = prepare(rq.getString("password"), false); + // Check login credentials in the database + if (Server.getInstance().isValidUser(username, password)) { + SessionManager manager = Server.getInstance().getWebServer().getSessionManager(); + Session session = manager.getSession(rq); + manager.addSessionUser(session, username); + return PostResponse.ok("Login successful", ContentType.TEXT_PLAIN, rq, session.createSessionCookie()); + } else { + return PostResponse.unauthorized("Wrong credentials!", rq); } - responseBuilder.append("\n]"); - return PostResponse.ok(responseBuilder.toString(), ContentType.JSON, request); - } catch (java.sql.SQLException e) { - return PostResponse.internalServerError("Database error: " + e.getMessage(), request); - } catch (IllegalArgumentException e) { - return PostResponse.badRequest("Invalid CSV format: " + e.getMessage(), request); - } - } - private PostResponse handleAddTeacher(PostRequest request) throws IOException { - // Test if current user is admin - User user = Server.getInstance().getWebServer().getSessionManager().getSessionUser(request); - if (user == null || !user.isAdmin()) { - return PostResponse.unauthorized("Not logged in or invalid session", request); - } - int contentLength = request.getContentLength(); - if (contentLength <= 0) { - return PostResponse.badRequest("Missing or invalid Content-Length!", request); - } - Map data = request.getFormData(); - String firstName = prepare(data.get("firstName")); - String lastName = prepare(data.get("lastName")); - String email = prepare(data.get("email")); - String password = Teacher.generateRandomPassword(12, (contentLength << 4 + firstName.length() + lastName.length()) << 7 + System.currentTimeMillis() * new Random().nextInt()); - - try { + }); + HttpHandler.registerPostRequestHandler("/add-students", AccessLevel.ADMIN, (rq) -> + handleBatchInsertCSV(rq, "students", ContentType.CSV, t -> { + try { + return Student.generateStudentsFromCSV(t); + } catch (SQLException e) { + throw new IllegalStateException(e); + } + }, PostRequestHandler::csvResult) + ); + HttpHandler.registerPostRequestHandler("/add-teachers", AccessLevel.ADMIN, (rq) -> + handleBatchInsertCSV(rq, "teachers", ContentType.CSV, t -> { + try { + return Teacher.generateTeachersFromCSV(t); + } catch (SQLException e) { + throw new IllegalStateException(e); + } + }, PostRequestHandler::csvResult) + ); + HttpHandler.registerPostRequestHandler("/add-rooms", AccessLevel.ADMIN, (rq) -> + handleBatchInsertCSV(rq, "rooms", ContentType.JSON, t -> { + try { + return Room.generateRoomsFromCSV(t); + } catch (SQLException e) { + throw new IllegalStateException(e); + } + }, Arrays::toString) + ); + HttpHandler.registerPostRequestHandler("/add-teacher", AccessLevel.ADMIN, (rq) -> { + String firstName = prepare(rq.getString("firstName")); + String lastName = prepare(rq.getString("lastName")); + String email = prepare(rq.getString("email"), false); + String password = Teacher.generateRandomPassword(12, (rq.getContentLength() << 4 + firstName.length() + lastName.length()) << 7 + System.currentTimeMillis() * new Random().nextInt()); Teacher teacher = Teacher.registerTeacher(firstName, lastName, email, password); - return PostResponse.ok(teacher.toString().replace("}", "") + ", \"password\": " + password + "}", ContentType.JSON, request); - } catch (java.sql.SQLException e) { - return PostResponse.internalServerError("Database error: " + e.getMessage(), request); - } catch (IllegalArgumentException e) { - return PostResponse.badRequest("Invalid input: " + e.getMessage(), request); - } - } - private PostResponse handleAddTeachers(PostRequest request) throws IOException { - // Test if current user is admin - User user = Server.getInstance().getWebServer().getSessionManager().getSessionUser(request); - if (user == null || !user.isAdmin()) { - return PostResponse.unauthorized("Not logged in or invalid session", request); - } - int contentLength = request.getContentLength(); - if (contentLength <= 0) { - return PostResponse.badRequest("Missing or invalid Content-Length!", request); - } - String csv = prepare(request.getBodyAsString().replaceFirst("csv=", ""), false); - try { - TeacherGenerationResult[] teachers = Teacher.generateTeachersFromCSV(csv); - StringBuilder responseBuilder = new StringBuilder(); - for (int i = 0; i < teachers.length; i++) { - TeacherGenerationResult result = teachers[i]; - responseBuilder.append(result.getId()).append(",") - .append(result.getFirstName()).append(",") - .append(result.getLastName()).append(",") - .append(result.getEmail()).append(",") - .append(result.getPassword()); - if (i < teachers.length - 1) { - responseBuilder.append("\n"); + return PostResponse.ok(teacher.toString().replace("}", "") + ", \"password\": " + password + "}", ContentType.JSON, rq); + }); + HttpHandler.registerPostRequestHandler("/add-subject", AccessLevel.ADMIN, (rq) -> { + Subject.addSubject(rq.getString("name")); + return PostResponse.redirect("/manage_subjects", rq); + }); + HttpHandler.registerPostRequestHandler("/add-class", AccessLevel.ADMIN, (rq) -> { + SchoolClass.addClass(rq.getString("className"), rq.getInt("grade")); + return PostResponse.redirect("/manage_subjects", rq); + }); + HttpHandler.registerPostRequestHandler("/lpt-file", AccessLevel.ADMIN, (rq) -> { + String file = prepare(rq.getBodyAsString().replaceFirst("file=", "").replace("Â", "")); + Application.getInstance().readFile(file); + return PostResponse.ok("File data stored", ContentType.TEXT_PLAIN, rq); + }); + HttpHandler.registerPostRequestHandler("/subject-request", AccessLevel.USER, (rq) -> { + Student student = rq.getCurrentStudent(); + Subject subject = rq.getSubject(); + SubjectRequest subjectRequest = rq.getSubjectRequest(); + if (student != null) { + if (rq.getBoolean("remove")) { + student.removeSubjectRequest(subject, subjectRequest); + return PostResponse.ok("Removed request", ContentType.TEXT_PLAIN, rq); + } else { + student.addSubjectRequest(subject, subjectRequest); + return PostResponse.ok("Added request", ContentType.TEXT_PLAIN, rq); } + } else { + return PostResponse.unauthorized(rq); } - return PostResponse.ok(responseBuilder.toString(), ContentType.JSON, request); - } catch (java.sql.SQLException e) { - return PostResponse.internalServerError("Database error: " + e.getMessage(), request); - } catch (IllegalArgumentException e) { - return PostResponse.badRequest("Invalid CSV format: " + e.getMessage(), request); - } - } - private PostResponse handleAddSubjectToTeacher(PostRequest request) throws IOException { - // Test if current user is admin - User user = Server.getInstance().getWebServer().getSessionManager().getSessionUser(request); - if (user == null || !user.isAdmin()) { - return PostResponse.unauthorized("Not logged in or invalid session", request); - } - int contentLength = request.getContentLength(); - if (contentLength <= 0) { - return PostResponse.badRequest("Missing or invalid Content-Length!", request); - } - Map data = request.getFormData(); - int teacherId = Integer.parseInt(data.get("teacherId")); - int subjectId = Integer.parseInt(data.get("subject")); - - Teacher teacher = Teacher.get(teacherId); - Subject subject = Subject.get(subjectId); - if (teacher == null || subject == null) { - return PostResponse.notFound("Teacher or subject not found", request); - } - - try { - teacher.addSubject(subject); - return PostResponse.redirect("/teacher", request); - } catch (java.sql.SQLException e) { - return PostResponse.internalServerError("Database error: " + e.getMessage(), request); - } - } - private PostResponse handleAddSubject(PostRequest request) throws IOException { - // Test if current user is admin - User user = Server.getInstance().getWebServer().getSessionManager().getSessionUser(request); - if (user == null || !user.isAdmin()) { - return PostResponse.unauthorized("Not logged in or invalid session", request); - } - int contentLength = request.getContentLength(); - if (contentLength <= 0) { - return PostResponse.badRequest("Missing or invalid Content-Length!", request); - } - Map data = request.getFormData(); - String name = prepare(data.get("name")); - - try { - Subject.addSubject(name); - return PostResponse.redirect("/manage_subjects", request); - } catch (java.sql.SQLException e) { - return PostResponse.internalServerError("Database error: " + e.getMessage(), request); - } catch (IllegalArgumentException e) { - return PostResponse.badRequest("Invalid input: " + e.getMessage(), request); - } - } - private PostResponse handleClassSubjects(PostRequest request) throws IOException { - // Test if current user is admin - User user = Server.getInstance().getWebServer().getSessionManager().getSessionUser(request); - if (user == null || !user.isAdmin()) { - return PostResponse.unauthorized("Not logged in or invalid session", request); - } - int contentLength = request.getContentLength(); - if (contentLength <= 0) { - return PostResponse.badRequest("Missing or invalid Content-Length!", request); - } - Map json = request.getJson(); - int classId = ((Number) json.get("classId")).intValue(); - - SchoolClass schoolClass = SchoolClass.get(classId); - if (schoolClass == null) { - return PostResponse.notFound("Class not found", request); - } - - java.util.List subjects = schoolClass.getSubjects(); - StringBuilder responseBuilder = new StringBuilder("["); - for (int i = 0; i < subjects.size(); i++) { - Subject subject = subjects.get(i); - responseBuilder.append("{\"id\":").append(subject.getId()) - .append(",\"name\":\"").append(subject.getName()).append("\"}"); - if (i < subjects.size() - 1) { - responseBuilder.append(","); - } - } - responseBuilder.append("]"); - return PostResponse.ok(responseBuilder.toString(), ContentType.JSON, request); - } - private PostResponse handleDeleteClass(PostRequest request) throws IOException { - // Test if current user is admin - User user = Server.getInstance().getWebServer().getSessionManager().getSessionUser(request); - if (user == null || !user.isAdmin()) { - return PostResponse.unauthorized("Not logged in or invalid session", request); - } - int contentLength = request.getContentLength(); - if (contentLength <= 0) { - return PostResponse.badRequest("Missing or invalid Content-Length!", request); - } - Map json = request.getJson(); - int classId = ((Number)json.get("id")).intValue(); - - SchoolClass schoolClass = SchoolClass.get(classId); - if (schoolClass == null) { - return PostResponse.notFound("Class not found", request); - } - - try { - schoolClass.delete(); - return PostResponse.ok("Class deleted successfully", ContentType.TEXT_PLAIN, request); - } catch (java.sql.SQLException e) { - return PostResponse.internalServerError("Database error: " + e.getMessage(), request); - } - } - private PostResponse handleAddClass(PostRequest request) throws IOException { - // Test if current user is admin - User user = Server.getInstance().getWebServer().getSessionManager().getSessionUser(request); - if (user == null || !user.isAdmin()) { - return PostResponse.unauthorized("Not logged in or invalid session", request); - } - int contentLength = request.getContentLength(); - if (contentLength <= 0) { - return PostResponse.badRequest("Missing or invalid Content-Length!", request); - } - Map data = request.getFormData(); - String name = data.get("className"); - int grade = Integer.parseInt(data.get("grade")); - - try { - SchoolClass.addClass(name, grade); - return PostResponse.redirect("/manage_classes", request); - } catch (java.sql.SQLException e) { - return PostResponse.internalServerError("Database error: " + e.getMessage(), request); - } catch (IllegalArgumentException e) { - return PostResponse.badRequest("Invalid input: " + e.getMessage(), request); - } - } - /** - * Handles a POST request to edit an existing class. - * Only admins are allowed to perform this action. - * - * @param request The parsed POST request containing the class edit data. - * @throws IOException If an I/O error occurs while reading or writing. - */ - private PostResponse handleEditClass(PostRequest request) throws IOException { - // Test if current user is admin - User user = Server.getInstance().getWebServer().getSessionManager().getSessionUser(request); - if (user == null || !user.isAdmin()) { - return PostResponse.unauthorized("Not logged in or invalid session", request); - } - int contentLength = request.getContentLength(); - if (contentLength <= 0) { - return PostResponse.badRequest("Missing or invalid Content-Length!", request); - } - Map data = request.getFormData(); - int classId; - try { - classId = Integer.parseInt(data.get("id")); - } catch (NumberFormatException | NullPointerException e) { - return PostResponse.badRequest("Invalid or missing classId.", request); - } - String name = data.get("name"); - int grade; - try { - grade = Integer.parseInt(data.get("grade")); - } catch (NumberFormatException | NullPointerException e) { - return PostResponse.badRequest("Invalid or missing grade.", request); - } - - SchoolClass schoolClass = SchoolClass.get(classId); - if (schoolClass == null) { - return PostResponse.notFound("Class not found", request); - } - - try { - schoolClass.edit(name, grade); - return PostResponse.redirect("/manage_classes", request); - } catch (java.sql.SQLException e) { - return PostResponse.internalServerError("Database error: " + e.getMessage(), request); - } catch (IllegalArgumentException e) { - return PostResponse.badRequest("Invalid input: " + e.getMessage(), request); - } - } - /** - * Handles a POST request to add a subject to a class. - * Only admins are allowed to perform this action. - * - * @param request The parsed POST request containing the class and subject data. - * @throws IOException If an I/O error occurs while reading or writing. - */ - private PostResponse handleAddSubjectToClass(PostRequest request) throws IOException { - // Test if current user is admin - User user = Server.getInstance().getWebServer().getSessionManager().getSessionUser(request); - if (user == null || !user.isAdmin()) { - return PostResponse.unauthorized("Not logged in or invalid session", request); - } - int contentLength = request.getContentLength(); - if (contentLength <= 0) { - return PostResponse.badRequest("Missing or invalid Content-Length!", request); - } - Map data = request.getFormData(); - int classId; - int subjectId; - try { - classId = Integer.parseInt(data.get("classId")); - subjectId = Integer.parseInt(data.get("subject")); - } catch (NumberFormatException | NullPointerException e) { - return PostResponse.badRequest("Invalid or missing classId or subjectId.", request); - } - - SchoolClass schoolClass = SchoolClass.get(classId); - Subject subject = Subject.get(subjectId); - if (schoolClass == null || subject == null) { - return PostResponse.notFound("Class or subject not found", request); - } - - try { - schoolClass.addSubject(subject); - return PostResponse.redirect("/class", request); - } catch (java.sql.SQLException e) { - return PostResponse.internalServerError("Database error: " + e.getMessage(), request); - } - } - - /** - * Handles a POST request to edit an existing subject. - * Only admins are allowed to perform this action. - * - * @param request The parsed POST request containing the subject edit data. - * @throws IOException If an I/O error occurs while reading or writing. - */ - private PostResponse handleEditSubject(PostRequest request) throws IOException { - // Test if current user is admin - User user = Server.getInstance().getWebServer().getSessionManager().getSessionUser(request); - if (user == null || !user.isAdmin()) { - return PostResponse.unauthorized("Not logged in or invalid session", request); - } - int contentLength = request.getContentLength(); - if (contentLength <= 0) { - return PostResponse.badRequest("Missing or invalid Content-Length!", request); - } - Map data = request.getFormData(); - int subjectId; - try { - subjectId = Integer.parseInt(data.get("id")); - } catch (NumberFormatException | NullPointerException e) { - return PostResponse.badRequest("Invalid or missing subjectId.", request); - } - String name = prepare(data.get("name")); - - Subject subject = Subject.get(subjectId); - if (subject == null) { - return PostResponse.notFound("Subject not found", request); - } - - try { - subject.edit(name); - return PostResponse.redirect("/manage_subjects", request); - } catch (java.sql.SQLException e) { - return PostResponse.internalServerError("Database error: " + e.getMessage(), request); - } catch (IllegalArgumentException e) { - return PostResponse.badRequest("Invalid input: " + e.getMessage(), request); - } - } - - /** - * Handles a POST request to delete an existing subject. - * Only admins are allowed to perform this action. - * - * @param request The parsed POST request containing the subject delete data. - * @throws IOException If an I/O error occurs while reading or writing. - */ - private PostResponse handleDeleteSubject(PostRequest request) throws IOException { - // Test if current user is admin - User user = Server.getInstance().getWebServer().getSessionManager().getSessionUser(request); - if (user == null || !user.isAdmin()) { - return PostResponse.unauthorized("Not logged in or invalid session", request); - } - int contentLength = request.getContentLength(); - if (contentLength <= 0) { - return PostResponse.badRequest("Missing or invalid Content-Length!", request); - } - Map data = request.getJson(); - int subjectId; - try { - subjectId = ((Number)data.get("id")).intValue(); - } catch (NumberFormatException | NullPointerException e) { - return PostResponse.badRequest("Invalid or missing subjectId.", request); - } - - Subject subject = Subject.get(subjectId); - if (subject == null) { - return PostResponse.notFound("Subject not found", request); - } - - try { - subject.delete(); - return PostResponse.redirect("/manage_subjects", request); - } catch (java.sql.SQLException e) { - return PostResponse.internalServerError("Database error: " + e.getMessage(), request); - } - } - /** - * Handles a POST request to retrieve the list of topics for a given subject. - * Only admins are allowed to perform this action. - * - * @param request The parsed POST request. - * @return PostResponse containing the topic list or an error. - * @throws IOException If an I/O error occurs while reading or writing. - */ - private PostResponse handleTopicList(PostRequest request) throws IOException { - // Test if current user is admin - User user = Server.getInstance().getWebServer().getSessionManager().getSessionUser(request); - if (user == null || !user.isAdmin()) { - return PostResponse.unauthorized("Not logged in or invalid session", request); - } - - Map json = request.getJson(); - if (!json.containsKey("subjectId")) { - return PostResponse.badRequest("Missing subjectId in request.", request); - } - - int subjectId = ((Number) json.get("subjectId")).intValue(); - Subject subject = Subject.get(subjectId); - if (subject == null) { - return PostResponse.notFound("Subject not found.", request); - } - - int grade = ((Number) json.get("grade")).intValue(); - - java.util.List topics = subject.getTopics(grade); - StringBuilder responseBuilder = new StringBuilder("["); - for (int i = 0; i < topics.size(); i++) { - Topic topic = topics.get(i); - responseBuilder.append(topic.toString()); - if (i < topics.size() - 1) { - responseBuilder.append(","); - } - } - responseBuilder.append("]"); - return PostResponse.ok(responseBuilder.toString(), ContentType.JSON, request); - } - /** - * Handles a POST request to retrieve the list of grades. - * Only admins are allowed to perform this action. - * - * @param request The parsed POST request. - * @return PostResponse containing the grade list or an error. - * @throws IOException If an I/O error occurs while reading or writing. - */ - private PostResponse handleGradeList(PostRequest request) throws IOException { - // Test if current user is admin - User user = Server.getInstance().getWebServer().getSessionManager().getSessionUser(request); - if (user == null || !user.isAdmin()) { - return PostResponse.unauthorized("Not logged in or invalid session", request); - } - - Subject subject = Subject.get(((Number)request.getJson().get("subjectId")).intValue()); - - int[] grades = subject.getGrades(); - - StringBuilder responseBuilder = new StringBuilder("["); - for (int i = 0; i < grades.length; i++) { - responseBuilder.append("\"").append(grades[i]).append("\""); - if (i < grades.length - 1) { - responseBuilder.append(","); - } - } - responseBuilder.append("]"); - return PostResponse.ok(responseBuilder.toString(), ContentType.JSON, request); - } - /** - * Handles a POST request to add a grade to a subject. - * Only admins are allowed to perform this action. - * - * @param request The parsed POST request containing the subject and grade data. - * @throws IOException If an I/O error occurs while reading or writing. - */ - private PostResponse handleAddGradeToSubject(PostRequest request) throws IOException { - // Test if current user is admin - User user = Server.getInstance().getWebServer().getSessionManager().getSessionUser(request); - if (user == null || !user.isAdmin()) { - return PostResponse.unauthorized("Not logged in or invalid session", request); - } - int contentLength = request.getContentLength(); - if (contentLength <= 0) { - return PostResponse.badRequest("Missing or invalid Content-Length!", request); - } - Map data = request.getFormData(); - int subjectId; - int grade; - try { - subjectId = Integer.parseInt(data.get("subjectId")); - grade = Integer.parseInt(data.get("grade")); - } catch (NumberFormatException | NullPointerException e) { - return PostResponse.badRequest("Invalid or missing subjectId or grade.", request); - } - - Subject subject = Subject.get(subjectId); - if (subject == null) { - return PostResponse.notFound("Subject not found", request); - } - - try { - subject.addToGrade(grade); - return PostResponse.redirect("/subject", request); - } catch (java.sql.SQLException e) { - return PostResponse.internalServerError("Database error: " + e.getMessage(), request); - } catch (IllegalArgumentException e) { - return PostResponse.badRequest("Invalid input: " + e.getMessage(), request); - } - } - - /** - * Handles a POST request to delete a grade from a subject. - * Only admins are allowed to perform this action. - * - * @param request The parsed POST request containing the subject and grade data. - * @throws IOException If an I/O error occurs while reading or writing. - */ - private PostResponse handleDeleteGradeFromSubject(PostRequest request) throws IOException { - // Test if current user is admin - User user = Server.getInstance().getWebServer().getSessionManager().getSessionUser(request); - if (user == null || !user.isAdmin()) { - return PostResponse.unauthorized("Not logged in or invalid session", request); - } - int contentLength = request.getContentLength(); - if (contentLength <= 0) { - return PostResponse.badRequest("Missing or invalid Content-Length!", request); - } - Map data = request.getFormData(); - int subjectId; - int grade; - try { - subjectId = Integer.parseInt(data.get("subject")); - grade = Integer.parseInt(data.get("grade")); - } catch (NumberFormatException | NullPointerException e) { - return PostResponse.badRequest("Invalid or missing subjectId or grade.", request); - } - - Subject subject = Subject.get(subjectId); - if (subject == null) { - return PostResponse.notFound("Subject not found", request); - } - - try { - subject.removeFromGrade(grade); - return PostResponse.redirect("/subject", request); - } catch (java.sql.SQLException e) { - return PostResponse.internalServerError("Database error: " + e.getMessage(), request); - } catch (IllegalArgumentException e) { - return PostResponse.badRequest("Invalid input: " + e.getMessage(), request); - } - } - /** - * Handles a POST request to add a class to a teacher. - * Only admins are allowed to perform this action. - * - * @param request The parsed POST request containing the teacher and class data. - * @throws IOException If an I/O error occurs while reading or writing. - */ - private PostResponse addClassToTeacher(PostRequest request) throws IOException { - // Test if current user is admin - User user = Server.getInstance().getWebServer().getSessionManager().getSessionUser(request); - if (user == null || !user.isAdmin()) { - return PostResponse.unauthorized("Not logged in or invalid session", request); - } - int contentLength = request.getContentLength(); - if (contentLength <= 0) { - return PostResponse.badRequest("Missing or invalid Content-Length!", request); - } - Map data = request.getFormData(); - int teacherId; - int classId; - try { - teacherId = Integer.parseInt(data.get("teacherId")); - classId = Integer.parseInt(data.get("class")); - } catch (NumberFormatException | NullPointerException e) { - return PostResponse.badRequest("Invalid or missing teacherId or classId.", request); - } - - Teacher teacher = Teacher.get(teacherId); - SchoolClass schoolClass = SchoolClass.get(classId); - if (teacher == null || schoolClass == null) { - return PostResponse.notFound("Teacher or class not found", request); - } - - try { - teacher.addClass(schoolClass); - return PostResponse.redirect("/teacher", request); - } catch (java.sql.SQLException e) { - return PostResponse.internalServerError("Database error: " + e.getMessage(), request); - } - } - private PostResponse handleLPTFile(PostRequest request) { - // Test if current user is admin - User user = Server.getInstance().getWebServer().getSessionManager().getSessionUser(request); - if (user == null || !user.isAdmin()) { - return PostResponse.unauthorized("Not logged in or invalid session", request); - } - int contentLength = request.getContentLength(); - if (contentLength <= 0) { - return PostResponse.badRequest("Missing or invalid Content-Length!", request); - } - - String file = prepare(request.getBodyAsString().replaceFirst("file=", "").replace("Â", "")); - try { - Application.getInstance().readFile(file); - } catch (SerializationException e) { - if (e.getCause() == null) - return PostResponse.badRequest("File is malformed (" + e + ")", request); - else - return PostResponse.badRequest("File is malformed(" + e + ", caused by " + e.getCause() + ")", request); - } catch (SQLException e) { - return PostResponse.internalServerError("Server sql database access failed.", request); - } - - return PostResponse.ok("File data stored", ContentType.TEXT_PLAIN, request); - } - /** - * Handles a POST request to delete topics from a subject. - * Only admins are allowed to perform this action. - * - * @param request The parsed POST request containing the subject and topic IDs. - * @throws IOException If an I/O error occurs while reading or writing. - */ - private PostResponse handleDeleteTopics(PostRequest request) throws IOException { - // Test if current user is admin - User user = Server.getInstance().getWebServer().getSessionManager().getSessionUser(request); - if (user == null || !user.isAdmin()) { - return PostResponse.unauthorized("Not logged in or invalid session", request); - } - int contentLength = request.getContentLength(); - if (contentLength <= 0) { - return PostResponse.badRequest("Missing or invalid Content-Length!", request); - } - Map json = request.getFormData(); - if (!json.containsKey("subjectId")) { - return PostResponse.badRequest("Missing subjectId or topicIds in request.", request); - } - int subjectId = Integer.parseInt(json.get("subjectId")); - Subject subject = Subject.get(subjectId); - if (subject == null) { - return PostResponse.notFound("Subject not found", request); - } - int grade = Integer.parseInt(json.get("grade")); - try { - subject.getTopics(grade).forEach((topic) -> { + }); + HttpHandler.registerPostRequestHandler("/current-topic", AccessLevel.USER, (rq) -> { + Student student = rq.getCurrentStudent(); + Subject subject = rq.getSubject(); + if (student == null) return PostResponse.unauthorized(rq); + Topic topic = student.getCurrentTopic(subject); + if (topic == null) return PostResponse.badRequest("No current topic for this subject.", rq); + return PostResponse.ok(topic.toJSON(), ContentType.JSON, rq); + }); + HttpHandler.registerPostRequestHandler("/change-current-topic", AccessLevel.TEACHER, (rq) -> { + Student student = rq.getCurrentStudent(); + if (student == null) return PostResponse.unauthorized(rq); + Subject subject = rq.getSubject(); + Topic topic = rq.getTopic(); + if (subject == null || topic == null) return PostResponse.badRequest("Subject or topic id not found", rq); + student.setCurrentTopic(subject, topic); + return PostResponse.ok("Current topic changed successfully", ContentType.TEXT_PLAIN, rq); + }); + HttpHandler.registerPostRequestHandler("/tasks", AccessLevel.USER, (rq) -> { + return PostResponse.ok(JSONUtils.toJSON(rq.getTaskList()), ContentType.JSON, rq); + }); + HttpHandler.registerPostRequestHandler("/update-room", AccessLevel.USER, (rq) -> { + Student student = rq.getCurrentStudent(); + if (student == null) return PostResponse.unauthorized(rq); + Room room = rq.getRoom(); + if (room == null) return PostResponse.badRequest("Room not found", rq); + student.setCurrentRoom(room); + return PostResponse.ok("Changed current room", ContentType.TEXT_PLAIN, rq); + }); + HttpHandler.registerPostRequestHandler("/begin-task", AccessLevel.USER, (rq) -> handleTaskChange(rq, Task.STATUS_IN_PROGRESS)); + HttpHandler.registerPostRequestHandler("/complete-task", AccessLevel.USER, (rq) -> handleTaskChange(rq, Task.STATUS_COMPLETED)); + HttpHandler.registerPostRequestHandler("/cancel-task", AccessLevel.USER, (rq) -> handleTaskChange(rq, Task.STATUS_NOT_STARTED)); + HttpHandler.registerPostRequestHandler("/reopen-task", AccessLevel.USER, (rq) -> handleTaskChange(rq, Task.STATUS_NOT_STARTED)); + HttpHandler.registerPostRequestHandler("/lock-task", AccessLevel.USER, (rq) -> handleTaskChange(rq, Task.STATUS_LOCKED)); + HttpHandler.registerPostRequestHandler("/student-data", AccessLevel.TEACHER, PostRequestHandler::handleStudentGetData); + HttpHandler.registerPostRequestHandler("/rooms", AccessLevel.TEACHER, PostRequestHandler::handleStudentGetData); + HttpHandler.registerPostRequestHandler("/student-subjects", AccessLevel.TEACHER, PostRequestHandler::handleStudentGetData); + HttpHandler.registerPostRequestHandler("/teacher-classes", AccessLevel.ADMIN, PostRequestHandler::handleTeacherGetData); + HttpHandler.registerPostRequestHandler("/teacher-subjects", AccessLevel.ADMIN, PostRequestHandler::handleTeacherGetData); + HttpHandler.registerPostRequestHandler("/student-list", AccessLevel.TEACHER, (rq) -> { + SchoolClass schoolClass = rq.getSchoolClass(); + if (schoolClass == null) return PostResponse.notFound("School class not found", rq); + if (rq.getUser().isTeacher() && !rq.getUser().asTeacher().getClassIds().contains(schoolClass.getId())) + return PostResponse.forbidden("You are not allowed to access this class's student list.", rq); + List students = schoolClass.getStudents(); + return PostResponse.ok( + JSONUtils.toJSON(students, (student, builder) -> { + builder + .addProperty("id", student.getId()) + .addProperty("name", student.getFirstName() + " " + student.getLastName()) + .addProperty("actionRequired", student.isActionRequired()) + .addProperty("graduationLevel", student.getGraduationLevel()) + .addProperty("room", student.getCurrentRoom() != null ? student.getCurrentRoom().getLabel() : "None"); + if (rq.getJson().containsKey("subjectId") && rq.getSubject() != null) { + Set subjectRequests = student.getCurrentRequests(rq.getSubject()); + builder.addProperty("experiment",subjectRequests.stream().anyMatch(r -> r == SubjectRequest.EXPERIMENT)) + .addProperty("help", subjectRequests.stream().anyMatch(r -> r == SubjectRequest.HELP)) + .addProperty("test", subjectRequests.stream().anyMatch(r -> r == SubjectRequest.EXAM)) + .addProperty("partner", subjectRequests.stream().anyMatch(r -> r == SubjectRequest.PARTNER)); + } + }), + ContentType.JSON, rq + ); + }); + HttpHandler.registerPostRequestHandler("/get-students-by-room", AccessLevel.TEACHER, (rq) -> { + Room room = rq.getRoom(); + List students = Student.getByRoom(room); + return PostResponse.ok( + JSONUtils.toJSON(students, (student, builder) -> { + builder + .addProperty("id", student.getId()) + .addProperty("name", student.getFirstName() + " " + student.getLastName()) + .addProperty("actionRequired", student.isActionRequired()) + .addProperty("graduationLevel", student.getGraduationLevel()) + .addProperty("room", student.getCurrentRoom() != null ? student.getCurrentRoom().getLabel() : "None"); + if (rq.getJson().containsKey("subjectId") && rq.getSubject() != null) { + Set subjectRequests = student.getCurrentRequests(rq.getSubject()); + builder.addProperty("experiment",subjectRequests.stream().anyMatch(r -> r == SubjectRequest.EXPERIMENT)) + .addProperty("help", subjectRequests.stream().anyMatch(r -> r == SubjectRequest.HELP)) + .addProperty("test", subjectRequests.stream().anyMatch(r -> r == SubjectRequest.EXAM)) + .addProperty("partner", subjectRequests.stream().anyMatch(r -> r == SubjectRequest.PARTNER)); + } + }), + ContentType.JSON, rq + ); + }); + HttpHandler.registerPostRequestHandler("/grade-list", AccessLevel.PUBLIC, (rq) -> { + return PostResponse.ok(JSONUtils.toJSON(rq.getSubject().getGrades()), ContentType.JSON, rq); + }); + HttpHandler.registerPostRequestHandler("/topic-list", AccessLevel.ADMIN, (rq) -> { + return PostResponse.ok(JSONUtils.toJSON(rq.getSubject().getTopics(rq.getInt("grade"))), ContentType.JSON, rq); + }); + HttpHandler.registerPostRequestHandler("/class-subjects", AccessLevel.ADMIN, (rq) -> { + SchoolClass schoolClass = rq.getSchoolClass(); + if (schoolClass == null) return PostResponse.notFound("School class not found", rq); + List subjects = schoolClass.getSubjects(); + return PostResponse.ok(JSONUtils.toJSON(subjects, (subject, builder) -> { + builder.addProperty("id", subject.getId()).addProperty("name", subject.getName()); + }), ContentType.JSON, rq); + }); + HttpHandler.registerPostRequestHandler("/search-partner", AccessLevel.USER, (rq) -> { + SchoolClass schoolClass = rq.getSchoolClass(); + Subject subject = rq.getSubject(); + Topic topic = rq.getTopic(); + Student student = rq.getCurrentStudent(); + + List students = Student.getAll().stream() + .filter((s) -> s.getSchoolClass().getGrade() == schoolClass.getGrade()) + .filter((s) -> s.getCurrentTopic(subject).equals(topic) + && s.getSelectedTasks().stream().filter((t) -> t.getTopic().equals(topic)).anyMatch((t) -> student.getSelectedTasks().contains(t)) + && s.getCurrentRequests(subject).stream().anyMatch((r) -> r == SubjectRequest.PARTNER)) + .toList(); + return PostResponse.ok(JSONUtils.toJSON(students, (partner, builder) -> { + builder.addProperty("id", partner.getId()) + .addProperty("name", partner.getFirstName() + " " + partner.getLastName()) + .addProperty("room", partner.getCurrentRoom() != null ? partner.getCurrentRoom().getLabel() : "None"); + }), ContentType.JSON, rq); + }); + HttpHandler.registerPostRequestHandler("/delete-subject", AccessLevel.ADMIN, (rq) -> + handleObjectAction(rq, new TypeToken() {}, PostResponse.redirect("/manage_subjects", rq), (subject) -> subject.delete()) + ); + HttpHandler.registerPostRequestHandler("/edit-subject", AccessLevel.ADMIN, (rq) -> + handleObjectAction(rq, new TypeToken() {}, PostResponse.redirect("/manage_subjects", rq), (subject) -> subject.edit(prepare(rq.getString("name")))) + ); + HttpHandler.registerPostRequestHandler("/delete-classs", AccessLevel.ADMIN, (rq) -> + handleObjectAction(rq, new TypeToken() {}, PostResponse.redirect("/manage_classes", rq), (schoolClass) -> schoolClass.delete()) + ); + HttpHandler.registerPostRequestHandler("/edit-class", AccessLevel.ADMIN, (rq) -> + handleObjectAction(rq, new TypeToken() {}, PostResponse.redirect("/manage_classes", rq), (schoolClass) -> schoolClass.edit(prepare(rq.getString("name")), rq.getInt("grade"))) + ); + HttpHandler.registerPostRequestHandler("/add-subject-to-class", AccessLevel.ADMIN, (rq) -> + handleObjectAction(rq, new TypeToken() {}, PostResponse.redirect("/class", rq), (schoolClass) -> schoolClass.addSubject(rq.getSubject())) + ); + HttpHandler.registerPostRequestHandler("/add-grade-to-subject", AccessLevel.ADMIN, (rq) -> + handleObjectAction(rq, new TypeToken() {}, PostResponse.redirect("/subject", rq), (subject) -> subject.addToGrade(rq.getInt("grade"))) + ); + HttpHandler.registerPostRequestHandler("/delete-grade-from-subject", AccessLevel.ADMIN, (rq) -> + handleObjectAction(rq, new TypeToken() {}, PostResponse.redirect("/subject", rq), (subject) -> subject.removeFromGrade(rq.getInt("grade"))) + ); + HttpHandler.registerPostRequestHandler("/delete-topics", AccessLevel.ADMIN, (rq) -> + handleObjectAction(rq, new TypeToken() {}, PostResponse.redirect("/subject", rq), (subject) -> subject.getTopics(rq.getInt("grade")).forEach((topic) -> { try { topic.delete(); } catch (SQLException e) { throw new IllegalStateException(e); } - }); - return PostResponse.redirect("/subject", request); - } catch (IllegalStateException e) { - return PostResponse.internalServerError("Database error: " + e.getCause().getMessage(), request); - } - } - private PostResponse handleChangeGraduationLevel(PostRequest request) throws IOException { - // Test if current user is admin - User user = Server.getInstance().getWebServer().getSessionManager().getSessionUser(request); - if (user == null || !user.isAdmin()) { - return PostResponse.unauthorized("Not logged in or invalid session", request); - } - int contentLength = request.getContentLength(); - if (contentLength <= 0) { - return PostResponse.badRequest("Missing or invalid Content-Length!", request); - } - Map data = request.getJson(); - Student student = getCurrentStudent(request); - int graduationLevel; - try { - graduationLevel = ((Number)data.get("graduationLevel")).intValue(); - } catch (NumberFormatException | NullPointerException e) { - return PostResponse.badRequest("Invalid or missing subjectId or grade.", request); - } - - try { - student.changeGraduationLevel(graduationLevel); - return PostResponse.ok("Changed graduation level", ContentType.TEXT_PLAIN, request); - } catch (java.sql.SQLException e) { - return PostResponse.internalServerError("Database error: " + e.getMessage(), request); - } catch (IllegalArgumentException e) { - return PostResponse.badRequest("Invalid input: " + e.getMessage(), request); - } - } - private PostResponse handleGetStudentsByRoom(PostRequest request) { - // Test if current user is admin or teacher - User user = Server.getInstance().getWebServer().getSessionManager().getSessionUser(request); - if (user == null || !(user.isAdmin() || user.isTeacher())) { - return PostResponse.unauthorized("Not logged in or invalid session", request); - } - - Map json = request.getJson(); - Room room = Room.getRoom((String) json.get("room")); - - java.util.List students = Student.getByRoom(room); - StringBuilder responseBuilder = new StringBuilder("["); - for (int i = 0; i < students.size(); i++) { - Student student = students.get(i); - responseBuilder.append("{\"id\":").append(student.getId()) - .append(",\"name\":\"").append(student.getFirstName()).append(" ").append(student.getLastName()).append('"') - .append(", \"actionRequired\":").append(student.isActionRequired()) - .append(", \"graduationLevel\":").append(student.getGraduationLevel().getLevel()) - .append(", \"room\":\"").append(student.getCurrentRoom() != null ? student.getCurrentRoom().getLabel() : "None").append("\""); - if (request.getJson().containsKey("subjectId") && request.getJson().get("subjectId") instanceof Number subjectId) { - Set subjectRequests = student.getCurrentRequests().keySet().contains(subjectId.intValue()) ? student.getCurrentRequests().get(subjectId.intValue()) : Set.of(); - responseBuilder.append(", \"experiment\":").append(subjectRequests.stream().anyMatch(r -> r == SubjectRequest.EXPERIMENT)) - .append(", \"help\":").append(subjectRequests.stream().anyMatch(r -> r == SubjectRequest.HELP)) - .append(", \"test\":").append(subjectRequests.stream().anyMatch(r -> r == SubjectRequest.EXAM)) - .append(", \"partner\":").append(subjectRequests.stream().anyMatch(r -> r == SubjectRequest.PARTNER)); - } - responseBuilder.append("}"); - if (i < students.size() - 1) { - responseBuilder.append(", "); - } - } - responseBuilder.append("]"); - - return PostResponse.ok(responseBuilder.toString(), ContentType.JSON, request); - } - private PostResponse handleSearchPartner(PostRequest request) { - if (Server.getInstance().getWebServer().getSessionManager().getSession(request) == null) return PostResponse.unauthorized("Not logged in or invalid session", request); - - Map json = request.getJson(); - SchoolClass schoolClass = SchoolClass.get(((Number)json.get("classId")).intValue()); - Subject subject = Subject.get(((Number) json.get("subjectId")).intValue()); - Topic topic = Topic.get(((Number) json.get("topicId")).intValue()); - Student student = Student.get(((Number)json.get("studentId")).intValue()); - - List students = Student.getAll().stream() - .filter((s) -> s.getSchoolClass().getGrade() == schoolClass.getGrade()) - .filter((s) -> s.getCurrentTopic(subject).equals(topic) - && s.getSelectedTasks().stream().filter((t) -> t.getTopic().equals(topic)).anyMatch((t) -> student.getSelectedTasks().contains(t)) - && s.getCurrentRequests(subject).stream().anyMatch((r) -> r == SubjectRequest.PARTNER)) - .toList(); - StringBuilder responseBuilder = new StringBuilder("["); - for (int i = 0; i < students.size(); i++) { - Student partner = students.get(i); - responseBuilder.append("{\"id\":").append(partner.getId()) - .append(",\"name\":\"").append(partner.getFirstName()).append(" ").append(partner.getLastName()).append('"') - .append(", \"room\":\"").append(partner.getCurrentRoom() != null ? partner.getCurrentRoom().getLabel() : "None").append("\""); - responseBuilder.append("}"); - if (i < students.size() - 1) { - responseBuilder.append(", "); - } - } - responseBuilder.append("]"); - return PostResponse.ok(responseBuilder.toString(), ContentType.JSON, request); + })) + ); + HttpHandler.registerPostRequestHandler("/add-class-to-teacher", AccessLevel.ADMIN, (rq) -> + handleObjectAction(rq, new TypeToken() {}, PostResponse.redirect("/teacher", rq), (teacher) -> teacher.addClass(rq.getSchoolClass())) + ); + HttpHandler.registerPostRequestHandler("/add-subject-to-teacher", AccessLevel.ADMIN, (rq) -> + handleObjectAction(rq, new TypeToken() {}, PostResponse.redirect("/teacher", rq), (teacher) -> teacher.addSubject(rq.getSubject())) + ); + HttpHandler.registerPostRequestHandler("/change-graduation-level", AccessLevel.ADMIN, (rq) -> + handleObjectAction(rq, new TypeToken() {}, PostResponse.ok("Successfully changed graduation level", ContentType.TEXT_PLAIN, rq), (student) -> student.changeGraduationLevel(rq.getInt("graduationLevel"))) + ); } } \ No newline at end of file diff --git a/src/main/java/de/igslandstuhl/database/server/webserver/Session.java b/src/main/java/de/igslandstuhl/database/server/webserver/Session.java index f2bafc7..a64cf6d 100644 --- a/src/main/java/de/igslandstuhl/database/server/webserver/Session.java +++ b/src/main/java/de/igslandstuhl/database/server/webserver/Session.java @@ -5,6 +5,8 @@ import java.util.Objects; import java.util.UUID; +import de.igslandstuhl.database.server.webserver.requests.HttpRequest; + public class Session { private final UUID uuid = UUID.randomUUID(); diff --git a/src/main/java/de/igslandstuhl/database/server/webserver/SessionManager.java b/src/main/java/de/igslandstuhl/database/server/webserver/SessionManager.java index 400ee22..50dcb1c 100644 --- a/src/main/java/de/igslandstuhl/database/server/webserver/SessionManager.java +++ b/src/main/java/de/igslandstuhl/database/server/webserver/SessionManager.java @@ -6,6 +6,7 @@ import java.util.UUID; import de.igslandstuhl.database.api.User; +import de.igslandstuhl.database.server.webserver.requests.HttpRequest; public class SessionManager { private Map sessionStore = new HashMap<>(); diff --git a/src/main/java/de/igslandstuhl/database/server/webserver/requests/APIPostRequest.java b/src/main/java/de/igslandstuhl/database/server/webserver/requests/APIPostRequest.java new file mode 100644 index 0000000..dee8fdb --- /dev/null +++ b/src/main/java/de/igslandstuhl/database/server/webserver/requests/APIPostRequest.java @@ -0,0 +1,111 @@ +package de.igslandstuhl.database.server.webserver.requests; + +import java.lang.reflect.Type; +import java.util.LinkedList; +import java.util.List; + +import com.google.gson.reflect.TypeToken; + +import de.igslandstuhl.database.api.APIObject; +import de.igslandstuhl.database.api.Room; +import de.igslandstuhl.database.api.SchoolClass; +import de.igslandstuhl.database.api.Student; +import de.igslandstuhl.database.api.Subject; +import de.igslandstuhl.database.api.SubjectRequest; +import de.igslandstuhl.database.api.Task; +import de.igslandstuhl.database.api.Teacher; +import de.igslandstuhl.database.api.Topic; +import de.igslandstuhl.database.api.User; +import de.igslandstuhl.database.server.Server; +import de.igslandstuhl.database.server.webserver.HttpHeader; + +public class APIPostRequest extends PostRequest { + public APIPostRequest(HttpHeader header, String body, String ipAddress, boolean secureConnection) { + super(header, body, ipAddress, secureConnection); + } + public Student getCurrentStudent() { + User user = Server.getInstance().getWebServer().getSessionManager().getSessionUser(this); + if (user == null || user == User.ANONYMOUS) { + return null; // User is not logged in + } + if (user instanceof Student student) { + return student; + } else if ((user.isTeacher() || user.isAdmin()) && containsKey("studentId")) { + return Student.get(getInt("studentId")); + } + return null; + } + public Teacher getCurrentTeacher() { + User user = Server.getInstance().getWebServer().getSessionManager().getSessionUser(this); + if (user == null || user == User.ANONYMOUS) { + return null; // User is not logged in + } + if (user instanceof Teacher teacher) { + return teacher; + } else if (user.isAdmin() && containsKey("teacherId")) { + return Teacher.get(getInt("teacherId")); + } + return null; + } + public User getUser() { + return Server.getInstance().getWebServer().getSessionManager().getSessionUser(this); + } + public Subject getSubject() { + return Subject.get(getInt("subjectId")); + } + public Topic getTopic() { + return Topic.get(getInt("topicId")); + } + public SubjectRequest getSubjectRequest() { + return SubjectRequest.fromGermanTranslation(getString("subjectRequest")); + } + public Room getRoom() { + return Room.getRoom(getString("room")); + } + public Task getTask() { + return Task.get(getInt("taskId")); + } + public SchoolClass getSchoolClass() { + return SchoolClass.get(getInt("classId")); + } + @SuppressWarnings("unchecked") + public T getAPIObject(TypeToken type) { + Type rawType = type.getType(); + if (rawType.getTypeName().contains("User")) { + return (T) getUser(); + } else if (rawType.getTypeName().contains("Student")) { + return (T) getCurrentStudent(); + } else if (rawType.getTypeName().contains("Teacher")) { + return (T) getCurrentTeacher(); + } else if (rawType.getTypeName().contains("Admin")) { + return (T) getUser().asAdmin(); + } else if (rawType.getTypeName().contains("Subject")) { + return (T) getSubject(); + } else if (rawType.getTypeName().contains("Topic")) { + return (T) getTopic(); + } else if (rawType.getTypeName().contains("SubjectRequest")) { + return (T) getSubjectRequest(); + } else if (rawType.getTypeName().contains("Task")) { + return (T) getTask(); + } else if (rawType.getTypeName().contains("Room")) { + return (T) getRoom(); + } else if (rawType.getTypeName().contains("SchoolClass")) { + return (T) getSchoolClass(); + } else { + return null; + } + } + public List getTaskList() { + List ids = new LinkedList<>(); + for (Object o : getList("ids")) { + if (o instanceof Number n) { + ids.add(n.intValue()); + } + } + return Task.getTasksByIds(ids); + } + public static APIPostRequest fromPostRequest(PostRequest request) { + if (request instanceof APIPostRequest rq) return rq; + else return new APIPostRequest(request.getHeader(), request.getBodyAsString(), request.getIP(), request.isSecureConnection()); + } +} diff --git a/src/main/java/de/igslandstuhl/database/server/webserver/GetRequest.java b/src/main/java/de/igslandstuhl/database/server/webserver/requests/GetRequest.java similarity index 94% rename from src/main/java/de/igslandstuhl/database/server/webserver/GetRequest.java rename to src/main/java/de/igslandstuhl/database/server/webserver/requests/GetRequest.java index 75f8228..bade7e1 100644 --- a/src/main/java/de/igslandstuhl/database/server/webserver/GetRequest.java +++ b/src/main/java/de/igslandstuhl/database/server/webserver/requests/GetRequest.java @@ -1,4 +1,4 @@ -package de.igslandstuhl.database.server.webserver; +package de.igslandstuhl.database.server.webserver.requests; import java.util.Arrays; import java.util.HashMap; @@ -6,6 +6,9 @@ import de.igslandstuhl.database.api.User; import de.igslandstuhl.database.server.resources.ResourceLocation; +import de.igslandstuhl.database.server.webserver.Cookie; +import de.igslandstuhl.database.server.webserver.HttpHeader; +import de.igslandstuhl.database.server.webserver.WebResourceHandler; /** * Represents a GET request in the web server. diff --git a/src/main/java/de/igslandstuhl/database/server/webserver/requests/HttpHandler.java b/src/main/java/de/igslandstuhl/database/server/webserver/requests/HttpHandler.java new file mode 100644 index 0000000..1f46ace --- /dev/null +++ b/src/main/java/de/igslandstuhl/database/server/webserver/requests/HttpHandler.java @@ -0,0 +1,49 @@ +package de.igslandstuhl.database.server.webserver.requests; + +import de.igslandstuhl.database.Registry; +import de.igslandstuhl.database.server.Server; +import de.igslandstuhl.database.server.webserver.AccessLevel; +import de.igslandstuhl.database.server.webserver.SessionManager; +import de.igslandstuhl.database.server.webserver.Status; +import de.igslandstuhl.database.server.webserver.responses.HttpResponse; +import de.igslandstuhl.database.utils.ThrowingFunction; + +public class HttpHandler { + private final String path; + private final AccessLevel accessLevel; + private final ThrowingFunction handler; + + private HttpHandler(String path, AccessLevel accessLevel, ThrowingFunction handler) { + this.accessLevel = accessLevel; + this.handler = handler; + this.path = path; + } + + public HttpResponse handleHttpRequest(Rq request) { + SessionManager sessionManager = Server.getInstance().getWebServer().getSessionManager(); + int contentLength = request.getContentLength(); + if (contentLength <= 0 && !(request instanceof GetRequest)) { + return HttpResponse.error(request, Status.BAD_REQUEST); + } + if (!accessLevel.hasAccess(sessionManager.getSessionUser(request))) { + return HttpResponse.error(request, Status.UNAUTHORIZED); + } else if (!path.equals(request.getPath().split("\\?")[0])) { + System.err.println("Wrong path for HTTP handler: " + handler + ", path: " + request.getPath()); + return HttpResponse.error(request, Status.INTERNAL_SERVER_ERROR); + } else { + try { + return handler.apply(request); + } catch (Throwable t) { + t.printStackTrace(); + return HttpResponse.error(request, Status.INTERNAL_SERVER_ERROR); + } + } + } + + public static void registerPostRequestHandler(String path, AccessLevel accessLevel, ThrowingFunction handler) { + Registry.postRequestHandlerRegistry().register(path, new HttpHandler<>(path, accessLevel, handler)); + } + public static void registerGetRequestHandler(String path, AccessLevel accessLevel, ThrowingFunction handler) { + Registry.getRequestHandlerRegistry().register(path, new HttpHandler<>(path, accessLevel, handler)); + } +} diff --git a/src/main/java/de/igslandstuhl/database/server/webserver/HttpRequest.java b/src/main/java/de/igslandstuhl/database/server/webserver/requests/HttpRequest.java similarity index 89% rename from src/main/java/de/igslandstuhl/database/server/webserver/HttpRequest.java rename to src/main/java/de/igslandstuhl/database/server/webserver/requests/HttpRequest.java index 1914f58..01658b3 100644 --- a/src/main/java/de/igslandstuhl/database/server/webserver/HttpRequest.java +++ b/src/main/java/de/igslandstuhl/database/server/webserver/requests/HttpRequest.java @@ -1,4 +1,6 @@ -package de.igslandstuhl.database.server.webserver; +package de.igslandstuhl.database.server.webserver.requests; + +import de.igslandstuhl.database.server.webserver.Cookie; public interface HttpRequest { /** diff --git a/src/main/java/de/igslandstuhl/database/server/webserver/PostRequest.java b/src/main/java/de/igslandstuhl/database/server/webserver/requests/PostRequest.java similarity index 72% rename from src/main/java/de/igslandstuhl/database/server/webserver/PostRequest.java rename to src/main/java/de/igslandstuhl/database/server/webserver/requests/PostRequest.java index 6103793..5542af7 100644 --- a/src/main/java/de/igslandstuhl/database/server/webserver/PostRequest.java +++ b/src/main/java/de/igslandstuhl/database/server/webserver/requests/PostRequest.java @@ -1,13 +1,17 @@ -package de.igslandstuhl.database.server.webserver; +package de.igslandstuhl.database.server.webserver.requests; import java.util.HashMap; +import java.util.List; import java.util.Map; import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; import com.google.gson.reflect.TypeToken; import de.igslandstuhl.database.api.User; import de.igslandstuhl.database.server.resources.ResourceLocation; +import de.igslandstuhl.database.server.webserver.Cookie; +import de.igslandstuhl.database.server.webserver.HttpHeader; /** * Represents a POST request in the web server. @@ -45,6 +49,7 @@ public class PostRequest implements HttpRequest { private final String userAgent; private final String acceptLanguage; private final boolean secureConnection; + private final HttpHeader header; /** * Constructs a new PostRequest with the given header and body. @@ -68,6 +73,7 @@ public PostRequest(HttpHeader header, String body, String ipAddress, boolean sec this.userAgent = header.getUserAgent(); this.acceptLanguage = header.getAcceptLanguage(); this.secureConnection = secureConnection; + this.header = header; String[] extPts = path.split("\\."); if (extPts.length > 1) { @@ -109,6 +115,9 @@ public String getAcceptLanguage() { public boolean isSecureConnection() { return secureConnection; } + public HttpHeader getHeader() { + return header; + } public Map getFormData() { if (body == null || body.isEmpty()) return Map.of(); @@ -138,6 +147,50 @@ public Map getJson() { return json; } + public int getInt(String key) { + try { + Map json = getJson(); + return ((Number)json.get(key)).intValue(); + } catch (JsonSyntaxException e) { + Map data = getFormData(); + return Integer.parseInt(data.get(key)); + } + } + public String getString(String key) { + try { + Map json = getJson(); + return (String)json.get(key); + } catch (JsonSyntaxException e) { + Map data = getFormData(); + return data.get(key); + } + } + public boolean getBoolean(String key) { + try { + Map json = getJson(); + return json.containsKey(key) && (boolean) json.get(key); + } catch (JsonSyntaxException e) { + Map data = getFormData(); + return Boolean.parseBoolean(data.get(key)); + } + } + public List getList(String key) { + return (List) getJson().get(key); + } + public boolean containsKey(String key) { + try { + Map json = getJson(); + return json.containsKey(key); + } catch (JsonSyntaxException e) { + try { + Map data = getFormData(); + return data.containsKey(key); + } catch (IllegalArgumentException e2) { + return false; + } + } + } + /** * Converts the path of the POST request to a ResourceLocation. * This method is used to create a ResourceLocation object from the path, diff --git a/src/main/java/de/igslandstuhl/database/server/webserver/GetResponse.java b/src/main/java/de/igslandstuhl/database/server/webserver/responses/GetResponse.java similarity index 90% rename from src/main/java/de/igslandstuhl/database/server/webserver/GetResponse.java rename to src/main/java/de/igslandstuhl/database/server/webserver/responses/GetResponse.java index a895ec9..0c3939a 100644 --- a/src/main/java/de/igslandstuhl/database/server/webserver/GetResponse.java +++ b/src/main/java/de/igslandstuhl/database/server/webserver/responses/GetResponse.java @@ -1,4 +1,4 @@ -package de.igslandstuhl.database.server.webserver; +package de.igslandstuhl.database.server.webserver.responses; import java.io.FileNotFoundException; import java.io.InputStream; @@ -7,11 +7,16 @@ import de.igslandstuhl.database.server.Server; import de.igslandstuhl.database.server.resources.ResourceHelper; import de.igslandstuhl.database.server.resources.ResourceLocation; +import de.igslandstuhl.database.server.webserver.AccessManager; +import de.igslandstuhl.database.server.webserver.ContentType; +import de.igslandstuhl.database.server.webserver.NoWebResourceException; +import de.igslandstuhl.database.server.webserver.Status; +import de.igslandstuhl.database.server.webserver.requests.HttpRequest; /** * Represents a response to a GET request in the web server. */ -public class GetResponse { +public class GetResponse implements HttpResponse { /** * Returns a response for a GET request that was not found. * @return the GetResponse object @@ -154,4 +159,16 @@ public String getResponseBody() throws FileNotFoundException { } return ""; } + @Override + public Status getStatus() { + return status; + } + @Override + public ContentType getContentType() { + return contentType; + } + @Override + public HttpRequest getHttpRequest() { + return request; + } } diff --git a/src/main/java/de/igslandstuhl/database/server/webserver/responses/HttpResponse.java b/src/main/java/de/igslandstuhl/database/server/webserver/responses/HttpResponse.java new file mode 100644 index 0000000..94612c7 --- /dev/null +++ b/src/main/java/de/igslandstuhl/database/server/webserver/responses/HttpResponse.java @@ -0,0 +1,97 @@ +package de.igslandstuhl.database.server.webserver.responses; + +import java.io.PrintStream; + +import de.igslandstuhl.database.server.Server; +import de.igslandstuhl.database.server.resources.ResourceHelper; +import de.igslandstuhl.database.server.resources.ResourceLocation; +import de.igslandstuhl.database.server.webserver.ContentType; +import de.igslandstuhl.database.server.webserver.Status; +import de.igslandstuhl.database.server.webserver.requests.HttpRequest; + +public interface HttpResponse { + public Status getStatus(); + public HttpRequest getHttpRequest(); + public ContentType getContentType(); + public void respond(PrintStream out); + + public static HttpResponse error(HttpRequest request, Status errorStatus) { + return new HttpResponse() { + @Override + public Status getStatus() { + return errorStatus; + } + @Override + public HttpRequest getHttpRequest() { + return request; + } + @Override + public void respond(PrintStream out) { + out.print("HTTP/1.1 ");errorStatus.write(out);out.println(); + out.print("Content-Type: text/html"); + out.print("; charset=UTF8"); + out.println(); + out.println("Set-Cookie: " + Server.getInstance().getWebServer().getSessionManager().getSession(request).createSessionCookie()); + out.println(); // <--- This line is important: seperates Header and Body! + ResourceLocation resourceLocation = new ResourceLocation("html", "errors", errorStatus.getCode() + ".html"); + String resource; + try { + resource = ResourceHelper.readResourceCompletely(resourceLocation); + out.println(resource); + } catch (Exception e) { + if (errorStatus != Status.INTERNAL_SERVER_ERROR) { + resourceLocation = new ResourceLocation("html", "errors", errorStatus.getCode() + ".html"); + try { + resource = ResourceHelper.readResourceCompletely(resourceLocation); + out.println(resource); + } catch (Exception e2) { + throw new IllegalStateException(e); + } + } else { + throw new IllegalStateException(e); + } + } + } + @Override + public ContentType getContentType() { + return ContentType.HTML; + } + + }; + } + public static HttpResponse internalServerError(HttpRequest request, Throwable cause) { + Status errorStatus = Status.INTERNAL_SERVER_ERROR; + return new HttpResponse() { + @Override + public Status getStatus() { + return errorStatus; + } + @Override + public HttpRequest getHttpRequest() { + return request; + } + @Override + public void respond(PrintStream out) { + out.print("HTTP/1.1 ");errorStatus.write(out);out.println(); + out.print("Content-Type: text/html"); + out.print("; charset=UTF8"); + out.println(); + out.println("Set-Cookie: " + Server.getInstance().getWebServer().getSessionManager().getSession(request).createSessionCookie()); + out.println(); // <--- This line is important: seperates Header and Body! + ResourceLocation resourceLocation = new ResourceLocation("html", "errors", errorStatus.getCode() + ".html"); + String resource; + try { + resource = ResourceHelper.readResourceCompletely(resourceLocation); + out.println(resource); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + @Override + public ContentType getContentType() { + return ContentType.HTML; + } + + }; + } +} diff --git a/src/main/java/de/igslandstuhl/database/server/webserver/PostResponse.java b/src/main/java/de/igslandstuhl/database/server/webserver/responses/PostResponse.java similarity index 87% rename from src/main/java/de/igslandstuhl/database/server/webserver/PostResponse.java rename to src/main/java/de/igslandstuhl/database/server/webserver/responses/PostResponse.java index 7594d7e..1fdde16 100644 --- a/src/main/java/de/igslandstuhl/database/server/webserver/PostResponse.java +++ b/src/main/java/de/igslandstuhl/database/server/webserver/responses/PostResponse.java @@ -1,15 +1,24 @@ -package de.igslandstuhl.database.server.webserver; +package de.igslandstuhl.database.server.webserver.responses; import java.io.FileNotFoundException; import java.io.PrintStream; +import com.google.gson.Gson; + import de.igslandstuhl.database.server.Server; import de.igslandstuhl.database.server.resources.ResourceLocation; +import de.igslandstuhl.database.server.webserver.AccessManager; +import de.igslandstuhl.database.server.webserver.ContentType; +import de.igslandstuhl.database.server.webserver.Cookie; +import de.igslandstuhl.database.server.webserver.NoWebResourceException; +import de.igslandstuhl.database.server.webserver.Status; +import de.igslandstuhl.database.server.webserver.requests.HttpRequest; +import de.igslandstuhl.database.server.webserver.requests.PostRequest; /** * Represents a response to a POST request in the web server. */ -public class PostResponse { +public class PostResponse implements HttpResponse { /** * The HTTP status code of this response. * This code indicates the result of processing the POST request. @@ -180,6 +189,14 @@ public static PostResponse badRequest(String message, PostRequest request) { public static PostResponse unauthorized(String message, PostRequest request) { return new PostResponse(Status.UNAUTHORIZED, message, ContentType.TEXT_PLAIN, request); } + /** + * Returns a response indicating that the user is unauthorized to access the requested resource. + * This is used when the user is not authenticated or does not have permission to access the resource. + * @return A PostResponse object representing the unauthorized response. + */ + public static PostResponse unauthorized(PostRequest rq) { + return unauthorized("Not logged in or invalid session", rq); + } /** * Returns a response indicating that the server encountered an internal error while processing the request. @@ -215,4 +232,20 @@ public static PostResponse redirect(String location, PostRequest request) { "Location: " + location }); } + public static PostResponse json(Object json, PostRequest request) { + Gson gson = new Gson(); + return new PostResponse(Status.OK, gson.toJson(json), ContentType.JSON, request); + } + @Override + public Status getStatus() { + return statusCode; + } + @Override + public HttpRequest getHttpRequest() { + return request; + } + @Override + public ContentType getContentType() { + return contentType; + } } \ No newline at end of file diff --git a/src/main/java/de/igslandstuhl/database/utils/JSONUtils.java b/src/main/java/de/igslandstuhl/database/utils/JSONUtils.java new file mode 100644 index 0000000..30a9dc0 --- /dev/null +++ b/src/main/java/de/igslandstuhl/database/utils/JSONUtils.java @@ -0,0 +1,76 @@ +package de.igslandstuhl.database.utils; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Function; + +import com.google.gson.Gson; + +import de.igslandstuhl.database.api.APIObject; + +public class JSONUtils { + public static class JSONBuilder { + private final Map map = new HashMap<>(); + private final Gson gson = new Gson(); + + public JSONBuilder addProperty(String property, Object o) { + map.put(property, gson.toJson(o)); + return this; + } + public JSONBuilder addProperty(String property, Number n) { + map.put(property, n.toString()); + return this; + } + public JSONBuilder addProperty(String property, String s) { + map.put(property, '"' + s + '"'); + return this; + } + public JSONBuilder addProperty(String property, APIObject o) { + map.put(property, o.toJSON()); + return this; + } + public JSONBuilder addProperty(String property, List l) { + map.put(property, toJSON(l)); + return this; + } + public JSONBuilder addProperty(String property, JSONBuilder builder) { + map.put(property, builder.toString()); + return this; + } + @Override + public String toString() { + return "{" + map.entrySet().stream() + .map((e) -> '"' + e.getKey() + "\": " + e.getValue()) + .reduce("", (s1, s2) -> s1 + ",\n " + s2) + .replaceFirst(",", "") + "\n}"; + } + } + public static String toJSON(List list) { + return "[" + list.stream() + .map(APIObject::toJSON) + .reduce("", (s1, s2) -> s1 + ",\n " + s2) + .replaceFirst(",", "") + "\n]"; + } + public static String toJSON(List list, BiConsumer jsonHandler) { + return "[" + list.stream() + .map((t) -> { + JSONBuilder builder = new JSONBuilder(); + jsonHandler.accept(t, builder); + return builder.toString(); + }) + .reduce("", (s1, s2) -> s1 + ",\n " + s2) + .replaceFirst(",", "") + "\n]"; + } + public static String toJON(List list, Function jsonHandler) { + return "[" + list.stream() + .map(jsonHandler) + .reduce("", (s1, s2) -> s1 + ",\n " + s2) + .replaceFirst(",", "") + "\n]"; + } + public static String toJSON(int[] arr) { + return Arrays.toString(arr); + } +} diff --git a/src/main/java/de/igslandstuhl/database/utils/ThrowingConsumer.java b/src/main/java/de/igslandstuhl/database/utils/ThrowingConsumer.java new file mode 100644 index 0000000..149760d --- /dev/null +++ b/src/main/java/de/igslandstuhl/database/utils/ThrowingConsumer.java @@ -0,0 +1,6 @@ +package de.igslandstuhl.database.utils; + +@FunctionalInterface +public interface ThrowingConsumer { + public void accept(T t) throws Exception; +} diff --git a/src/main/java/de/igslandstuhl/database/utils/ThrowingFunction.java b/src/main/java/de/igslandstuhl/database/utils/ThrowingFunction.java new file mode 100644 index 0000000..dda4989 --- /dev/null +++ b/src/main/java/de/igslandstuhl/database/utils/ThrowingFunction.java @@ -0,0 +1,6 @@ +package de.igslandstuhl.database.utils; + +@FunctionalInterface +public interface ThrowingFunction { + public V apply(T input) throws Exception; +} diff --git a/src/main/resources/html/admin/class.html b/src/main/resources/html/admin/class.html index bd1e620..76506fc 100644 --- a/src/main/resources/html/admin/class.html +++ b/src/main/resources/html/admin/class.html @@ -16,7 +16,7 @@

Klasse bearbeiten

Daten bearbeiten

- + @@ -38,7 +38,7 @@

Fach hinzufügen

- +
diff --git a/src/main/resources/html/admin/teacher.html b/src/main/resources/html/admin/teacher.html index 3cab065..8373c21 100644 --- a/src/main/resources/html/admin/teacher.html +++ b/src/main/resources/html/admin/teacher.html @@ -14,16 +14,16 @@

Lehrerverwaltung -

Lehrer bearbeiten

-
+ - +
- - + +
diff --git a/src/main/resources/js/teacher/build_student.js b/src/main/resources/js/teacher/build_student.js index 985b74a..17373f5 100644 --- a/src/main/resources/js/teacher/build_student.js +++ b/src/main/resources/js/teacher/build_student.js @@ -53,7 +53,7 @@ function createRequestButton(subject, type, label) { await fetch('/subject-request', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ subjectId: subject.id, type, remove: true, studentId }) + body: JSON.stringify({ subjectId: subject.id, subjectRequest: type, remove: true, studentId }) }); // Update local state if (studentData.currentRequests[subject.id]) { @@ -67,7 +67,7 @@ function createRequestButton(subject, type, label) { await fetch('/subject-request', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ subjectId: subject.id, type, studentId }) + body: JSON.stringify({ subjectId: subject.id, subjectRequest: type, studentId }) }); // Update local state if (!studentData.currentRequests[subject.id]) { diff --git a/src/main/resources/js/user/build_dashboard.js b/src/main/resources/js/user/build_dashboard.js index 16ec2f4..f26ff17 100644 --- a/src/main/resources/js/user/build_dashboard.js +++ b/src/main/resources/js/user/build_dashboard.js @@ -51,7 +51,7 @@ function createRequestButton(subject, type, label) { await fetch('/subject-request', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ subjectId: subject.id, type, remove: true }) + body: JSON.stringify({ subjectId: subject.id, subjectRequest: type, remove: true }) }); // Update local state if (studentData.currentRequests[subject.id]) { @@ -65,7 +65,7 @@ function createRequestButton(subject, type, label) { await fetch('/subject-request', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ subjectId: subject.id, type }) + body: JSON.stringify({ subjectId: subject.id, subjectRequest: type }) }); // Update local state if (!studentData.currentRequests[subject.id]) { diff --git a/src/test/java/de/igslandstuhl/database/server/webserver/GetRequestTest.java b/src/test/java/de/igslandstuhl/database/server/webserver/GetRequestTest.java index a0fc3bc..4988ed1 100644 --- a/src/test/java/de/igslandstuhl/database/server/webserver/GetRequestTest.java +++ b/src/test/java/de/igslandstuhl/database/server/webserver/GetRequestTest.java @@ -6,6 +6,7 @@ import org.junit.jupiter.api.Test; import de.igslandstuhl.database.server.resources.ResourceLocation; +import de.igslandstuhl.database.server.webserver.requests.GetRequest; public class GetRequestTest { private static final String LOCALHOST = "127.0.0.1"; diff --git a/src/test/java/de/igslandstuhl/database/server/webserver/GetResponseTest.java b/src/test/java/de/igslandstuhl/database/server/webserver/GetResponseTest.java index dc8f09a..da9d3fc 100644 --- a/src/test/java/de/igslandstuhl/database/server/webserver/GetResponseTest.java +++ b/src/test/java/de/igslandstuhl/database/server/webserver/GetResponseTest.java @@ -9,6 +9,8 @@ import org.junit.jupiter.api.Test; import de.igslandstuhl.database.server.resources.ResourceLocation; +import de.igslandstuhl.database.server.webserver.requests.GetRequest; +import de.igslandstuhl.database.server.webserver.responses.GetResponse; public class GetResponseTest { GetRequest request = new GetRequest("GET / HTTP/1.1", "127.0.0.1", true); diff --git a/src/test/java/de/igslandstuhl/database/server/webserver/PostRequestTest.java b/src/test/java/de/igslandstuhl/database/server/webserver/PostRequestTest.java index 993b828..cc4c146 100644 --- a/src/test/java/de/igslandstuhl/database/server/webserver/PostRequestTest.java +++ b/src/test/java/de/igslandstuhl/database/server/webserver/PostRequestTest.java @@ -13,6 +13,8 @@ import com.google.gson.JsonSyntaxException; +import de.igslandstuhl.database.server.webserver.requests.PostRequest; + public class PostRequestTest { private static final String LOCALHOST = "127.0.0.1"; PostRequest postRequest1; diff --git a/src/test/java/de/igslandstuhl/database/server/webserver/PostResponseTest.java b/src/test/java/de/igslandstuhl/database/server/webserver/PostResponseTest.java index 6e68a66..2068f90 100644 --- a/src/test/java/de/igslandstuhl/database/server/webserver/PostResponseTest.java +++ b/src/test/java/de/igslandstuhl/database/server/webserver/PostResponseTest.java @@ -6,6 +6,9 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import de.igslandstuhl.database.server.webserver.requests.PostRequest; +import de.igslandstuhl.database.server.webserver.responses.PostResponse; + public class PostResponseTest { PostRequest initialRequest; @BeforeEach diff --git a/src/test/java/de/igslandstuhl/database/server/webserver/SessionManagerTest.java b/src/test/java/de/igslandstuhl/database/server/webserver/SessionManagerTest.java index aa8a7a6..7530b2d 100644 --- a/src/test/java/de/igslandstuhl/database/server/webserver/SessionManagerTest.java +++ b/src/test/java/de/igslandstuhl/database/server/webserver/SessionManagerTest.java @@ -12,6 +12,7 @@ import org.junit.jupiter.api.Test; import de.igslandstuhl.database.api.PreConditions; +import de.igslandstuhl.database.server.webserver.requests.PostRequest; public class SessionManagerTest { private static final String LOCALHOST = "127.0.0.1";