From fd81854b337860ca5eefaf001681efcfa4941984 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kr=C3=BChlmann?= Date: Wed, 10 Dec 2025 20:24:58 +0100 Subject: [PATCH] Add suggested admins --- .../app/controller/ChannelApiController.java | 37 +++++++++++++++---- .../app/service/TwitchUserLookupService.java | 4 ++ src/main/resources/static/css/styles.css | 4 ++ 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/imgfloat/app/controller/ChannelApiController.java b/src/main/java/com/imgfloat/app/controller/ChannelApiController.java index d80bd31..641be4c 100644 --- a/src/main/java/com/imgfloat/app/controller/ChannelApiController.java +++ b/src/main/java/com/imgfloat/app/controller/ChannelApiController.java @@ -18,6 +18,7 @@ import org.springframework.http.MediaType; 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.annotation.RegisteredOAuth2AuthorizedClient; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -72,7 +73,8 @@ public class ChannelApiController { @GetMapping("/admins") public Collection listAdmins(@PathVariable("broadcaster") String broadcaster, - OAuth2AuthenticationToken authentication) { + OAuth2AuthenticationToken authentication, + @RegisteredOAuth2AuthorizedClient("twitch") OAuth2AuthorizedClient authorizedClient) { String login = TwitchUser.from(authentication).login(); ensureBroadcaster(broadcaster, login); LOG.debug("Listing admins for {} by {}", broadcaster, login); @@ -80,9 +82,7 @@ public class ChannelApiController { List admins = channel.getAdmins().stream() .sorted(Comparator.naturalOrder()) .toList(); - OAuth2AuthorizedClient authorizedClient = authorizedClientService.loadAuthorizedClient( - authentication.getAuthorizedClientRegistrationId(), - authentication.getName()); + authorizedClient = resolveAuthorizedClient(authentication, authorizedClient); String accessToken = Optional.ofNullable(authorizedClient) .map(OAuth2AuthorizedClient::getAccessToken) .map(token -> token.getTokenValue()) @@ -96,14 +96,18 @@ public class ChannelApiController { @GetMapping("/admins/suggestions") public Collection listAdminSuggestions(@PathVariable("broadcaster") String broadcaster, - OAuth2AuthenticationToken authentication) { + OAuth2AuthenticationToken authentication, + @RegisteredOAuth2AuthorizedClient("twitch") OAuth2AuthorizedClient authorizedClient) { String login = TwitchUser.from(authentication).login(); ensureBroadcaster(broadcaster, login); LOG.debug("Listing admin suggestions for {} by {}", broadcaster, login); var channel = channelDirectoryService.getOrCreateChannel(broadcaster); - OAuth2AuthorizedClient authorizedClient = authorizedClientService.loadAuthorizedClient( - authentication.getAuthorizedClientRegistrationId(), - authentication.getName()); + authorizedClient = resolveAuthorizedClient(authentication, authorizedClient); + + if (authorizedClient == null) { + LOG.warn("No authorized Twitch client found for {} while fetching admin suggestions for {}", login, broadcaster); + return List.of(); + } String accessToken = Optional.ofNullable(authorizedClient) .map(OAuth2AuthorizedClient::getAccessToken) .map(token -> token.getTokenValue()) @@ -112,6 +116,10 @@ public class ChannelApiController { .map(OAuth2AuthorizedClient::getClientRegistration) .map(registration -> registration.getClientId()) .orElse(null); + if (accessToken == null || accessToken.isBlank() || clientId == null || clientId.isBlank()) { + LOG.warn("Missing Twitch credentials for {} while fetching admin suggestions for {}", login, broadcaster); + return List.of(); + } return twitchUserLookupService.fetchModerators(broadcaster, channel.getAdmins(), accessToken, clientId); } @@ -308,4 +316,17 @@ public class ChannelApiController { throw new ResponseStatusException(FORBIDDEN, "No permission for channel"); } } + + private OAuth2AuthorizedClient resolveAuthorizedClient(OAuth2AuthenticationToken authentication, + OAuth2AuthorizedClient authorizedClient) { + if (authorizedClient != null) { + return authorizedClient; + } + if (authentication == null) { + return null; + } + return authorizedClientService.loadAuthorizedClient( + authentication.getAuthorizedClientRegistrationId(), + authentication.getName()); + } } diff --git a/src/main/java/com/imgfloat/app/service/TwitchUserLookupService.java b/src/main/java/com/imgfloat/app/service/TwitchUserLookupService.java index 7ee331e..73a2686 100644 --- a/src/main/java/com/imgfloat/app/service/TwitchUserLookupService.java +++ b/src/main/java/com/imgfloat/app/service/TwitchUserLookupService.java @@ -65,10 +65,12 @@ public class TwitchUserLookupService { String accessToken, String clientId) { if (broadcasterLogin == null || broadcasterLogin.isBlank()) { + LOG.warn("Cannot fetch moderators without a broadcaster login"); return List.of(); } if (accessToken == null || accessToken.isBlank() || clientId == null || clientId.isBlank()) { + LOG.warn("Missing Twitch auth details when requesting moderators for {}", broadcasterLogin); return List.of(); } @@ -116,6 +118,7 @@ public class TwitchUserLookupService { TwitchModeratorsResponse.class); TwitchModeratorsResponse body = response.getBody(); + LOG.debug("Fetched {} moderator records for {} (cursor={})", body != null && body.data() != null ? body.data().size() : 0, broadcasterLogin, cursor); if (body != null && body.data() != null) { body.data().stream() .filter(Objects::nonNull) @@ -136,6 +139,7 @@ public class TwitchUserLookupService { } while (cursor != null && !cursor.isBlank()); if (moderatorLogins.isEmpty()) { + LOG.info("No moderator suggestions available for {} after filtering existing admins", broadcasterLogin); return List.of(); } diff --git a/src/main/resources/static/css/styles.css b/src/main/resources/static/css/styles.css index 54e8848..d29df6d 100644 --- a/src/main/resources/static/css/styles.css +++ b/src/main/resources/static/css/styles.css @@ -2,6 +2,10 @@ box-sizing: border-box; } +p { + margin: 0; +} + .hidden { display: none !important; }