diff --git a/build.gradle.kts b/build.gradle.kts index a6c1a1d..648b95e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,7 +7,7 @@ plugins { group = "igs-landstuhl" -version = "v2.0.0-SNAPSHOT-1" +version = "v2.0.0-SNAPSHOT-2" application { mainClass.set("de.igslandstuhl.database.Application") @@ -15,6 +15,14 @@ application { repositories { mavenCentral() + maven { + name = "Plugin Loader Repository" + url = uri("https://maven.pkg.github.com/Learn-Monitor/plugin-loader/") + credentials { + username = System.getenv("GITHUB_ACTOR") + password = System.getenv("GITHUB_TOKEN") + } + } } dependencies { @@ -23,12 +31,14 @@ dependencies { implementation("commons-codec:commons-codec:1.19.0") implementation("com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer:20260101.1") implementation("org.jline:jline:3.30.6") // for better console input handling - implementation("org.yaml:snakeyaml:2.2") // plugin imports // Logging implementation("org.slf4j:slf4j-api:2.0.13") implementation("ch.qos.logback:logback-classic:1.5.6") + // built-in plugins + implementation("de.igs-landstuhl:plugin-loader:v1.0.5") + testImplementation("org.junit.jupiter:junit-jupiter:5.13.4") // using JUnit 5 (latest) testRuntimeOnly("org.junit.platform:junit-platform-launcher") } diff --git a/src/main/java/de/igslandstuhl/database/Application.java b/src/main/java/de/igslandstuhl/database/Application.java index b1ac18e..7884e4c 100644 --- a/src/main/java/de/igslandstuhl/database/Application.java +++ b/src/main/java/de/igslandstuhl/database/Application.java @@ -102,11 +102,17 @@ public Topic[] readFile(String file) throws SerializationException, SQLException return topics.toArray(topicsArr); } + private static void registerBuiltinPlugins() { + LOGGER.info("Registering built-in plugins..."); + Registry.builtinPluginRegistry().register("plugin-loader", de.igslandstuhl.database.plugins.PluginLoader.class); + } + public static void main(String[] args) throws Exception { LOGGER.info("Starting up student-database..."); instance = new Application(args); + registerBuiltinPlugins(); PluginLoader.getInstance().preloadPlugins(); if (!getInstance().suppressCmd()) { diff --git a/src/main/java/de/igslandstuhl/database/Registry.java b/src/main/java/de/igslandstuhl/database/Registry.java index e0604f0..2c83a3f 100644 --- a/src/main/java/de/igslandstuhl/database/Registry.java +++ b/src/main/java/de/igslandstuhl/database/Registry.java @@ -11,6 +11,7 @@ import de.igslandstuhl.database.client.dynamic.DynamicFieldType; import de.igslandstuhl.database.client.navigation.NavigationElement; import de.igslandstuhl.database.client.navigation.NavigationType; +import de.igslandstuhl.database.plugins.BuiltinPlugin; import de.igslandstuhl.database.plugins.Plugin; import de.igslandstuhl.database.server.commands.Command; import de.igslandstuhl.database.server.commands.CommandDescription; @@ -28,6 +29,7 @@ public class Registry implements Closeable { private static final Registry> GET_HANDLER_REGISTRY = new Registry<>(); private static final Registry SQL_REQUEST_HANDLER_REGISTRY = new Registry<>(); private static final Registry PLUGIN_REGISTRY = new Registry<>(); + private static final Registry> BUILTIN_PLUGIN_REGISTRY = new Registry<>(); private static final Registry WEB_PATH_REGISTRY = new Registry<>(); private static final EnumRegistry NAVIGATION_REGISTRY = new EnumRegistry<>(NavigationType.class); @@ -49,6 +51,9 @@ public static Registry sqlRequestHandlerRegistry() { public static Registry pluginRegistry() { return PLUGIN_REGISTRY; } + public static Registry> builtinPluginRegistry() { + return BUILTIN_PLUGIN_REGISTRY; + } public static Registry commandDescriptionRegistry() { return COMMAND_DESCRIPTION_REGISTRY; } diff --git a/src/main/java/de/igslandstuhl/database/plugins/Plugin.java b/src/main/java/de/igslandstuhl/database/plugins/Plugin.java deleted file mode 100644 index 0cbac36..0000000 --- a/src/main/java/de/igslandstuhl/database/plugins/Plugin.java +++ /dev/null @@ -1,126 +0,0 @@ -package de.igslandstuhl.database.plugins; - -import java.util.List; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import de.igslandstuhl.database.plugins.config.PluginConfig; -import de.igslandstuhl.database.plugins.config.PluginSetting; - -public abstract class Plugin { - private String id; - private String name; - private String description; - - private boolean enabled; - private boolean initialized = false; - - public Plugin() { - this.enabled = false; - } - - void init(String id, String name, String description) { - if (initialized) throw new IllegalStateException("Plugin already initialized"); - this.id = id; - this.name = name; - this.description = description; - this.initialized = true; - } - void init(PluginDescription description) { - init(description.id(), description.name(), description.description()); - } - - public String getId() { - return id; - } - public String getName() { - return name; - } - public String getDescription() { - return description; - } - public boolean isEnabled() { - return enabled; - } - - public String toJSON() { - StringBuilder sb = new StringBuilder(); - sb.append("{"); - sb.append("\"id\":\"").append(id).append("\","); - sb.append("\"name\":\"").append(name).append("\","); - sb.append("\"description\":\"").append(description).append("\","); - sb.append("\"enabled\":").append(enabled).append(","); - sb.append("\"config\":").append(getConfig().toJSON()); - sb.append("}"); - return sb.toString(); - } - - /** - * Returns the config of this plugin. - * This should never be null, as it will break the plugin lifecycle otherwise. - * @return the config - */ - public abstract PluginConfig getConfig(); - - protected abstract void onEnable(); - protected abstract void onDisable(); - protected abstract void onLoad(); - - public void enable() { - if (enabled) return; - onEnable(); - enabled = true; - } - public void disable() { - if (!enabled) return; - onDisable(); - enabled = false; - } - public void toggle() { - if (enabled) { - disable(); - } else { - enable(); - } - getConfig().save(); - } - void load() { - onLoad(); - } - - public Logger getLogger() { - return LoggerFactory.getLogger(id); - } - - static class DummyModule extends Plugin { - private final PluginConfig config; - public DummyModule(String id, String name, String description, List> settings) { - init(id, name, description); - config = new PluginConfig(this, settings) { - - }; - } - - @Override - public PluginConfig getConfig() { - return config; - } - - @Override - protected void onEnable() { - // Dummy enable logic - } - - @Override - protected void onDisable() { - // Dummy disable logic - } - - @Override - protected void onLoad() { - // Dummy load logic - } - } - -} diff --git a/src/main/java/de/igslandstuhl/database/plugins/PluginDescription.java b/src/main/java/de/igslandstuhl/database/plugins/PluginDescription.java deleted file mode 100644 index 1a2d7eb..0000000 --- a/src/main/java/de/igslandstuhl/database/plugins/PluginDescription.java +++ /dev/null @@ -1,7 +0,0 @@ -package de.igslandstuhl.database.plugins; - -import java.util.List; - -public record PluginDescription(String id, String name, String description, String main, List depends) { - -} diff --git a/src/main/java/de/igslandstuhl/database/plugins/PluginLoader.java b/src/main/java/de/igslandstuhl/database/plugins/PluginLoader.java deleted file mode 100644 index 6f6a070..0000000 --- a/src/main/java/de/igslandstuhl/database/plugins/PluginLoader.java +++ /dev/null @@ -1,199 +0,0 @@ -package de.igslandstuhl.database.plugins; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.yaml.snakeyaml.Yaml; - -import de.igslandstuhl.database.Registry; - -public class PluginLoader { - public static final Logger LOGGER = LoggerFactory.getLogger(PluginLoader.class); - private final List pluginInfos = new ArrayList<>(); - public List getPluginInfos() { - return pluginInfos; - } - private static final PluginLoader INSTANCE = new PluginLoader(); - public static PluginLoader getInstance() { - return INSTANCE; - } - private PluginLoader() {} - - private Map loadYaml(URLClassLoader classLoader) { - try (InputStream is = classLoader.getResourceAsStream("plugin.yml")) { - if (is == null) return null; - - Yaml yaml = new Yaml(); - return yaml.load(is); - } catch (Exception e) { - LOGGER.error("Failed loading yaml from classLoader {}", classLoader.getName()); - return null; - } - } - public PreLoadedPlugin loadPluginFromJar(File jarFile) { - URLClassLoader classLoader; - URLClassLoader resourceLoader; - try { - classLoader = new URLClassLoader( - new URL[]{jarFile.toURI().toURL()}, - getClass().getClassLoader() - ); - resourceLoader = new URLClassLoader( - new URL[]{jarFile.toURI().toURL()}, - null - ); - } catch (MalformedURLException e) { - LOGGER.error("URL of {} corrupted", jarFile.getName(), e); - return null; - } - try { - - Map yaml = loadYaml(classLoader); - if (yaml == null) { - LOGGER.error("No plugin.yml found in {}", jarFile.getName()); - throw new FileNotFoundException("No plugin.yml found"); - } - - String mainClassName = (String) yaml.get("main"); - String id = (String) yaml.get("id"); - if (id == null || mainClassName == null) { - LOGGER.error("Invalid plugin.yml in {}: you must define id and main", jarFile.getName()); - throw new IllegalArgumentException("Invalid plugin.yml"); - } - String name = (String) yaml.getOrDefault("name", id); - String description = (String) yaml.getOrDefault("description", ""); - - Object dependsObj = yaml.get("depends"); - List depends = new ArrayList<>(); - - if (dependsObj instanceof List) { - for (Object o : (List) dependsObj) { - if (o instanceof String s) { - depends.add(s); - } - } - } - - Class clazz = classLoader.loadClass(mainClassName); - - if (!Plugin.class.isAssignableFrom(clazz)) { - LOGGER.error("{}, the main class of {} does not extend Plugin", clazz.getCanonicalName(), jarFile.getName()); - throw new ClassCastException("Plugin main class does not extend Plugin"); - } - - return new PreLoadedPlugin(new PluginDescription(id, name, description, mainClassName, depends), clazz, classLoader, resourceLoader); - - } catch (Exception e) { - LOGGER.error("Failed to preload plugin {}", jarFile.getName(), e); - try { - classLoader.close(); - } catch (IOException e1) { - LOGGER.error("Failed to close classloader for incomplete plugin {}", jarFile.getName(), e1); - } - return null; - } - } - public void load(PreLoadedPlugin preload) { - Plugin plugin; - try { - plugin = (Plugin) preload.clazz().getDeclaredConstructor().newInstance(); - plugin.init(preload.description()); - registerPlugin(plugin); - plugin.load(); - if (plugin.getConfig() == null) { - LOGGER.error("Plugin '{}' does not have a config", preload.description().id()); - throw new NullPointerException("Plugin must have a config"); - } - } catch (Exception e) { - LOGGER.error("Failed to load plugin '{}'", preload.description().id(), e); - pluginInfos.remove(preload); - if (Registry.pluginRegistry().get(preload.description().id()) != null) Registry.pluginRegistry().unregister(preload.description().id()); - } - } - public void preloadAllPlugins(File folder) { - File[] jars = folder.listFiles((dir, name) -> name.endsWith(".jar")); - if (jars == null) return; - - List plugins = new ArrayList<>(); - - for (File jar : jars) { - PreLoadedPlugin m = loadPluginFromJar(jar); - if (m != null) { - plugins.add(m); - } - } - - // Check for duplicate ids - Set ids = new HashSet<>(); - for (PreLoadedPlugin p : plugins) { - if (!ids.add(p.description().id())) { - LOGGER.error("Duplicate plugin id '{}', aborting", p.description().id()); - throw new IllegalStateException("Duplicate plugin id"); - } - } - - PluginSort.sortPlugins(plugins).forEach((p) -> pluginInfos.add(p)); - } - public void loadAllPlugins(File folder) { - pluginInfos.forEach(this::load); - } - public void enablePlugins() { - LOGGER.info("Enabling plugins..."); - pluginInfos.forEach((p) -> { - Plugin plugin = Registry.pluginRegistry().get(p.description().id()); - if (plugin.getConfig().isEnabledOnStart()) { - plugin.enable(); - } - }); - } - public void unloadPlugins() { - LOGGER.info("Unloading plugins..."); - Collections.reverse(pluginInfos); - pluginInfos.forEach((p) -> { - Plugin plugin = Registry.pluginRegistry().get(p.description().id()); - plugin.getConfig().save(); - if (plugin != null && plugin.isEnabled()) { - plugin.disable(); - } - Registry.pluginRegistry().unregister(plugin.getId()); - - try { - p.classLoader().close(); - p.resourceLoader().close(); - } catch (IOException e) { - LOGGER.error("Failed to unload plugin '{}'", plugin.getId()); - } - }); - pluginInfos.clear(); - } - - - - private void registerPlugin(Plugin plugin) { - if (Registry.pluginRegistry().keyStream().anyMatch(plugin.getId()::equals)) { - throw new IllegalStateException("Duplicate module id: " + plugin.getId()); - } - Registry.pluginRegistry().register(plugin.getId(), plugin); - } - public void preloadPlugins() { - LOGGER.info("Preloading plugins from directory \"plugins\"..."); - preloadAllPlugins(new File("plugins")); - } - public void registerPlugins() { - LOGGER.info("Registering plugins from directory \"plugins\"..."); - loadAllPlugins(new File("plugins")); - } -} diff --git a/src/main/java/de/igslandstuhl/database/plugins/PluginResourceProvider.java b/src/main/java/de/igslandstuhl/database/plugins/PluginResourceProvider.java index b502ff2..2cb6e16 100644 --- a/src/main/java/de/igslandstuhl/database/plugins/PluginResourceProvider.java +++ b/src/main/java/de/igslandstuhl/database/plugins/PluginResourceProvider.java @@ -25,6 +25,7 @@ public InputStream open(ResourceLocation location) { for (PreLoadedPlugin module : PluginLoader.getInstance().getPluginInfos()) { ClassLoader cl = module.resourceLoader(); + if (cl == null) continue; // built-in plugin InputStream stream = cl.getResourceAsStream(path); if (stream != null) { @@ -42,6 +43,7 @@ public List openAll(ResourceLocation location) { for (PreLoadedPlugin module : PluginLoader.getInstance().getPluginInfos()) { ClassLoader cl = module.resourceLoader(); + if (cl == null) continue; // built-in plugin InputStream stream = cl.getResourceAsStream(path); if (stream != null) { diff --git a/src/main/java/de/igslandstuhl/database/plugins/PluginSort.java b/src/main/java/de/igslandstuhl/database/plugins/PluginSort.java deleted file mode 100644 index 05f59d4..0000000 --- a/src/main/java/de/igslandstuhl/database/plugins/PluginSort.java +++ /dev/null @@ -1,57 +0,0 @@ -package de.igslandstuhl.database.plugins; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -public class PluginSort { - private PluginSort() {} - private static void visit( - PreLoadedPlugin plugin, - Map map, - List sorted, - Set visited, - Set visiting - ) { - String id = plugin.description().id(); - - if (visited.contains(id)) return; - - if (visiting.contains(id)) { - throw new IllegalStateException("Circular dependency detected: " + id); - } - - visiting.add(id); - - for (String dep : plugin.description().depends()) { - PreLoadedPlugin dependency = map.get(dep); - if (dependency == null) { - throw new IllegalStateException("Missing dependency: " + dep + " for " + id); - } - visit(dependency, map, sorted, visited, visiting); - } - - visiting.remove(id); - visited.add(id); - sorted.add(plugin); - } - public static List sortPlugins(List plugins) { - Map map = new HashMap<>(); - for (PreLoadedPlugin m : plugins) { - map.put(m.description().id(), m); - } - - List sorted = new ArrayList<>(); - Set visited = new HashSet<>(); - Set visiting = new HashSet<>(); - - for (PreLoadedPlugin m : plugins) { - visit(m, map, sorted, visited, visiting); - } - - return sorted; - } -} diff --git a/src/main/java/de/igslandstuhl/database/plugins/PreLoadedPlugin.java b/src/main/java/de/igslandstuhl/database/plugins/PreLoadedPlugin.java deleted file mode 100644 index dcb82df..0000000 --- a/src/main/java/de/igslandstuhl/database/plugins/PreLoadedPlugin.java +++ /dev/null @@ -1,12 +0,0 @@ -package de.igslandstuhl.database.plugins; - -import java.net.URLClassLoader; - -record PreLoadedPlugin ( - PluginDescription description, - Class clazz, - URLClassLoader classLoader, - URLClassLoader resourceLoader -) { - -} \ No newline at end of file diff --git a/src/main/java/de/igslandstuhl/database/plugins/config/BoolSetting.java b/src/main/java/de/igslandstuhl/database/plugins/config/BoolSetting.java deleted file mode 100644 index 1e368ff..0000000 --- a/src/main/java/de/igslandstuhl/database/plugins/config/BoolSetting.java +++ /dev/null @@ -1,31 +0,0 @@ -package de.igslandstuhl.database.plugins.config; - -public class BoolSetting extends PluginSetting { - public BoolSetting(String key, String name, String description, boolean defaultValue) { - super(key, name, description, defaultValue); - } - - public void toggle() { - setValue(!getValue()); - } - public void enable() { - setValue(true); - } - public void disable() { - setValue(false); - } - public boolean isEnabled() { - return getValue(); - } - - @Override - public String toJSON() { - return "{" + - "\"key\":\"" + getKey() + "\"," + - "\"name\":\"" + getName() + "\"," + - "\"description\":\"" + getDescription() + "\"," + - "\"defaultValue\":" + getDefaultValue() + "," + - "\"value\":" + getValue() + - "}"; - } -} diff --git a/src/main/java/de/igslandstuhl/database/plugins/config/PluginConfig.java b/src/main/java/de/igslandstuhl/database/plugins/config/PluginConfig.java deleted file mode 100644 index b0735f6..0000000 --- a/src/main/java/de/igslandstuhl/database/plugins/config/PluginConfig.java +++ /dev/null @@ -1,135 +0,0 @@ -package de.igslandstuhl.database.plugins.config; - -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.util.Arrays; -import java.util.List; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonObject; - -import de.igslandstuhl.database.plugins.Plugin; -import de.igslandstuhl.database.plugins.PluginLoader; - -public abstract class PluginConfig { - private final T plugin; - private final BoolSetting[] boolSettings; - - private final File configFile; - private boolean enabledOnStart; - - public PluginConfig(T plugin, PluginSetting... moduleSettings) { - this.plugin = plugin; - List boolSettings = Arrays.stream(moduleSettings).filter((s) -> s instanceof BoolSetting).map((s) -> (BoolSetting) s).toList(); - this.boolSettings = boolSettings.toArray(new BoolSetting[boolSettings.size()]); - this.configFile = new File("plugins/config", plugin.getId() + ".json"); - load(); - } - - public PluginConfig(T plugin, List> moduleSettings) { - this.plugin = plugin; - List boolSettings = moduleSettings.stream().filter((s) -> s instanceof BoolSetting).map((s) -> (BoolSetting) s).toList(); - this.boolSettings = boolSettings.toArray(new BoolSetting[boolSettings.size()]); - this.configFile = new File("plugins/config", plugin.getId() + ".json"); - load(); - } - - public T getPlugin() { - return plugin; - } - public boolean isEnabledOnStart() { - return enabledOnStart; - } - - private BoolSetting findBoolSetting(String key) { - return Arrays.stream(boolSettings) - .filter((s) -> s.getKey().equals(key)) - .findAny().orElseThrow(); - } - - public boolean getBool(String key) { - return findBoolSetting(key).getValue(); - } - - public void setBool(String key, boolean value) { - findBoolSetting(key).setValue(value); - save(); - } - public void enableSetting(String key) { - findBoolSetting(key).enable(); - save(); - } - public void disableSetting(String key) { - findBoolSetting(key).disable(); - save(); - } - public void toggleSetting(String key) { - findBoolSetting(key).toggle(); - save(); - } - - private JsonObject valuesJSON() { - JsonObject values = new JsonObject(); - for (BoolSetting s : boolSettings) { - values.addProperty(s.getKey(), s.getValue()); - } - return values; - } - public String toJSON() { - StringBuilder builder = new StringBuilder("{"); - builder.append("\"settings\": {") - .append("\"bools\": ["); - for (int i = 0; i < boolSettings.length; i++) { - builder.append(boolSettings[i].toJSON()); - if (i < boolSettings.length - 1) { - builder.append(", "); - } - } - builder - .append("]}, ") - .append("\"values\": ") - .append((new Gson()).toJson(valuesJSON())) - .append("}"); - return builder.toString(); - } - - // Persistence - public void save() { - if (configFile.getParentFile() != null && !configFile.getParentFile().exists()) { - configFile.getParentFile().mkdirs(); - } - try (FileWriter writer = new FileWriter(configFile)) { - JsonObject root = new JsonObject(); - root.add("values", valuesJSON()); - root.addProperty("enabled", plugin.isEnabled()); - - Gson gson = new GsonBuilder().setPrettyPrinting().create(); - gson.toJson(root, writer); - } catch (IOException e) { - PluginLoader.LOGGER.error("Failed to save plugin config for {}", plugin.getId(), e); - } - } - public void load() { - if (!configFile.exists()) return; - try (FileReader reader = new FileReader(configFile)) { - Gson gson = new Gson(); - JsonObject root = gson.fromJson(reader, JsonObject.class); - - JsonObject values = root.getAsJsonObject("values"); - if (values != null) { - for (BoolSetting s : boolSettings) { - if (values.has(s.getKey())) { - s.setValue(values.get(s.getKey()).getAsBoolean()); - } - } - } - enabledOnStart = root.get("enabled").getAsBoolean(); - - } catch (IOException e) { - PluginLoader.LOGGER.error("Failed to load plugin config for {}", plugin.getId(), e); - } - } -} \ No newline at end of file diff --git a/src/main/java/de/igslandstuhl/database/plugins/config/PluginSetting.java b/src/main/java/de/igslandstuhl/database/plugins/config/PluginSetting.java deleted file mode 100644 index 0d632fa..0000000 --- a/src/main/java/de/igslandstuhl/database/plugins/config/PluginSetting.java +++ /dev/null @@ -1,49 +0,0 @@ -package de.igslandstuhl.database.plugins.config; - -public class PluginSetting { - private final String key; - private final String name; - private final String description; - private final T defaultValue; - private T value; - - PluginSetting(String key, String name, String description, T defaultValue) { - this.key = key; - this.name = name; - this.description = description; - this.defaultValue = defaultValue; - this.value = defaultValue; - } - - public String getKey() { - return key; - } - public String getName() { - return name; - } - public String getDescription() { - return description; - } - public T getDefaultValue() { - return defaultValue; - } - - public T getValue() { - return value; - } - - public void setValue(T value) { - this.value = value; - } - - public String toJSON() { - return "{" + - "\"key\":\"" + key + "\"," + - "\"name\":\"" + name + "\"," + - "\"description\":\"" + description + "\"," + - "\"defaultValue\":\"" + defaultValue + "\"," + - "\"value\":\"" + value + "\"" + - "}"; - } - -} diff --git a/src/main/java/de/igslandstuhl/database/server/sql/SQLiteConnection.java b/src/main/java/de/igslandstuhl/database/server/sql/SQLiteConnection.java index 7208b49..03f2364 100644 --- a/src/main/java/de/igslandstuhl/database/server/sql/SQLiteConnection.java +++ b/src/main/java/de/igslandstuhl/database/server/sql/SQLiteConnection.java @@ -80,7 +80,7 @@ public PreparedStatement prepareStatement(String sql) throws SQLException { public ResultSet executeStatementQuerySecure(PreparedStatement statement) throws SQLException { lock.readLock().lock(); try (statement) { - return statement.executeQuery(); + return statement.executeQuery(); } finally { lock.readLock().unlock(); } diff --git a/src/main/java/de/igslandstuhl/database/server/webserver/handlers/PostRequestHandler.java b/src/main/java/de/igslandstuhl/database/server/webserver/handlers/PostRequestHandler.java index c9d5991..a60266a 100644 --- a/src/main/java/de/igslandstuhl/database/server/webserver/handlers/PostRequestHandler.java +++ b/src/main/java/de/igslandstuhl/database/server/webserver/handlers/PostRequestHandler.java @@ -31,6 +31,11 @@ import de.igslandstuhl.database.api.Topic; import de.igslandstuhl.database.api.User; import de.igslandstuhl.database.api.results.GenerationResult; +import de.igslandstuhl.database.plugins.config.BoolSetting; +import de.igslandstuhl.database.plugins.config.IntSetting; +import de.igslandstuhl.database.plugins.config.PluginConfig; +import de.igslandstuhl.database.plugins.config.PluginSetting; +import de.igslandstuhl.database.plugins.config.ShortAnswerSetting; import de.igslandstuhl.database.server.Server; import de.igslandstuhl.database.server.webserver.AccessLevel; import de.igslandstuhl.database.server.webserver.ContentType; @@ -383,6 +388,20 @@ public static void registerHandlers() { Registry.pluginRegistry().get(key[0]).getConfig().toggleSetting(key[1]); return PostResponse.ok("Plugin setting toggled", ContentType.TEXT_PLAIN, rq); }); + HttpHandler.registerPostRequestHandler("/set-plugin-setting", AccessLevel.ADMIN, (rq) -> { + String[] key = rq.getString("key").split(":"); + PluginConfig config = Registry.pluginRegistry().get(key[0]).getConfig(); + PluginSetting setting = config.getSetting(key[1]); + if (setting instanceof BoolSetting boolSetting) { + boolSetting.setValue(rq.getBoolean("value")); + } else if (setting instanceof IntSetting intSetting) { + intSetting.setValue(rq.getInt("value")); + } else if (setting instanceof ShortAnswerSetting shortAnswerSetting) { + shortAnswerSetting.setValue(rq.getString("value")); + } else { + return PostResponse.badRequest("Setting not found", rq);} + return PostResponse.ok("Plugin setting set", ContentType.TEXT_PLAIN, rq); + }); HttpHandler.registerPostRequestHandler("/student-results-csv", AccessLevel.TEACHER, (rq) -> { Student student = rq.getCurrentStudent(); diff --git a/src/main/java/de/igslandstuhl/database/server/webserver/responses/PostResponse.java b/src/main/java/de/igslandstuhl/database/server/webserver/responses/PostResponse.java index 9226521..f2d373a 100644 --- a/src/main/java/de/igslandstuhl/database/server/webserver/responses/PostResponse.java +++ b/src/main/java/de/igslandstuhl/database/server/webserver/responses/PostResponse.java @@ -6,6 +6,7 @@ import com.google.gson.Gson; import de.igslandstuhl.database.server.Server; +import de.igslandstuhl.database.server.WebServer; import de.igslandstuhl.database.server.resources.ResourceLocation; import de.igslandstuhl.database.server.webserver.AccessManager; import de.igslandstuhl.database.server.webserver.ContentType; @@ -107,7 +108,7 @@ public void respond(PrintStream out) { out.print("HTTP/1.1 "); statusCode.write(out); out.print("\r\n"); - out.print("Content-Type: " + contentType + "; charset=UTF-8\r\n"); + out.print("Content-Type: " + contentType.getName() + "; charset=UTF-8\r\n"); if (cookie != null) { out.print("Set-Cookie: " + cookie + "; HttpOnly; Secure\r\n"); } @@ -116,6 +117,7 @@ public void respond(PrintStream out) { } out.print("\r\n"); if (body != null) { + WebServer.LOGGER.debug("Response body: {}", body); out.print(body); } out.flush(); diff --git a/src/main/resources/js/site/student-database.js b/src/main/resources/js/site/student-database.js index 3104254..eb1d6c4 100644 --- a/src/main/resources/js/site/student-database.js +++ b/src/main/resources/js/site/student-database.js @@ -158,6 +158,9 @@ async function togglePlugin(pluginKey) { async function togglePluginSetting(pluginKey, setting) { return await post('/toggle-plugin-setting', { key: pluginKey + ":" + setting }); } +async function setPluginSetting(pluginKey, setting, value) { + return await post('/set-plugin-setting', { key: pluginKey + ":" + setting, value }); +} async function deleteClass(classId) { return await post('/delete-class', { id: classId }); @@ -682,6 +685,16 @@ function loadPluginSection(pluginKey) { tr.innerHTML = `${b.name}${b.value}`; tbody.appendChild(tr); }); + settings.ints.forEach((i) => { + const tr = document.createElement("tr"); + tr.innerHTML = `${i.name}${i.value}`; + tbody.appendChild(tr); + }) + settings.shortAnswers.forEach((s) => { + const tr = document.createElement("tr"); + tr.innerHTML = `${s.name}${s.value}`; + tbody.appendChild(tr); + }) }) } async function loadPluginsView(pluginContainer) {