Add suggested admins

This commit is contained in:
2025-12-10 20:24:58 +01:00
parent 5e0ef4fa74
commit fd81854b33
3 changed files with 37 additions and 8 deletions

View File

@@ -18,6 +18,7 @@ import org.springframework.http.MediaType;
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.annotation.RegisteredOAuth2AuthorizedClient;
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;
@@ -72,7 +73,8 @@ public class ChannelApiController {
@GetMapping("/admins") @GetMapping("/admins")
public Collection<TwitchUserProfile> listAdmins(@PathVariable("broadcaster") String broadcaster, public Collection<TwitchUserProfile> listAdmins(@PathVariable("broadcaster") String broadcaster,
OAuth2AuthenticationToken authentication) { OAuth2AuthenticationToken authentication,
@RegisteredOAuth2AuthorizedClient("twitch") OAuth2AuthorizedClient authorizedClient) {
String login = TwitchUser.from(authentication).login(); String login = TwitchUser.from(authentication).login();
ensureBroadcaster(broadcaster, login); ensureBroadcaster(broadcaster, login);
LOG.debug("Listing admins for {} by {}", broadcaster, login); LOG.debug("Listing admins for {} by {}", broadcaster, login);
@@ -80,9 +82,7 @@ public class ChannelApiController {
List<String> admins = channel.getAdmins().stream() List<String> admins = channel.getAdmins().stream()
.sorted(Comparator.naturalOrder()) .sorted(Comparator.naturalOrder())
.toList(); .toList();
OAuth2AuthorizedClient authorizedClient = authorizedClientService.loadAuthorizedClient( authorizedClient = resolveAuthorizedClient(authentication, authorizedClient);
authentication.getAuthorizedClientRegistrationId(),
authentication.getName());
String accessToken = Optional.ofNullable(authorizedClient) String accessToken = Optional.ofNullable(authorizedClient)
.map(OAuth2AuthorizedClient::getAccessToken) .map(OAuth2AuthorizedClient::getAccessToken)
.map(token -> token.getTokenValue()) .map(token -> token.getTokenValue())
@@ -96,14 +96,18 @@ public class ChannelApiController {
@GetMapping("/admins/suggestions") @GetMapping("/admins/suggestions")
public Collection<TwitchUserProfile> listAdminSuggestions(@PathVariable("broadcaster") String broadcaster, public Collection<TwitchUserProfile> listAdminSuggestions(@PathVariable("broadcaster") String broadcaster,
OAuth2AuthenticationToken authentication) { OAuth2AuthenticationToken authentication,
@RegisteredOAuth2AuthorizedClient("twitch") OAuth2AuthorizedClient authorizedClient) {
String login = TwitchUser.from(authentication).login(); String login = TwitchUser.from(authentication).login();
ensureBroadcaster(broadcaster, login); ensureBroadcaster(broadcaster, login);
LOG.debug("Listing admin suggestions for {} by {}", broadcaster, login); LOG.debug("Listing admin suggestions for {} by {}", broadcaster, login);
var channel = channelDirectoryService.getOrCreateChannel(broadcaster); var channel = channelDirectoryService.getOrCreateChannel(broadcaster);
OAuth2AuthorizedClient authorizedClient = authorizedClientService.loadAuthorizedClient( authorizedClient = resolveAuthorizedClient(authentication, authorizedClient);
authentication.getAuthorizedClientRegistrationId(),
authentication.getName()); 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) String accessToken = Optional.ofNullable(authorizedClient)
.map(OAuth2AuthorizedClient::getAccessToken) .map(OAuth2AuthorizedClient::getAccessToken)
.map(token -> token.getTokenValue()) .map(token -> token.getTokenValue())
@@ -112,6 +116,10 @@ public class ChannelApiController {
.map(OAuth2AuthorizedClient::getClientRegistration) .map(OAuth2AuthorizedClient::getClientRegistration)
.map(registration -> registration.getClientId()) .map(registration -> registration.getClientId())
.orElse(null); .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); return twitchUserLookupService.fetchModerators(broadcaster, channel.getAdmins(), accessToken, clientId);
} }
@@ -308,4 +316,17 @@ public class ChannelApiController {
throw new ResponseStatusException(FORBIDDEN, "No permission for channel"); 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());
}
} }

View File

@@ -65,10 +65,12 @@ public class TwitchUserLookupService {
String accessToken, String accessToken,
String clientId) { String clientId) {
if (broadcasterLogin == null || broadcasterLogin.isBlank()) { if (broadcasterLogin == null || broadcasterLogin.isBlank()) {
LOG.warn("Cannot fetch moderators without a broadcaster login");
return List.of(); return List.of();
} }
if (accessToken == null || accessToken.isBlank() || clientId == null || clientId.isBlank()) { if (accessToken == null || accessToken.isBlank() || clientId == null || clientId.isBlank()) {
LOG.warn("Missing Twitch auth details when requesting moderators for {}", broadcasterLogin);
return List.of(); return List.of();
} }
@@ -116,6 +118,7 @@ public class TwitchUserLookupService {
TwitchModeratorsResponse.class); TwitchModeratorsResponse.class);
TwitchModeratorsResponse body = response.getBody(); 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) { if (body != null && body.data() != null) {
body.data().stream() body.data().stream()
.filter(Objects::nonNull) .filter(Objects::nonNull)
@@ -136,6 +139,7 @@ public class TwitchUserLookupService {
} while (cursor != null && !cursor.isBlank()); } while (cursor != null && !cursor.isBlank());
if (moderatorLogins.isEmpty()) { if (moderatorLogins.isEmpty()) {
LOG.info("No moderator suggestions available for {} after filtering existing admins", broadcasterLogin);
return List.of(); return List.of();
} }

View File

@@ -2,6 +2,10 @@
box-sizing: border-box; box-sizing: border-box;
} }
p {
margin: 0;
}
.hidden { .hidden {
display: none !important; display: none !important;
} }