Improve settings

This commit is contained in:
2025-12-29 12:46:47 +01:00
parent 714c5f758c
commit e870a74fa6
15 changed files with 371 additions and 108 deletions

View File

@@ -29,13 +29,13 @@ public class SchemaMigration implements ApplicationRunner {
}
private void cleanupSpringSessionTables() {
try {
jdbcTemplate.execute("DELETE FROM SPRING_SESSION_ATTRIBUTES");
jdbcTemplate.execute("DELETE FROM SPRING_SESSION");
logger.info("Cleared persisted Spring Session tables on startup to avoid stale session conflicts");
} catch (DataAccessException ex) {
logger.debug("Spring Session tables not available for cleanup", ex);
}
// try {
// jdbcTemplate.execute("DELETE FROM SPRING_SESSION_ATTRIBUTES");
// jdbcTemplate.execute("DELETE FROM SPRING_SESSION");
// logger.info("Cleared persisted Spring Session tables on startup to avoid stale session conflicts");
// } catch (DataAccessException ex) {
// logger.debug("Spring Session tables not available for cleanup", ex);
// }
}
private void ensureChannelCanvasColumns() {

View File

@@ -20,32 +20,31 @@ public class SecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers(
"/",
"/css/**",
"/js/**",
"/webjars/**",
"/actuator/health",
"/v3/api-docs/**",
"/swagger-ui.html",
"/swagger-ui/**",
"/channels"
).permitAll()
.requestMatchers(HttpMethod.GET, "/view/*/broadcast").permitAll()
.requestMatchers(HttpMethod.GET, "/api/channels").permitAll()
.requestMatchers(HttpMethod.GET, "/api/channels/*/assets/visible").permitAll()
.requestMatchers(HttpMethod.GET, "/api/channels/*/canvas").permitAll()
.requestMatchers(HttpMethod.GET, "/api/channels/*/assets/*/content").permitAll()
.requestMatchers("/ws/**").permitAll()
.anyRequest().authenticated()
)
.oauth2Login(oauth -> oauth
.tokenEndpoint(token -> token.accessTokenResponseClient(twitchAccessTokenResponseClient()))
.userInfoEndpoint(user -> user.userService(twitchOAuth2UserService()))
)
.logout(logout -> logout.logoutSuccessUrl("/").permitAll())
.csrf(csrf -> csrf.ignoringRequestMatchers("/ws/**", "/api/**"));
.authorizeHttpRequests(auth -> auth
.requestMatchers(
"/",
"/css/**",
"/js/**",
"/webjars/**",
"/actuator/health",
"/v3/api-docs/**",
"/swagger-ui.html",
"/swagger-ui/**",
"/channels"
).permitAll()
.requestMatchers(HttpMethod.GET, "/view/*/broadcast").permitAll()
.requestMatchers(HttpMethod.GET, "/api/channels").permitAll()
.requestMatchers(HttpMethod.GET, "/api/channels/*/assets/visible").permitAll()
.requestMatchers(HttpMethod.GET, "/api/channels/*/canvas").permitAll()
.requestMatchers(HttpMethod.GET, "/api/channels/*/assets/*/content").permitAll()
.requestMatchers("/ws/**").permitAll()
.anyRequest().authenticated()
)
.oauth2Login(oauth -> oauth
.tokenEndpoint(token -> token.accessTokenResponseClient(twitchAccessTokenResponseClient()))
.userInfoEndpoint(user -> user.userService(twitchOAuth2UserService())))
.logout(logout -> logout.logoutSuccessUrl("/").permitAll())
.csrf(csrf -> csrf.ignoringRequestMatchers("/ws/**", "/api/**"));
return http.build();
}

View File

