using System.Net.Http.Headers; using System.Net.Http.Json; using System.Text.Json; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace SfeduSchedule.Plugin.UniVerse; public sealed class UniverseUserLookupService( HttpClient httpClient, IConfiguration configuration, ILogger logger) : IUniverseUserLookupService { private const string AccessControlApiPath = "access-control/api"; private const string UsersQuery = """ query($t:String!){ users(filter:{text:$t}, pager:{activePage:1,pageSize:10}) { items { id name displayName description } } } """; private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web); public async Task FindSubIdAsync( string fullName, CancellationToken cancellationToken = default) { var token = configuration["TOKEN"]; if (string.IsNullOrWhiteSpace(token)) return UniverseUserLookupResult.UpstreamError("Modeus TOKEN is not configured."); var modeusUrl = configuration["MODEUS_URL"]; if (string.IsNullOrWhiteSpace(modeusUrl)) return UniverseUserLookupResult.UpstreamError("MODEUS_URL is not configured."); httpClient.BaseAddress = new Uri(modeusUrl, UriKind.Absolute); using var request = new HttpRequestMessage(HttpMethod.Post, AccessControlApiPath); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); request.Content = JsonContent.Create( new UniverseUsersGraphQlRequest( UsersQuery, new UniverseUsersGraphQlVariables(fullName)), options: JsonOptions); using var response = await httpClient.SendAsync(request, cancellationToken); if (!response.IsSuccessStatusCode) { logger.LogWarning( "Modeus access-control/api returned {StatusCode} while searching user by fullName.", response.StatusCode); return UniverseUserLookupResult.UpstreamError( $"Modeus access-control/api returned {(int)response.StatusCode}."); } UniverseUsersGraphQlResponse? payload; try { await using var contentStream = await response.Content.ReadAsStreamAsync(cancellationToken); payload = await JsonSerializer.DeserializeAsync( contentStream, JsonOptions, cancellationToken); } catch (JsonException exception) { logger.LogWarning(exception, "Unable to deserialize Modeus access-control/api response."); return UniverseUserLookupResult.UpstreamError("Modeus access-control/api returned invalid JSON."); } if (payload?.Errors is { Count: > 0 }) { logger.LogWarning( "Modeus access-control/api returned GraphQL errors: {Errors}", JsonSerializer.Serialize(payload.Errors, JsonOptions)); return UniverseUserLookupResult.UpstreamError("Modeus access-control/api returned GraphQL errors."); } var users = payload?.Data?.Users?.Items; if (users is null || users.Count == 0) return UniverseUserLookupResult.NotFound(); var subId = users[0].Name; return string.IsNullOrWhiteSpace(subId) ? UniverseUserLookupResult.NotFound() : UniverseUserLookupResult.Found(subId); } }