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