@@ -55,14 +55,11 @@ public class SystemEnvironmentValidator {
);
}
log.info("Environment validation successful.");
log.info("Configuration:");
log.info("Environment validation successful:");
log.info(" - TWITCH_CLIENT_ID: {}", redact(twitchClientId));
log.info(" - TWITCH_CLIENT_SECRET: {}", redact(twitchClientSecret));
log.info(" - SPRING_SERVLET_MULTIPART_MAX_FILE_SIZE: {} ({} bytes)",
springMaxFileSize, maxUploadBytes);
log.info(" - SPRING_SERVLET_MULTIPART_MAX_REQUEST_SIZE: {} ({} bytes)",
springMaxRequestSize, maxRequestBytes);
log.info(" - SPRING_SERVLET_MULTIPART_MAX_FILE_SIZE: {} ({} bytes)", springMaxFileSize, maxUploadBytes);
log.info(" - SPRING_SERVLET_MULTIPART_MAX_REQUEST_SIZE: {} ({} bytes)", springMaxRequestSize, maxRequestBytes);
log.info(" - IMGFLOAT_DB_PATH: {}", dbPath);
log.info(" - IMGFLOAT_INITIAL_TWITCH_USERNAME_SYSADMIN: {}", initialSysadmin);
log.info(" - IMGFLOAT_ASSETS_PATH: {}", assetsPath);
@@ -70,20 +67,23 @@ public class SystemEnvironmentValidator {
}
private void checkString(String value, String name, StringBuilder missing) {
if (!StringUtils.hasText(value) || "changeme".equalsIgnoreCase(value.trim())) {
missing.append(" - ").append(name).append("\n");
if (value != null && StringUtils.hasText(value)) {
return
}
missing.append(" - ").append(name).append("\n");
}
private <T extends Number> void checkUnsignedNumeric(T value, String name, StringBuilder missing) {
if (value == null || value.doubleValue() <= 0) {
missing.append(" - ").append(name).append('\n');
if (value !== null && value.doubleValue() >= 0) {
return;
}
missing.append(" - ").append(name).append('\n');
}
private String redact(String value) {
if (!StringUtils.hasText(value)) return "(missing)";
if (value.length() <= 6) return "******";
return value.substring(0, 2) + "****" + value.substring(value.length() - 2);
if (value != null && StringUtils.hasText(value)) {
return "**************";
};
return "<not set>";
}
}

View File

@@ -41,10 +41,11 @@ final class TwitchAuthorizationCodeGrantRequestEntityConverter implements
}
return new RequestEntity<>(
body,
entity.getHeaders(),
entity.getMethod() == null ? HttpMethod.POST : entity.getMethod(),
entity.getUrl() == null ? URI.create(registration.getProviderDetails().getTokenUri()) : entity.getUrl());
body,
entity.getHeaders(),
entity.getMethod() == null ? HttpMethod.POST : entity.getMethod(),
entity.getUrl() == null ? URI.create(registration.getProviderDetails().getTokenUri()) : entity.getUrl()
);
}
private MultiValueMap<String, String> cloneBody(MultiValueMap<?, ?> existingBody) {

View File

@@ -68,6 +68,21 @@ public class ViewController {
return "channels";
}
@org.springframework.web.bind.annotation.GetMapping("/settings")
public String settingsView(OAuth2AuthenticationToken oauthToken, Model model) {
String sessionUsername = OauthSessionUser.from(oauthToken).login();
authorizationService.userIsSystemAdministratorOrThrowHttpError(sessionUsername);
LOG.info("Rendering settings for {}", sessionUsername);
Settings settings = settingsService.get();
try {
model.addAttribute("settingsJson", objectMapper.writeValueAsString(settings));
} catch (JsonProcessingException e) {
LOG.error("Failed to serialize settings for settings view", e);
throw new ResponseStatusException(INTERNAL_SERVER_ERROR, "Failed to serialize settings");
}
return "settings";
}
@org.springframework.web.bind.annotation.GetMapping("/view/{broadcaster}/admin")
public String adminView(@org.springframework.web.bind.annotation.PathVariable("broadcaster") String broadcaster,
OAuth2AuthenticationToken oauthToken,

View File

@@ -16,15 +16,16 @@ import java.util.UUID;
public class Asset {
@Id
private String id;
@Column(nullable = false)
private String broadcaster;
@Column(nullable = false)
private String name;
@Column(columnDefinition = "TEXT")
@Column(columnDefinition = "TEXT", nullable = false)
private String url;
@Column(columnDefinition = "TEXT", nullable = false)
private String preview;
@Column(nullable = false)
private Instant createdAt;
private double x;
private double y;
private double width;
@@ -34,8 +35,6 @@ public class Asset {
private Boolean muted;
private String mediaType;
private String originalMediaType;
@Column(columnDefinition = "TEXT")
private String preview;
private Integer zIndex;
private Boolean audioLoop;
private Integer audioDelayMillis;
@@ -43,7 +42,6 @@ public class Asset {
private Double audioPitch;
private Double audioVolume;
private boolean hidden;
private Instant createdAt;
public Asset() {
}
@@ -61,7 +59,7 @@ public class Asset {
this.speed = 1.0;
this.muted = false;
this.zIndex = 1;
this.hidden = false;
this.hidden = true;
this.createdAt = Instant.now();
}

View File

@@ -44,12 +44,13 @@ public class AssetEvent {
return event;
}
public static AssetEvent visibility(String channel, AssetPatch patch) {
public static AssetEvent visibility(String channel, AssetPatch patch, AssetView asset) {
AssetEvent event = new AssetEvent();
event.type = Type.VISIBILITY;
event.channel = channel;
event.patch = patch;
event.assetId = patch.id();
event.payload = asset;
return event;
}