From 1d48b7d5e7da1aaf1ca3981cceee1958c621369d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kr=C3=BChlmann?= Date: Tue, 27 Jan 2026 23:15:29 +0100 Subject: [PATCH] Major refactor --- pom.xml | 6 +++ .../config/AuditLogDataSourceConfig.java | 8 ++-- .../config/PrimaryDataSourceConfig.java | 4 +- .../SQLiteOAuth2AuthorizedClientService.java | 2 - .../imgfloat/config/SecurityConfig.java | 8 ++-- .../config/SystemEnvironmentValidator.java | 11 ++--- ...zationCodeGrantRequestEntityConverter.java | 1 + ...TwitchOAuth2ErrorResponseErrorHandler.java | 23 ++++------ .../controller/ChannelApiController.java | 42 +++++++++---------- .../ScriptMarketplaceController.java | 6 +-- .../controller/SettingsApiController.java | 2 +- .../imgfloat/controller/ViewController.java | 8 ---- .../model/api/request/AdminRequest.java | 14 +------ .../api/request/CanvasSettingsRequest.java | 13 +----- .../request/ChannelScriptSettingsRequest.java | 13 ------ .../model/api/request/CodeAssetRequest.java | 11 +++-- .../ScriptMarketplaceImportRequest.java | 14 +------ .../model/api/request/TransformRequest.java | 16 ------- .../model/api/response/CanvasEvent.java | 11 ----- .../imgfloat/model/db/imgfloat/Asset.java | 12 ------ .../imgfloat/model/db/imgfloat/Channel.java | 39 ++++++++--------- .../db/imgfloat/MarketplaceScriptHeart.java | 4 -- .../repository/audit/AuditLogRepository.java | 2 - .../imgfloat/service/AssetCleanupService.java | 1 - .../imgfloat/service/AssetStorageService.java | 2 +- .../imgfloat/service/AuditLogService.java | 14 ------- .../service/ChannelDirectoryService.java | 15 ++++--- .../imgfloat/service/EmoteSyncScheduler.java | 5 ++- .../imgfloat/service/GitInfoService.java | 4 -- .../service/MarketplaceScriptSeedLoader.java | 15 +++---- .../service/TwitchAppAccessTokenService.java | 8 ++-- .../imgfloat/service/VersionService.java | 31 +------------- .../imgfloat/service/media/AssetContent.java | 3 ++ .../media/MediaOptimizationService.java | 8 +--- .../service/media/OptimizedAsset.java | 3 ++ .../imgfloat/ChannelDirectoryServiceTest.java | 34 +++++++-------- ...onCodeGrantRequestEntityConverterTest.java | 18 ++++---- ...chOAuth2ErrorResponseErrorHandlerTest.java | 2 +- 38 files changed, 140 insertions(+), 293 deletions(-) diff --git a/pom.xml b/pom.xml index 7a2e2fe..d0a3708 100644 --- a/pom.xml +++ b/pom.xml @@ -131,6 +131,12 @@ spring-security-test test + + org.jetbrains + annotations + 13.0 + compile + diff --git a/src/main/java/dev/kruhlmann/imgfloat/config/AuditLogDataSourceConfig.java b/src/main/java/dev/kruhlmann/imgfloat/config/AuditLogDataSourceConfig.java index f7e33c4..e6dfb2d 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/config/AuditLogDataSourceConfig.java +++ b/src/main/java/dev/kruhlmann/imgfloat/config/AuditLogDataSourceConfig.java @@ -11,16 +11,18 @@ import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.autoconfigure.orm.jpa.HibernateProperties; import org.springframework.boot.autoconfigure.orm.jpa.HibernateSettings; import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties; -import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; @Configuration +@EnableTransactionManagement @EnableJpaRepositories( basePackages = "dev.kruhlmann.imgfloat.repository.audit", entityManagerFactoryRef = "auditEntityManagerFactory", @@ -36,9 +38,7 @@ public class AuditLogDataSourceConfig { @Bean @ConfigurationProperties("imgfloat.audit.datasource.hikari") - public HikariDataSource auditDataSource( - @Qualifier("auditDataSourceProperties") DataSourceProperties properties - ) { + public HikariDataSource auditDataSource(@Qualifier("auditDataSourceProperties") DataSourceProperties properties) { return properties.initializeDataSourceBuilder().type(HikariDataSource.class).build(); } diff --git a/src/main/java/dev/kruhlmann/imgfloat/config/PrimaryDataSourceConfig.java b/src/main/java/dev/kruhlmann/imgfloat/config/PrimaryDataSourceConfig.java index 0a3848f..4dad34a 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/config/PrimaryDataSourceConfig.java +++ b/src/main/java/dev/kruhlmann/imgfloat/config/PrimaryDataSourceConfig.java @@ -27,9 +27,7 @@ import org.springframework.transaction.PlatformTransactionManager; @Configuration @EnableJpaRepositories( basePackages = "dev.kruhlmann.imgfloat.repository", - excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = AuditLogRepository.class), - entityManagerFactoryRef = "entityManagerFactory", - transactionManagerRef = "transactionManager" + excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = AuditLogRepository.class) ) public class PrimaryDataSourceConfig { diff --git a/src/main/java/dev/kruhlmann/imgfloat/config/SQLiteOAuth2AuthorizedClientService.java b/src/main/java/dev/kruhlmann/imgfloat/config/SQLiteOAuth2AuthorizedClientService.java index c89d36f..f551fbd 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/config/SQLiteOAuth2AuthorizedClientService.java +++ b/src/main/java/dev/kruhlmann/imgfloat/config/SQLiteOAuth2AuthorizedClientService.java @@ -24,7 +24,6 @@ public class SQLiteOAuth2AuthorizedClientService implements OAuth2AuthorizedClie private static final String TABLE_NAME = "oauth2_authorized_client"; private final JdbcOperations jdbcOperations; - private final ClientRegistrationRepository clientRegistrationRepository; private final RowMapper rowMapper; private final OAuthTokenCipher tokenCipher; @@ -41,7 +40,6 @@ public class SQLiteOAuth2AuthorizedClientService implements OAuth2AuthorizedClie OAuthTokenCipher tokenCipher ) { this.jdbcOperations = jdbcOperations; - this.clientRegistrationRepository = clientRegistrationRepository; this.tokenCipher = tokenCipher; this.rowMapper = (rs, rowNum) -> { String registrationId = rs.getString("client_registration_id"); diff --git a/src/main/java/dev/kruhlmann/imgfloat/config/SecurityConfig.java b/src/main/java/dev/kruhlmann/imgfloat/config/SecurityConfig.java index d6e2562..e1f387f 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/config/SecurityConfig.java +++ b/src/main/java/dev/kruhlmann/imgfloat/config/SecurityConfig.java @@ -4,6 +4,7 @@ import jakarta.servlet.FilterChain; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Bean; @@ -154,11 +155,12 @@ public class SecurityConfig { @Bean OncePerRequestFilter csrfTokenCookieFilter() { return new OncePerRequestFilter() { + @NotNull @Override protected void doFilterInternal( - HttpServletRequest request, - HttpServletResponse response, - FilterChain filterChain + @NotNull HttpServletRequest request, + @NotNull HttpServletResponse response, + @NotNull FilterChain filterChain ) throws java.io.IOException, jakarta.servlet.ServletException { CsrfToken csrfToken = (CsrfToken) request.getAttribute("_csrf"); if (csrfToken == null) { diff --git a/src/main/java/dev/kruhlmann/imgfloat/config/SystemEnvironmentValidator.java b/src/main/java/dev/kruhlmann/imgfloat/config/SystemEnvironmentValidator.java index 3a3dbf0..2110cbf 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/config/SystemEnvironmentValidator.java +++ b/src/main/java/dev/kruhlmann/imgfloat/config/SystemEnvironmentValidator.java @@ -52,9 +52,6 @@ public class SystemEnvironmentValidator { @Value("${IMGFLOAT_GITHUB_CLIENT_VERSION:#{null}}") private String githubClientVersion; - private long maxUploadBytes; - private long maxRequestBytes; - public SystemEnvironmentValidator(Environment environment) { this.environment = environment; } @@ -72,8 +69,8 @@ public class SystemEnvironmentValidator { StringBuilder missing = new StringBuilder(); - maxUploadBytes = DataSize.parse(springMaxFileSize).toBytes(); - maxRequestBytes = DataSize.parse(springMaxRequestSize).toBytes(); + long maxUploadBytes = DataSize.parse(springMaxFileSize).toBytes(); + long maxRequestBytes = DataSize.parse(springMaxRequestSize).toBytes(); checkUnsignedNumeric(maxUploadBytes, "SPRING_SERVLET_MULTIPART_MAX_FILE_SIZE", missing); checkUnsignedNumeric(maxRequestBytes, "SPRING_SERVLET_MULTIPART_MAX_REQUEST_SIZE", missing); checkString(twitchClientId, "TWITCH_CLIENT_ID", missing); @@ -107,7 +104,7 @@ public class SystemEnvironmentValidator { } private void checkString(String value, String name, StringBuilder missing) { - if (value != null && StringUtils.hasText(value)) { + if (StringUtils.hasText(value)) { return; } missing.append(" - ").append(name).append("\n"); @@ -121,7 +118,7 @@ public class SystemEnvironmentValidator { } private String redact(String value) { - if (value != null && StringUtils.hasText(value)) { + if (StringUtils.hasText(value)) { return "**************"; } return ""; diff --git a/src/main/java/dev/kruhlmann/imgfloat/config/TwitchAuthorizationCodeGrantRequestEntityConverter.java b/src/main/java/dev/kruhlmann/imgfloat/config/TwitchAuthorizationCodeGrantRequestEntityConverter.java index a14f9f7..52ca7d9 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/config/TwitchAuthorizationCodeGrantRequestEntityConverter.java +++ b/src/main/java/dev/kruhlmann/imgfloat/config/TwitchAuthorizationCodeGrantRequestEntityConverter.java @@ -27,6 +27,7 @@ final class TwitchAuthorizationCodeGrantRequestEntityConverter @Override public @Nullable RequestEntity convert(@Nullable OAuth2AuthorizationCodeGrantRequest request) { + assert request != null; RequestEntity entity = delegate.convert(request); if (entity == null || !(entity.getBody() instanceof MultiValueMap existingBody)) { return entity; diff --git a/src/main/java/dev/kruhlmann/imgfloat/config/TwitchOAuth2ErrorResponseErrorHandler.java b/src/main/java/dev/kruhlmann/imgfloat/config/TwitchOAuth2ErrorResponseErrorHandler.java index 88659a3..03b78f1 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/config/TwitchOAuth2ErrorResponseErrorHandler.java +++ b/src/main/java/dev/kruhlmann/imgfloat/config/TwitchOAuth2ErrorResponseErrorHandler.java @@ -3,6 +3,8 @@ package dev.kruhlmann.imgfloat.config; import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; + +import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.client.ClientHttpResponse; @@ -58,26 +60,15 @@ class TwitchOAuth2ErrorResponseErrorHandler extends OAuth2ErrorResponseErrorHand return new OAuth2AuthorizationException(oauth2Error, ex); } - private static final class CachedBodyClientHttpResponse implements ClientHttpResponse { - - private final ClientHttpResponse delegate; - private final byte[] body; - - private CachedBodyClientHttpResponse(ClientHttpResponse delegate, byte[] body) { - this.delegate = delegate; - this.body = body; - } + private record CachedBodyClientHttpResponse(ClientHttpResponse delegate, byte[] body) implements ClientHttpResponse { + @NotNull @Override public org.springframework.http.HttpStatusCode getStatusCode() throws IOException { return delegate.getStatusCode(); } - @Override - public int getRawStatusCode() throws IOException { - return delegate.getRawStatusCode(); - } - + @NotNull @Override public String getStatusText() throws IOException { return delegate.getStatusText(); @@ -88,11 +79,13 @@ class TwitchOAuth2ErrorResponseErrorHandler extends OAuth2ErrorResponseErrorHand delegate.close(); } + @NotNull @Override - public java.io.InputStream getBody() throws IOException { + public java.io.InputStream getBody() { return new ByteArrayInputStream(body); } + @NotNull @Override public org.springframework.http.HttpHeaders getHeaders() { return delegate.getHeaders(); diff --git a/src/main/java/dev/kruhlmann/imgfloat/controller/ChannelApiController.java b/src/main/java/dev/kruhlmann/imgfloat/controller/ChannelApiController.java index 52b1d0e..d6b8777 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/controller/ChannelApiController.java +++ b/src/main/java/dev/kruhlmann/imgfloat/controller/ChannelApiController.java @@ -1,7 +1,6 @@ package dev.kruhlmann.imgfloat.controller; import static org.springframework.http.HttpStatus.BAD_REQUEST; -import static org.springframework.http.HttpStatus.FORBIDDEN; import static org.springframework.http.HttpStatus.NOT_FOUND; import dev.kruhlmann.imgfloat.model.api.request.AdminRequest; @@ -32,10 +31,13 @@ import org.slf4j.LoggerFactory; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.lang.Nullable; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; +import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; +import org.springframework.security.oauth2.core.AbstractOAuth2Token; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -83,10 +85,10 @@ public class ChannelApiController { String sessionUsername = OauthSessionUser.from(oauthToken).login(); String logBroadcaster = LogSanitizer.sanitize(broadcaster); String logSessionUsername = LogSanitizer.sanitize(sessionUsername); - String logRequestUsername = LogSanitizer.sanitize(request.getUsername()); + String logRequestUsername = LogSanitizer.sanitize(request.username()); authorizationService.userMatchesSessionUsernameOrThrowHttpError(broadcaster, sessionUsername); LOG.info("User {} adding admin {} to {}", logSessionUsername, logRequestUsername, logBroadcaster); - boolean added = channelDirectoryService.addAdmin(broadcaster, request.getUsername(), sessionUsername); + boolean added = channelDirectoryService.addAdmin(broadcaster, request.username(), sessionUsername); if (!added) { LOG.info("User {} already admin for {} or could not be added", logRequestUsername, logBroadcaster); } @@ -106,14 +108,14 @@ public class ChannelApiController { LOG.debug("Listing admins for {} by {}", logBroadcaster, logSessionUsername); var channel = channelDirectoryService.getOrCreateChannel(broadcaster); List admins = channel.getAdmins().stream().sorted(Comparator.naturalOrder()).toList(); - OAuth2AuthorizedClient authorizedClient = resolveAuthorizedClient(oauthToken, null, request); + OAuth2AuthorizedClient authorizedClient = resolveAuthorizedClient(oauthToken, request); String accessToken = Optional.ofNullable(authorizedClient) .map(OAuth2AuthorizedClient::getAccessToken) - .map((token) -> token.getTokenValue()) + .map(AbstractOAuth2Token::getTokenValue) .orElse(null); String clientId = Optional.ofNullable(authorizedClient) .map(OAuth2AuthorizedClient::getClientRegistration) - .map((registration) -> registration.getClientId()) + .map(ClientRegistration::getClientId) .orElse(null); return twitchUserLookupService.fetchProfiles(admins, accessToken, clientId); } @@ -130,7 +132,7 @@ public class ChannelApiController { authorizationService.userMatchesSessionUsernameOrThrowHttpError(broadcaster, sessionUsername); LOG.debug("Listing admin suggestions for {} by {}", logBroadcaster, logSessionUsername); var channel = channelDirectoryService.getOrCreateChannel(broadcaster); - OAuth2AuthorizedClient authorizedClient = resolveAuthorizedClient(oauthToken, null, request); + OAuth2AuthorizedClient authorizedClient = resolveAuthorizedClient(oauthToken, request); if (authorizedClient == null) { LOG.warn( @@ -140,13 +142,13 @@ public class ChannelApiController { ); return List.of(); } - String accessToken = Optional.ofNullable(authorizedClient) + String accessToken = Optional.of(authorizedClient) .map(OAuth2AuthorizedClient::getAccessToken) - .map((token) -> token.getTokenValue()) + .map(AbstractOAuth2Token::getTokenValue) .orElse(null); - String clientId = Optional.ofNullable(authorizedClient) + String clientId = Optional.of(authorizedClient) .map(OAuth2AuthorizedClient::getClientRegistration) - .map((registration) -> registration.getClientId()) + .map(ClientRegistration::getClientId) .orElse(null); if (accessToken == null || accessToken.isBlank() || clientId == null || clientId.isBlank()) { LOG.warn( @@ -406,7 +408,7 @@ public class ChannelApiController { .contentType(MediaType.parseMediaType(content.mediaType())) .body(content.bytes()) ) - .orElseThrow(() -> createAsset404()); + .orElseThrow(this::createAsset404); } @GetMapping("/script-assets/{assetId}/attachments/{attachmentId}/content") @@ -433,7 +435,7 @@ public class ChannelApiController { .contentType(MediaType.parseMediaType(content.mediaType())) .body(content.bytes()) ) - .orElseThrow(() -> createAsset404()); + .orElseThrow(this::createAsset404); } @GetMapping("/assets/{assetId}/logo") @@ -456,7 +458,7 @@ public class ChannelApiController { .contentType(MediaType.parseMediaType(content.mediaType())) .body(content.bytes()) ) - .orElseThrow(() -> createAsset404()); + .orElseThrow(this::createAsset404); } @GetMapping("/assets/{assetId}/preview") @@ -475,13 +477,12 @@ public class ChannelApiController { .contentType(MediaType.parseMediaType(content.mediaType())) .body(content.bytes()) ) - .orElseThrow(() -> createAsset404()); + .orElseThrow(this::createAsset404); } private String contentDispositionFor(String mediaType) { if ( - mediaType != null && - dev.kruhlmann.imgfloat.service.media.MediaDetectionService.isInlineDisplayType(mediaType) + dev.kruhlmann.imgfloat.service.media.MediaDetectionService.isInlineDisplayType(mediaType) ) { return "inline"; } @@ -616,14 +617,11 @@ public class ChannelApiController { } private OAuth2AuthorizedClient resolveAuthorizedClient( - OAuth2AuthenticationToken oauthToken, - OAuth2AuthorizedClient authorizedClient, + @Nullable OAuth2AuthenticationToken oauthToken, HttpServletRequest request ) { - if (authorizedClient != null) { - return authorizedClient; - } if (oauthToken == null) { + LOG.error("Attempt to resolve authorized client without oauth token"); return null; } OAuth2AuthorizedClient sessionClient = authorizedClientRepository.loadAuthorizedClient( diff --git a/src/main/java/dev/kruhlmann/imgfloat/controller/ScriptMarketplaceController.java b/src/main/java/dev/kruhlmann/imgfloat/controller/ScriptMarketplaceController.java index 07135b9..abc1621 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/controller/ScriptMarketplaceController.java +++ b/src/main/java/dev/kruhlmann/imgfloat/controller/ScriptMarketplaceController.java @@ -78,14 +78,14 @@ public class ScriptMarketplaceController { ) { String sessionUsername = OauthSessionUser.from(oauthToken).login(); authorizationService.userIsBroadcasterOrChannelAdminForBroadcasterOrThrowHttpError( - request.getTargetBroadcaster(), + request.targetBroadcaster(), sessionUsername ); String logScriptId = LogSanitizer.sanitize(scriptId); - String logTarget = LogSanitizer.sanitize(request.getTargetBroadcaster()); + String logTarget = LogSanitizer.sanitize(request.targetBroadcaster()); LOG.info("Importing marketplace script {} into {}", logScriptId, logTarget); return channelDirectoryService - .importMarketplaceScript(request.getTargetBroadcaster(), scriptId, sessionUsername) + .importMarketplaceScript(request.targetBroadcaster(), scriptId, sessionUsername) .map(ResponseEntity::ok) .orElseThrow(() -> new ResponseStatusException(BAD_REQUEST, "Unable to import script")); } diff --git a/src/main/java/dev/kruhlmann/imgfloat/controller/SettingsApiController.java b/src/main/java/dev/kruhlmann/imgfloat/controller/SettingsApiController.java index c869f3b..eb964eb 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/controller/SettingsApiController.java +++ b/src/main/java/dev/kruhlmann/imgfloat/controller/SettingsApiController.java @@ -39,7 +39,7 @@ public class SettingsApiController { authorizationService.userIsSystemAdministratorOrThrowHttpError(sessionUsername); Settings currentSettings = settingsService.get(); - LOG.info("Sytem administrator settings change request"); + LOG.info("System administrator settings change request"); settingsService.logSettings("From: ", currentSettings); settingsService.logSettings("To: ", newSettings); diff --git a/src/main/java/dev/kruhlmann/imgfloat/controller/ViewController.java b/src/main/java/dev/kruhlmann/imgfloat/controller/ViewController.java index 6234ec0..94455a7 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/controller/ViewController.java +++ b/src/main/java/dev/kruhlmann/imgfloat/controller/ViewController.java @@ -83,14 +83,6 @@ public class ViewController { return "index"; } - @org.springframework.web.bind.annotation.GetMapping("/channels") - public String channelDirectory(Model model) { - LOG.info("Rendering channel directory"); - addStagingAttribute(model); - addVersionAttributes(model); - return "channels"; - } - @org.springframework.web.bind.annotation.GetMapping("/terms") public String termsOfUse(Model model) { LOG.info("Rendering terms of use"); diff --git a/src/main/java/dev/kruhlmann/imgfloat/model/api/request/AdminRequest.java b/src/main/java/dev/kruhlmann/imgfloat/model/api/request/AdminRequest.java index 81ea3a9..1968c78 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/model/api/request/AdminRequest.java +++ b/src/main/java/dev/kruhlmann/imgfloat/model/api/request/AdminRequest.java @@ -2,16 +2,4 @@ package dev.kruhlmann.imgfloat.model.api.request; import jakarta.validation.constraints.NotBlank; -public class AdminRequest { - - @NotBlank - private String username; - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } -} +public record AdminRequest(@NotBlank String username) {} \ No newline at end of file diff --git a/src/main/java/dev/kruhlmann/imgfloat/model/api/request/CanvasSettingsRequest.java b/src/main/java/dev/kruhlmann/imgfloat/model/api/request/CanvasSettingsRequest.java index 8eef4a2..e14e814 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/model/api/request/CanvasSettingsRequest.java +++ b/src/main/java/dev/kruhlmann/imgfloat/model/api/request/CanvasSettingsRequest.java @@ -5,12 +5,10 @@ import jakarta.validation.constraints.Positive; public class CanvasSettingsRequest { @Positive - private double width; + private final double width; @Positive - private double height; - - public CanvasSettingsRequest() {} + private final double height; public CanvasSettingsRequest(double width, double height) { this.width = width; @@ -21,15 +19,8 @@ public class CanvasSettingsRequest { return width; } - public void setWidth(double width) { - this.width = width; - } - public double getHeight() { return height; } - public void setHeight(double height) { - this.height = height; - } } diff --git a/src/main/java/dev/kruhlmann/imgfloat/model/api/request/ChannelScriptSettingsRequest.java b/src/main/java/dev/kruhlmann/imgfloat/model/api/request/ChannelScriptSettingsRequest.java index 1ca47db..0483ee7 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/model/api/request/ChannelScriptSettingsRequest.java +++ b/src/main/java/dev/kruhlmann/imgfloat/model/api/request/ChannelScriptSettingsRequest.java @@ -6,8 +6,6 @@ public class ChannelScriptSettingsRequest { private boolean allowSevenTvEmotesForAssets = true; private boolean allowScriptChatAccess = true; - public ChannelScriptSettingsRequest() {} - public ChannelScriptSettingsRequest( boolean allowChannelEmotesForAssets, boolean allowSevenTvEmotesForAssets, @@ -22,23 +20,12 @@ public class ChannelScriptSettingsRequest { return allowChannelEmotesForAssets; } - public void setAllowChannelEmotesForAssets(boolean allowChannelEmotesForAssets) { - this.allowChannelEmotesForAssets = allowChannelEmotesForAssets; - } - public boolean isAllowSevenTvEmotesForAssets() { return allowSevenTvEmotesForAssets; } - public void setAllowSevenTvEmotesForAssets(boolean allowSevenTvEmotesForAssets) { - this.allowSevenTvEmotesForAssets = allowSevenTvEmotesForAssets; - } - public boolean isAllowScriptChatAccess() { return allowScriptChatAccess; } - public void setAllowScriptChatAccess(boolean allowScriptChatAccess) { - this.allowScriptChatAccess = allowScriptChatAccess; - } } diff --git a/src/main/java/dev/kruhlmann/imgfloat/model/api/request/CodeAssetRequest.java b/src/main/java/dev/kruhlmann/imgfloat/model/api/request/CodeAssetRequest.java index c2d7cf3..e12401c 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/model/api/request/CodeAssetRequest.java +++ b/src/main/java/dev/kruhlmann/imgfloat/model/api/request/CodeAssetRequest.java @@ -1,5 +1,6 @@ package dev.kruhlmann.imgfloat.model.api.request; +import jakarta.annotation.Nullable; import jakarta.validation.constraints.NotBlank; public class CodeAssetRequest { @@ -12,6 +13,7 @@ public class CodeAssetRequest { private String description; + @Nullable private Boolean isPublic; private java.util.List allowedDomains; @@ -40,14 +42,11 @@ public class CodeAssetRequest { this.description = description; } + @Nullable public Boolean getIsPublic() { return isPublic; } - public void setIsPublic(Boolean isPublic) { - this.isPublic = isPublic; - } - public java.util.List getAllowedDomains() { return allowedDomains; } @@ -55,4 +54,8 @@ public class CodeAssetRequest { public void setAllowedDomains(java.util.List allowedDomains) { this.allowedDomains = allowedDomains; } + + public void setPublic(@Nullable Boolean aPublic) { + isPublic = aPublic; + } } diff --git a/src/main/java/dev/kruhlmann/imgfloat/model/api/request/ScriptMarketplaceImportRequest.java b/src/main/java/dev/kruhlmann/imgfloat/model/api/request/ScriptMarketplaceImportRequest.java index 4e74947..6c19f68 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/model/api/request/ScriptMarketplaceImportRequest.java +++ b/src/main/java/dev/kruhlmann/imgfloat/model/api/request/ScriptMarketplaceImportRequest.java @@ -2,16 +2,4 @@ package dev.kruhlmann.imgfloat.model.api.request; import jakarta.validation.constraints.NotBlank; -public class ScriptMarketplaceImportRequest { - - @NotBlank - private String targetBroadcaster; - - public String getTargetBroadcaster() { - return targetBroadcaster; - } - - public void setTargetBroadcaster(String targetBroadcaster) { - this.targetBroadcaster = targetBroadcaster; - } -} +public record ScriptMarketplaceImportRequest(@NotBlank String targetBroadcaster) { } diff --git a/src/main/java/dev/kruhlmann/imgfloat/model/api/request/TransformRequest.java b/src/main/java/dev/kruhlmann/imgfloat/model/api/request/TransformRequest.java index 7b13869..c6e6a3d 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/model/api/request/TransformRequest.java +++ b/src/main/java/dev/kruhlmann/imgfloat/model/api/request/TransformRequest.java @@ -112,34 +112,18 @@ public class TransformRequest { return audioLoop; } - public void setAudioLoop(Boolean audioLoop) { - this.audioLoop = audioLoop; - } - public Integer getAudioDelayMillis() { return audioDelayMillis; } - public void setAudioDelayMillis(Integer audioDelayMillis) { - this.audioDelayMillis = audioDelayMillis; - } - public Double getAudioSpeed() { return audioSpeed; } - public void setAudioSpeed(Double audioSpeed) { - this.audioSpeed = audioSpeed; - } - public Double getAudioPitch() { return audioPitch; } - public void setAudioPitch(Double audioPitch) { - this.audioPitch = audioPitch; - } - public Double getAudioVolume() { return audioVolume; } diff --git a/src/main/java/dev/kruhlmann/imgfloat/model/api/response/CanvasEvent.java b/src/main/java/dev/kruhlmann/imgfloat/model/api/response/CanvasEvent.java index 3a62fb8..3af8118 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/model/api/response/CanvasEvent.java +++ b/src/main/java/dev/kruhlmann/imgfloat/model/api/response/CanvasEvent.java @@ -19,15 +19,4 @@ public class CanvasEvent { return event; } - public Type getType() { - return type; - } - - public String getChannel() { - return channel; - } - - public CanvasSettingsRequest getPayload() { - return payload; - } } diff --git a/src/main/java/dev/kruhlmann/imgfloat/model/db/imgfloat/Asset.java b/src/main/java/dev/kruhlmann/imgfloat/model/db/imgfloat/Asset.java index d3f5984..39a23f0 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/model/db/imgfloat/Asset.java +++ b/src/main/java/dev/kruhlmann/imgfloat/model/db/imgfloat/Asset.java @@ -83,26 +83,14 @@ public class Asset { return assetType == null ? AssetType.OTHER : assetType; } - public void setAssetType(AssetType assetType) { - this.assetType = assetType == null ? AssetType.OTHER : assetType; - } - public Instant getCreatedAt() { return createdAt; } - public void setCreatedAt(Instant createdAt) { - this.createdAt = createdAt; - } - public Instant getUpdatedAt() { return updatedAt; } - public void setUpdatedAt(Instant updatedAt) { - this.updatedAt = updatedAt; - } - public Integer getDisplayOrder() { return displayOrder; } diff --git a/src/main/java/dev/kruhlmann/imgfloat/model/db/imgfloat/Channel.java b/src/main/java/dev/kruhlmann/imgfloat/model/db/imgfloat/Channel.java index dfe8cff..b30852f 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/model/db/imgfloat/Channel.java +++ b/src/main/java/dev/kruhlmann/imgfloat/model/db/imgfloat/Channel.java @@ -15,7 +15,6 @@ import java.util.Collections; import java.util.HashSet; import java.util.Locale; import java.util.Set; -import java.util.stream.Collectors; @Entity @Table(name = "channels") @@ -27,7 +26,7 @@ public class Channel { @ElementCollection(fetch = FetchType.EAGER) @CollectionTable(name = "channel_admins", joinColumns = @JoinColumn(name = "channel_id")) @Column(name = "admin_username") - private Set admins = new HashSet<>(); + private final Set admins = new HashSet<>(); private double canvasWidth = 1920; @@ -52,8 +51,6 @@ public class Channel { public Channel(String broadcaster) { this.broadcaster = normalize(broadcaster); - this.canvasWidth = 1920; - this.canvasHeight = 1080; } public String getBroadcaster() { @@ -112,24 +109,6 @@ public class Channel { this.allowScriptChatAccess = allowScriptChatAccess; } - @PrePersist - @PreUpdate - public void normalizeFields() { - Instant now = Instant.now(); - if (createdAt == null) { - createdAt = now; - } - updatedAt = now; - this.broadcaster = normalize(broadcaster); - this.admins = admins.stream().map(Channel::normalize).collect(Collectors.toSet()); - if (canvasWidth <= 0) { - canvasWidth = 1920; - } - if (canvasHeight <= 0) { - canvasHeight = 1080; - } - } - public Instant getCreatedAt() { return createdAt; } @@ -138,6 +117,22 @@ public class Channel { return updatedAt; } + @PrePersist + private void ensureCreatedAt() { + Instant now = Instant.now(); + if (createdAt == null) { + createdAt = now; + } + if (updatedAt == null) { + updatedAt = now; + } + } + + @PreUpdate + private void touchUpdatedAt() { + updatedAt = Instant.now(); + } + private static String normalize(String value) { return value == null ? null : value.toLowerCase(Locale.ROOT); } diff --git a/src/main/java/dev/kruhlmann/imgfloat/model/db/imgfloat/MarketplaceScriptHeart.java b/src/main/java/dev/kruhlmann/imgfloat/model/db/imgfloat/MarketplaceScriptHeart.java index 35b03ed..5113e62 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/model/db/imgfloat/MarketplaceScriptHeart.java +++ b/src/main/java/dev/kruhlmann/imgfloat/model/db/imgfloat/MarketplaceScriptHeart.java @@ -30,10 +30,6 @@ public class MarketplaceScriptHeart { return scriptId; } - public void setScriptId(String scriptId) { - this.scriptId = scriptId; - } - public String getUsername() { return username; } diff --git a/src/main/java/dev/kruhlmann/imgfloat/repository/audit/AuditLogRepository.java b/src/main/java/dev/kruhlmann/imgfloat/repository/audit/AuditLogRepository.java index a25514f..5d051a8 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/repository/audit/AuditLogRepository.java +++ b/src/main/java/dev/kruhlmann/imgfloat/repository/audit/AuditLogRepository.java @@ -1,7 +1,6 @@ package dev.kruhlmann.imgfloat.repository.audit; import dev.kruhlmann.imgfloat.model.db.audit.AuditLogEntry; -import java.util.List; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; @@ -11,7 +10,6 @@ import org.springframework.stereotype.Repository; @Repository public interface AuditLogRepository extends JpaRepository { - List findTop200ByBroadcasterOrderByCreatedAtDesc(String broadcaster); @Query( """ diff --git a/src/main/java/dev/kruhlmann/imgfloat/service/AssetCleanupService.java b/src/main/java/dev/kruhlmann/imgfloat/service/AssetCleanupService.java index 0f1bd35..2649606 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/service/AssetCleanupService.java +++ b/src/main/java/dev/kruhlmann/imgfloat/service/AssetCleanupService.java @@ -2,7 +2,6 @@ package dev.kruhlmann.imgfloat.service; import dev.kruhlmann.imgfloat.model.db.imgfloat.Asset; import dev.kruhlmann.imgfloat.model.db.imgfloat.ScriptAsset; -import dev.kruhlmann.imgfloat.model.db.imgfloat.ScriptAssetAttachment; import dev.kruhlmann.imgfloat.repository.AssetRepository; import dev.kruhlmann.imgfloat.repository.ScriptAssetAttachmentRepository; import dev.kruhlmann.imgfloat.repository.ScriptAssetRepository; diff --git a/src/main/java/dev/kruhlmann/imgfloat/service/AssetStorageService.java b/src/main/java/dev/kruhlmann/imgfloat/service/AssetStorageService.java index 12030f3..ebcbfc8 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/service/AssetStorageService.java +++ b/src/main/java/dev/kruhlmann/imgfloat/service/AssetStorageService.java @@ -73,7 +73,7 @@ public class AssetStorageService { StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE ); - logger.info("Wrote asset to {}", file); + logger.info("Wrote asset preview to {}", file); } public Optional loadAssetFile(String broadcaster, String assetId, String mediaType) { diff --git a/src/main/java/dev/kruhlmann/imgfloat/service/AuditLogService.java b/src/main/java/dev/kruhlmann/imgfloat/service/AuditLogService.java index f284f43..e0b3f57 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/service/AuditLogService.java +++ b/src/main/java/dev/kruhlmann/imgfloat/service/AuditLogService.java @@ -1,10 +1,8 @@ package dev.kruhlmann.imgfloat.service; import dev.kruhlmann.imgfloat.model.db.audit.AuditLogEntry; -import dev.kruhlmann.imgfloat.model.api.response.AuditLogEntryView; import dev.kruhlmann.imgfloat.repository.audit.AuditLogRepository; import dev.kruhlmann.imgfloat.util.LogSanitizer; -import java.util.List; import java.util.Locale; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -54,18 +52,6 @@ public class AuditLogService { } } - public List listEntries(String broadcaster) { - String normalizedBroadcaster = normalize(broadcaster); - if (normalizedBroadcaster == null || normalizedBroadcaster.isBlank()) { - return List.of(); - } - return auditLogRepository - .findTop200ByBroadcasterOrderByCreatedAtDesc(normalizedBroadcaster) - .stream() - .map(AuditLogEntryView::fromEntry) - .toList(); - } - public Page listEntries( String broadcaster, String actor, diff --git a/src/main/java/dev/kruhlmann/imgfloat/service/ChannelDirectoryService.java b/src/main/java/dev/kruhlmann/imgfloat/service/ChannelDirectoryService.java index 1f415b9..610342e 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/service/ChannelDirectoryService.java +++ b/src/main/java/dev/kruhlmann/imgfloat/service/ChannelDirectoryService.java @@ -581,14 +581,14 @@ public class ChannelDirectoryService { } @Transactional - public Optional clearScriptLogo(String broadcaster, String assetId, String actor) { + public void clearScriptLogo(String broadcaster, String assetId, String actor) { Asset asset = requireScriptAssetForBroadcaster(broadcaster, assetId); ScriptAsset script = scriptAssetRepository .findById(asset.getId()) .orElseThrow(() -> new ResponseStatusException(BAD_REQUEST, "Asset is not a script")); String previousLogoFileId = script.getLogoFileId(); if (previousLogoFileId == null) { - return Optional.empty(); + return; } script.setLogoFileId(null); script.setAttachments(loadScriptAttachments(asset.getBroadcaster(), asset.getId(), null)); @@ -602,7 +602,6 @@ public class ChannelDirectoryService { "SCRIPT_LOGO_CLEARED", "Cleared script logo for " + script.getName() + " (" + asset.getId() + ")" ); - return Optional.of(view); } public List listMarketplaceScripts(String query, String sessionUsername) { @@ -804,7 +803,7 @@ public class ChannelDirectoryService { .findById(scriptId) .filter(ScriptAsset::isPublic) .map(ScriptAsset::getLogoFileId) - .flatMap((logoFileId) -> scriptAssetFileRepository.findById(logoFileId)) + .flatMap(scriptAssetFileRepository::findById) .flatMap((file) -> assetStorageService.loadAssetFileSafely(file.getBroadcaster(), file.getId(), file.getMediaType()) ); @@ -1065,7 +1064,7 @@ public class ChannelDirectoryService { .findById(asset.getId()) .orElseThrow(() -> new ResponseStatusException(BAD_REQUEST, "Asset is not a script")); Integer beforeOrder = asset.getDisplayOrder(); - List orderUpdates = List.of(); + List orderUpdates; if (req.getOrder() != null) { if (req.getOrder() < 1) { throw new ResponseStatusException(BAD_REQUEST, "Order must be >= 1"); @@ -1514,7 +1513,7 @@ public class ChannelDirectoryService { return scriptAssetRepository .findById(asset.getId()) .map(ScriptAsset::getLogoFileId) - .flatMap((logoFileId) -> scriptAssetFileRepository.findById(logoFileId)) + .flatMap(scriptAssetFileRepository::findById) .flatMap((file) -> assetStorageService.loadAssetFileSafely(file.getBroadcaster(), file.getId(), file.getMediaType()) ); @@ -1723,7 +1722,7 @@ public class ChannelDirectoryService { return assets .stream() .sorted( - Comparator.comparingInt((Asset asset) -> displayOrderValue(asset)) + Comparator.comparingInt(this::displayOrderValue) .reversed() .thenComparing(Asset::getCreatedAt, Comparator.nullsFirst(Comparator.naturalOrder())) ) @@ -1761,7 +1760,7 @@ public class ChannelDirectoryService { .stream() .filter((asset) -> types.contains(asset.getAssetType())) .sorted( - Comparator.comparingInt((Asset asset) -> displayOrderValue(asset)) + Comparator.comparingInt(this::displayOrderValue) .reversed() .thenComparing(Asset::getCreatedAt, Comparator.nullsFirst(Comparator.naturalOrder())) ) diff --git a/src/main/java/dev/kruhlmann/imgfloat/service/EmoteSyncScheduler.java b/src/main/java/dev/kruhlmann/imgfloat/service/EmoteSyncScheduler.java index a349408..1287ada 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/service/EmoteSyncScheduler.java +++ b/src/main/java/dev/kruhlmann/imgfloat/service/EmoteSyncScheduler.java @@ -50,9 +50,10 @@ public class EmoteSyncScheduler implements SchedulingConfigurer { private Trigger buildTrigger() { return (TriggerContext triggerContext) -> { - Instant lastCompletion = triggerContext.lastCompletionTime() == null + Instant lastCompletion = triggerContext.lastCompletion() == null ? Instant.now() - : triggerContext.lastCompletionTime().toInstant(); + : triggerContext.lastCompletion(); + assert lastCompletion != null; return lastCompletion.plus(Duration.ofMinutes(resolveIntervalMinutes())); }; } diff --git a/src/main/java/dev/kruhlmann/imgfloat/service/GitInfoService.java b/src/main/java/dev/kruhlmann/imgfloat/service/GitInfoService.java index 4d4e7fe..3d69692 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/service/GitInfoService.java +++ b/src/main/java/dev/kruhlmann/imgfloat/service/GitInfoService.java @@ -41,10 +41,6 @@ public class GitInfoService { this.commitUrlPrefix = normalize(commitUrlPrefix); } - public String getCommitSha() { - return commitSha; - } - public String getShortCommitSha() { return shortCommitSha; } diff --git a/src/main/java/dev/kruhlmann/imgfloat/service/MarketplaceScriptSeedLoader.java b/src/main/java/dev/kruhlmann/imgfloat/service/MarketplaceScriptSeedLoader.java index c35ff8b..2ec2251 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/service/MarketplaceScriptSeedLoader.java +++ b/src/main/java/dev/kruhlmann/imgfloat/service/MarketplaceScriptSeedLoader.java @@ -134,10 +134,7 @@ public class MarketplaceScriptSeedLoader { if (!Files.isDirectory(scriptDir)) { continue; } - SeedScript script = loadScriptDirectory(scriptDir).orElse(null); - if (script != null) { - loaded.add(script); - } + loadScriptDirectory(scriptDir).ifPresent(loaded::add); } } catch (IOException ex) { logger.warn("Failed to read marketplace script directory {}", rootPath, ex); @@ -179,7 +176,7 @@ public class MarketplaceScriptSeedLoader { broadcaster, sourceMediaType, logoMediaType, - Optional.ofNullable(sourcePath), + Optional.of(sourcePath), Optional.ofNullable(logoPath), allowedDomains, attachments, @@ -207,7 +204,7 @@ public class MarketplaceScriptSeedLoader { attachments.add( new SeedAttachment( name, - mediaType == null ? "application/octet-stream" : mediaType, + mediaType, attachment, new AtomicReference<>() ) @@ -348,7 +345,7 @@ public class MarketplaceScriptSeedLoader { } try { String content = Files.readString(path); - return JsonSupport.read(content, ScriptSeedMetadata.class); + return JsonSupport.read(content); } catch (IOException ex) { logger.warn("Failed to read marketplace metadata {}", path, ex); return null; @@ -362,8 +359,8 @@ public class MarketplaceScriptSeedLoader { private JsonSupport() {} - static T read(String payload, Class type) throws IOException { - return mapper().readValue(payload, type); + static T read(String payload) throws IOException { + return mapper().readValue(payload, (Class) ScriptSeedMetadata.class); } private static com.fasterxml.jackson.databind.ObjectMapper mapper() { diff --git a/src/main/java/dev/kruhlmann/imgfloat/service/TwitchAppAccessTokenService.java b/src/main/java/dev/kruhlmann/imgfloat/service/TwitchAppAccessTokenService.java index f517609..347344c 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/service/TwitchAppAccessTokenService.java +++ b/src/main/java/dev/kruhlmann/imgfloat/service/TwitchAppAccessTokenService.java @@ -42,12 +42,12 @@ public class TwitchAppAccessTokenService { return Optional.empty(); } AccessToken current = cachedToken; - if (current != null && !current.isExpired()) { + if (current != null && current.isActive()) { return Optional.of(current.token()); } synchronized (this) { AccessToken refreshed = cachedToken; - if (refreshed != null && !refreshed.isExpired()) { + if (refreshed != null && refreshed.isActive()) { return Optional.of(refreshed.token()); } cachedToken = requestToken(); @@ -91,8 +91,8 @@ public class TwitchAppAccessTokenService { } private record AccessToken(String token, Instant expiresAt) { - boolean isExpired() { - return expiresAt == null || expiresAt.isBefore(Instant.now()); + boolean isActive() { + return expiresAt != null && !expiresAt.isBefore(Instant.now()); } } diff --git a/src/main/java/dev/kruhlmann/imgfloat/service/VersionService.java b/src/main/java/dev/kruhlmann/imgfloat/service/VersionService.java index a28db9b..be8e4fe 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/service/VersionService.java +++ b/src/main/java/dev/kruhlmann/imgfloat/service/VersionService.java @@ -1,14 +1,10 @@ package dev.kruhlmann.imgfloat.service; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; @@ -17,32 +13,17 @@ import org.springframework.stereotype.Component; public class VersionService { private static final Logger LOG = LoggerFactory.getLogger(VersionService.class); - private static final Pattern PACKAGE_VERSION_PATTERN = Pattern.compile("\"version\"\\s*:\\s*\"([^\"]+)\""); private final String serverVersion; - private final String releaseVersion; - public VersionService() throws IOException { + public VersionService() { this.serverVersion = resolveServerVersion(); - this.releaseVersion = normalizeReleaseVersion(serverVersion); } public String getVersion() { return serverVersion; } - public String getReleaseVersion() { - return releaseVersion; - } - - public String getReleaseTag() { - if (releaseVersion == null || releaseVersion.isBlank()) { - throw new IllegalStateException("Release version is not available"); - } - String normalized = releaseVersion.startsWith("v") ? releaseVersion.substring(1) : releaseVersion; - return "v" + normalized; - } - private String resolveServerVersion() { String pomVersion = getPomVersion(); if (pomVersion != null && !pomVersion.isBlank()) { @@ -57,16 +38,6 @@ public class VersionService { throw new IllegalStateException("Release version is not available"); } - private String normalizeReleaseVersion(String baseVersion) throws IllegalStateException { - String normalized = baseVersion.trim(); - normalized = normalized.replaceFirst("(?i)^v", ""); - normalized = normalized.replaceFirst("-SNAPSHOT$", ""); - if (normalized.isBlank()) { - throw new IllegalStateException("Invalid version: " + baseVersion); - } - return normalized; - } - private String getPomVersion() { try ( var inputStream = getClass().getResourceAsStream("/META-INF/maven/dev.kruhlmann/imgfloat/pom.properties") diff --git a/src/main/java/dev/kruhlmann/imgfloat/service/media/AssetContent.java b/src/main/java/dev/kruhlmann/imgfloat/service/media/AssetContent.java index 8c74c61..3475810 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/service/media/AssetContent.java +++ b/src/main/java/dev/kruhlmann/imgfloat/service/media/AssetContent.java @@ -1,5 +1,7 @@ package dev.kruhlmann.imgfloat.service.media; +import jakarta.validation.constraints.NotNull; + import java.util.Arrays; import java.util.Objects; @@ -19,6 +21,7 @@ public record AssetContent(byte[] bytes, String mediaType) { return result; } + @NotNull @Override public String toString() { return "AssetContent{" + "bytes=" + Arrays.toString(bytes) + ", mediaType='" + mediaType + '\'' + '}'; diff --git a/src/main/java/dev/kruhlmann/imgfloat/service/media/MediaOptimizationService.java b/src/main/java/dev/kruhlmann/imgfloat/service/media/MediaOptimizationService.java index bead9ac..dc03e39 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/service/media/MediaOptimizationService.java +++ b/src/main/java/dev/kruhlmann/imgfloat/service/media/MediaOptimizationService.java @@ -42,11 +42,7 @@ public class MediaOptimizationService { } } if (mediaType.startsWith("image/")) { - OptimizedAsset imageAsset = optimizeImage(bytes, mediaType); - if (imageAsset == null) { - return null; - } - return imageAsset; + return optimizeImage(bytes, mediaType); } if (mediaType.startsWith("video/")) { @@ -86,7 +82,7 @@ public class MediaOptimizationService { return "image/png".equalsIgnoreCase(mediaType) && ApngDetector.isApng(bytes); } - private OptimizedAsset optimizeApng(byte[] bytes, String mediaType) throws IOException { + private OptimizedAsset optimizeApng(byte[] bytes, String mediaType) { return ffmpegService .transcodeApngToGif(bytes) .map(this::transcodeGifToVideo) diff --git a/src/main/java/dev/kruhlmann/imgfloat/service/media/OptimizedAsset.java b/src/main/java/dev/kruhlmann/imgfloat/service/media/OptimizedAsset.java index 7541b9f..5444313 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/service/media/OptimizedAsset.java +++ b/src/main/java/dev/kruhlmann/imgfloat/service/media/OptimizedAsset.java @@ -1,5 +1,7 @@ package dev.kruhlmann.imgfloat.service.media; +import jakarta.validation.constraints.NotNull; + import java.util.Arrays; import java.util.Objects; @@ -26,6 +28,7 @@ public record OptimizedAsset(byte[] bytes, String mediaType, int width, int heig return result; } + @NotNull @Override public String toString() { return ( diff --git a/src/test/java/dev/kruhlmann/imgfloat/ChannelDirectoryServiceTest.java b/src/test/java/dev/kruhlmann/imgfloat/ChannelDirectoryServiceTest.java index 7c2f6b8..b2a5062 100644 --- a/src/test/java/dev/kruhlmann/imgfloat/ChannelDirectoryServiceTest.java +++ b/src/test/java/dev/kruhlmann/imgfloat/ChannelDirectoryServiceTest.java @@ -64,12 +64,7 @@ class ChannelDirectoryServiceTest { private VisualAssetRepository visualAssetRepository; private AudioAssetRepository audioAssetRepository; private ScriptAssetRepository scriptAssetRepository; - private ScriptAssetAttachmentRepository scriptAssetAttachmentRepository; private ScriptAssetFileRepository scriptAssetFileRepository; - private MarketplaceScriptHeartRepository marketplaceScriptHeartRepository; - private SettingsService settingsService; - private MarketplaceScriptSeedLoader marketplaceScriptSeedLoader; - private AuditLogService auditLogService; @BeforeEach void setup() throws Exception { @@ -79,13 +74,13 @@ class ChannelDirectoryServiceTest { visualAssetRepository = mock(VisualAssetRepository.class); audioAssetRepository = mock(AudioAssetRepository.class); scriptAssetRepository = mock(ScriptAssetRepository.class); - scriptAssetAttachmentRepository = mock(ScriptAssetAttachmentRepository.class); + ScriptAssetAttachmentRepository scriptAssetAttachmentRepository = mock(ScriptAssetAttachmentRepository.class); scriptAssetFileRepository = mock(ScriptAssetFileRepository.class); - marketplaceScriptHeartRepository = mock(MarketplaceScriptHeartRepository.class); - auditLogService = mock(AuditLogService.class); + MarketplaceScriptHeartRepository marketplaceScriptHeartRepository = mock(MarketplaceScriptHeartRepository.class); + AuditLogService auditLogService = mock(AuditLogService.class); when(marketplaceScriptHeartRepository.countByScriptIds(any())).thenReturn(List.of()); when(marketplaceScriptHeartRepository.findByUsernameAndScriptIdIn(anyString(), any())).thenReturn(List.of()); - settingsService = mock(SettingsService.class); + SettingsService settingsService = mock(SettingsService.class); when(settingsService.get()).thenReturn(Settings.defaults()); setupInMemoryPersistence(); Path assetRoot = Files.createTempDirectory("imgfloat-assets-test"); @@ -112,24 +107,24 @@ class ChannelDirectoryServiceTest { Files.writeString(scriptRoot.resolve("source.js"), "console.log('seeded');"); Files.write(scriptRoot.resolve("logo.png"), samplePng()); Files.write(scriptRoot.resolve("attachments/rotate.png"), samplePng()); - marketplaceScriptSeedLoader = new MarketplaceScriptSeedLoader(marketplaceRoot.toString()); + MarketplaceScriptSeedLoader marketplaceScriptSeedLoader = new MarketplaceScriptSeedLoader(marketplaceRoot.toString()); service = new ChannelDirectoryService( channelRepository, assetRepository, visualAssetRepository, audioAssetRepository, scriptAssetRepository, - scriptAssetAttachmentRepository, + scriptAssetAttachmentRepository, scriptAssetFileRepository, - marketplaceScriptHeartRepository, + marketplaceScriptHeartRepository, messagingTemplate, assetStorageService, mediaDetectionService, mediaOptimizationService, - settingsService, + settingsService, uploadLimitBytes, - marketplaceScriptSeedLoader, - auditLogService + marketplaceScriptSeedLoader, + auditLogService ); } @@ -195,9 +190,14 @@ class ChannelDirectoryServiceTest { } @Test - void appliesBoundaryValues() throws Exception { + void appliesBoundaryValues() { String channel = "caster"; - String id = createSampleAsset(channel); + String id = null; + try { + id = createSampleAsset(channel); + } catch (Exception e) { + throw new RuntimeException(e); + } TransformRequest transform = validTransform(); transform.setSpeed(0.1); diff --git a/src/test/java/dev/kruhlmann/imgfloat/config/TwitchAuthorizationCodeGrantRequestEntityConverterTest.java b/src/test/java/dev/kruhlmann/imgfloat/config/TwitchAuthorizationCodeGrantRequestEntityConverterTest.java index aeb3b1c..1068d0c 100644 --- a/src/test/java/dev/kruhlmann/imgfloat/config/TwitchAuthorizationCodeGrantRequestEntityConverterTest.java +++ b/src/test/java/dev/kruhlmann/imgfloat/config/TwitchAuthorizationCodeGrantRequestEntityConverterTest.java @@ -43,21 +43,25 @@ class TwitchAuthorizationCodeGrantRequestEntityConverterTest { .state("state") .build(); + MultiValueMap body = getStringStringMultiValueMap(authorizationRequest, authorizationResponse, registration); + + assertThat(body.getFirst(OAuth2ParameterNames.CLIENT_ID)).isEqualTo("twitch-id"); + assertThat(body.getFirst(OAuth2ParameterNames.CLIENT_SECRET)).isEqualTo("twitch-secret"); + } + + private static MultiValueMap getStringStringMultiValueMap(OAuth2AuthorizationRequest authorizationRequest, OAuth2AuthorizationResponse authorizationResponse, ClientRegistration registration) { OAuth2AuthorizationExchange exchange = new OAuth2AuthorizationExchange( - authorizationRequest, - authorizationResponse + authorizationRequest, + authorizationResponse ); OAuth2AuthorizationCodeGrantRequest grantRequest = new OAuth2AuthorizationCodeGrantRequest( - registration, + registration, exchange ); var converter = new TwitchAuthorizationCodeGrantRequestEntityConverter(); RequestEntity requestEntity = converter.convert(grantRequest); - MultiValueMap body = (MultiValueMap) requestEntity.getBody(); - - assertThat(body.getFirst(OAuth2ParameterNames.CLIENT_ID)).isEqualTo("twitch-id"); - assertThat(body.getFirst(OAuth2ParameterNames.CLIENT_SECRET)).isEqualTo("twitch-secret"); + return (MultiValueMap) requestEntity.getBody(); } } diff --git a/src/test/java/dev/kruhlmann/imgfloat/config/TwitchOAuth2ErrorResponseErrorHandlerTest.java b/src/test/java/dev/kruhlmann/imgfloat/config/TwitchOAuth2ErrorResponseErrorHandlerTest.java index 1052da6..3b7845b 100644 --- a/src/test/java/dev/kruhlmann/imgfloat/config/TwitchOAuth2ErrorResponseErrorHandlerTest.java +++ b/src/test/java/dev/kruhlmann/imgfloat/config/TwitchOAuth2ErrorResponseErrorHandlerTest.java @@ -22,7 +22,7 @@ class TwitchOAuth2ErrorResponseErrorHandlerTest { private final TwitchOAuth2ErrorResponseErrorHandler handler = new TwitchOAuth2ErrorResponseErrorHandler(); @Test - void fallsBackToSyntheticErrorWhenErrorBodyIsMissing() throws Exception { + void fallsBackToSyntheticErrorWhenErrorBodyIsMissing() { MockClientHttpResponse response = new MockClientHttpResponse(new byte[0], HttpStatus.BAD_REQUEST); response.getHeaders().setContentType(MediaType.APPLICATION_JSON);