From e8b9c31688e8757dd2e95cc9fdcafdb5657ec4e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kr=C3=BChlmann?= Date: Fri, 9 Jan 2026 21:15:42 +0100 Subject: [PATCH] Temo commit --- .../imgfloat/controller/ViewController.java | 5 ++ .../service/SystemAdministratorService.java | 54 ++++++++++++++----- src/main/resources/static/js/settings.js | 16 +++++- src/main/resources/templates/settings.html | 1 + 4 files changed, 62 insertions(+), 14 deletions(-) diff --git a/src/main/java/dev/kruhlmann/imgfloat/controller/ViewController.java b/src/main/java/dev/kruhlmann/imgfloat/controller/ViewController.java index cfc3eac..5fa9235 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/controller/ViewController.java +++ b/src/main/java/dev/kruhlmann/imgfloat/controller/ViewController.java @@ -11,6 +11,7 @@ import dev.kruhlmann.imgfloat.service.ChannelDirectoryService; import dev.kruhlmann.imgfloat.service.GitInfoService; import dev.kruhlmann.imgfloat.service.GithubReleaseService; import dev.kruhlmann.imgfloat.service.SettingsService; +import dev.kruhlmann.imgfloat.service.SystemAdministratorService; import dev.kruhlmann.imgfloat.service.VersionService; import dev.kruhlmann.imgfloat.util.LogSanitizer; import org.slf4j.Logger; @@ -31,6 +32,7 @@ public class ViewController { private final ObjectMapper objectMapper; private final AuthorizationService authorizationService; private final GithubReleaseService githubReleaseService; + private final SystemAdministratorService systemAdministratorService; private final long uploadLimitBytes; public ViewController( @@ -41,6 +43,7 @@ public class ViewController { ObjectMapper objectMapper, AuthorizationService authorizationService, GithubReleaseService githubReleaseService, + SystemAdministratorService systemAdministratorService, long uploadLimitBytes ) { this.channelDirectoryService = channelDirectoryService; @@ -50,6 +53,7 @@ public class ViewController { this.objectMapper = objectMapper; this.authorizationService = authorizationService; this.githubReleaseService = githubReleaseService; + this.systemAdministratorService = systemAdministratorService; this.uploadLimitBytes = uploadLimitBytes; } @@ -111,6 +115,7 @@ public class ViewController { LOG.error("Failed to serialize settings for settings view", e); throw new ResponseStatusException(INTERNAL_SERVER_ERROR, "Failed to serialize settings"); } + model.addAttribute("initialSysadmin", systemAdministratorService.getInitialSysadmin()); return "settings"; } diff --git a/src/main/java/dev/kruhlmann/imgfloat/service/SystemAdministratorService.java b/src/main/java/dev/kruhlmann/imgfloat/service/SystemAdministratorService.java index c8f0ff9..bae5648 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/service/SystemAdministratorService.java +++ b/src/main/java/dev/kruhlmann/imgfloat/service/SystemAdministratorService.java @@ -8,7 +8,6 @@ import java.util.Locale; import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; import org.springframework.core.env.Environment; import org.springframework.stereotype.Service; @@ -18,25 +17,18 @@ public class SystemAdministratorService { private static final Logger logger = LoggerFactory.getLogger(SystemAdministratorService.class); private final SystemAdministratorRepository repo; - private final String initialSysadmin; private final Environment environment; public SystemAdministratorService( SystemAdministratorRepository repo, - @Value("${IMGFLOAT_INITIAL_TWITCH_USERNAME_SYSADMIN:#{null}}") String initialSysadmin, Environment environment ) { this.repo = repo; - this.initialSysadmin = initialSysadmin; this.environment = environment; } @PostConstruct public void initDefaults() { - if (repo.count() > 0) { - return; - } - if ( Boolean.parseBoolean( environment.getProperty("org.springframework.boot.test.context.SpringBootTestContextBootstrapper") @@ -46,18 +38,34 @@ public class SystemAdministratorService { return; } + String initialSysadmin = getInitialSysadmin(); + if (initialSysadmin != null) { + long deleted = repo.deleteByTwitchUsername(initialSysadmin); + if (deleted > 0) { + logger.info("Removed persisted initial system administrator '{}' to use environment value", initialSysadmin); + } + } + + if (repo.count() > 0) { + return; + } + if (initialSysadmin == null || initialSysadmin.isBlank()) { throw new IllegalStateException( "No system administrators exist and IMGFLOAT_INITIAL_TWITCH_USERNAME_SYSADMIN is not set" ); } - addSysadmin(initialSysadmin); - logger.info("Created initial system administrator '{}'", initialSysadmin); + logger.info("Using initial system administrator '{}' from environment", initialSysadmin); } public void addSysadmin(String twitchUsername) { String normalized = normalize(twitchUsername); + String initialSysadmin = getInitialSysadmin(); + + if (initialSysadmin != null && initialSysadmin.equals(normalized)) { + return; + } if (repo.existsByTwitchUsername(normalized)) { return; @@ -67,11 +75,18 @@ public class SystemAdministratorService { } public void removeSysadmin(String twitchUsername) { - if (repo.count() <= 1) { + String normalized = normalize(twitchUsername); + String initialSysadmin = getInitialSysadmin(); + + if (initialSysadmin != null && initialSysadmin.equals(normalized)) { + throw new IllegalStateException("Cannot remove the initial system administrator"); + } + + if (initialSysadmin == null && repo.count() <= 1) { throw new IllegalStateException("Cannot remove the last system administrator"); } - long deleted = repo.deleteByTwitchUsername(normalize(twitchUsername)); + long deleted = repo.deleteByTwitchUsername(normalized); if (deleted == 0) { throw new IllegalArgumentException("System administrator does not exist"); @@ -79,7 +94,20 @@ public class SystemAdministratorService { } public boolean isSysadmin(String twitchUsername) { - return repo.existsByTwitchUsername(normalize(twitchUsername)); + String normalized = normalize(twitchUsername); + String initialSysadmin = getInitialSysadmin(); + if (initialSysadmin != null && initialSysadmin.equals(normalized)) { + return true; + } + return repo.existsByTwitchUsername(normalized); + } + + public String getInitialSysadmin() { + String value = environment.getProperty("IMGFLOAT_INITIAL_TWITCH_USERNAME_SYSADMIN"); + if (value == null || value.isBlank()) { + return null; + } + return normalize(value); } public List listSysadmins() { diff --git a/src/main/resources/static/js/settings.js b/src/main/resources/static/js/settings.js index 5a3424e..5c87d3c 100644 --- a/src/main/resources/static/js/settings.js +++ b/src/main/resources/static/js/settings.js @@ -19,6 +19,10 @@ const sysadminInputElement = document.getElementById("new-sysadmin"); const addSysadminButtonElement = document.getElementById("add-sysadmin-button"); let currentSettings = JSON.parse(serverRenderedSettings); +const initialSysadmin = + typeof serverRenderedInitialSysadmin === "string" && serverRenderedInitialSysadmin.trim() !== "" + ? serverRenderedInitialSysadmin.trim().toLowerCase() + : null; let userSettings = { ...currentSettings }; function jsonEquals(a, b) { @@ -165,7 +169,7 @@ function renderSystemAdministrators(admins) { }); } -function loadSystemAdministrators() { +async function loadSystemAdministrators() { return fetch("/api/system-administrators") .then((r) => { if (!r.ok) { @@ -254,3 +258,13 @@ setFormSettings(currentSettings); updateStatCards(currentSettings); updateSubmitButtonDisabledState(); loadSystemAdministrators(); + +if (initialSysadmin) { + document.querySelectorAll("[data-sysadmin-remove]").forEach((button) => { + const username = button.getAttribute("data-sysadmin-username"); + if (username && username.trim().toLowerCase() === initialSysadmin) { + button.disabled = true; + button.title = "The initial system administrator cannot be removed."; + } + }); +} diff --git a/src/main/resources/templates/settings.html b/src/main/resources/templates/settings.html index 7707837..e78e536 100644 --- a/src/main/resources/templates/settings.html +++ b/src/main/resources/templates/settings.html @@ -285,6 +285,7 @@