85ef2a1c22
🚀 Create and publish a Docker image / Detect changes in backend and frontend (push) Failing after 10m14s
Frontend CI / build-and-check (push) Failing after 16m12s
🚀 Create and publish a Docker image / Build & publish frontend image (push) Failing after 14m7s
🚀 Create and publish a Docker image / Build & publish backend image (push) Failing after 14m59s
🚀 Create and publish a Docker image / Update stack on Portainer (push) Failing after 14m57s
Backend CI / build-and-test (push) Failing after 13m27s
164 lines
6.5 KiB
C#
164 lines
6.5 KiB
C#
using System.Net.Http.Json;
|
|
using System.Net;
|
|
using System.Net.Http.Headers;
|
|
using System.Text.Json;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.Logging;
|
|
using UniVerse.Application.DTOs.Sync;
|
|
using UniVerse.Application.Interfaces;
|
|
|
|
namespace UniVerse.Infrastructure.ExternalServices;
|
|
|
|
public class ModeusApiClient : IModeusApiClient
|
|
{
|
|
private readonly HttpClient _http;
|
|
private readonly ILogger<ModeusApiClient> _logger;
|
|
|
|
public ModeusApiClient(HttpClient http, IConfiguration config, ILogger<ModeusApiClient> logger)
|
|
{
|
|
_http = http; _logger = logger;
|
|
var apiKey = config["ModeusApi:ApiKey"];
|
|
if (!string.IsNullOrEmpty(apiKey))
|
|
_http.DefaultRequestHeaders.Add("X-API-Key", apiKey);
|
|
}
|
|
|
|
public async Task<ModeusEventsResponse> SearchEventsAsync(SyncScheduleRequest request)
|
|
{
|
|
var pageSize = request.Size is > 0 ? request.Size.Value : 900;
|
|
var body = new Dictionary<string, object?>
|
|
{
|
|
["size"] = pageSize,
|
|
["timeMin"] = request.TimeMin,
|
|
["timeMax"] = request.TimeMax
|
|
};
|
|
|
|
AddNonEmpty(body, "roomId", request.RoomId);
|
|
AddNonEmpty(body, "attendeePersonId", request.AttendeePersonId);
|
|
AddNonEmpty(body, "courseUnitRealizationId", request.CourseUnitRealizationId);
|
|
AddNonEmpty(body, "cycleRealizationId", request.CycleRealizationId);
|
|
AddNonEmpty(body, "specialtyCode", request.SpecialtyCode);
|
|
AddNonEmpty(body, "learningStartYear", request.LearningStartYear);
|
|
AddNonEmpty(body, "profileName", request.ProfileName);
|
|
AddNonEmpty(body, "curriculumId", request.CurriculumId);
|
|
AddNonEmpty(body, "typeId", request.TypeId);
|
|
|
|
var response = await _http.PostAsJsonAsync("/api/proxy/events/search", body);
|
|
var requestJson = JsonSerializer.Serialize(body);
|
|
await EnsureSuccessAsync(response, "Modeus events search",
|
|
BuildEventsRequestSummary(requestJson));
|
|
return await ReadJsonAsync<ModeusEventsResponse>(response, "Modeus events search",
|
|
BuildEventsRequestSummary(requestJson))
|
|
?? new ModeusEventsResponse();
|
|
}
|
|
|
|
public async Task<ModeusRoomsResponse> SearchRoomsAsync()
|
|
{
|
|
const int pageSize = 100;
|
|
var allRooms = new List<ModeusRoom>();
|
|
var page = 0;
|
|
var totalPages = 1;
|
|
|
|
do
|
|
{
|
|
var body = new
|
|
{
|
|
name = "",
|
|
sort = "+building.name,+name",
|
|
size = pageSize,
|
|
page,
|
|
deleted = false
|
|
};
|
|
|
|
var response = await _http.PostAsJsonAsync("/api/proxy/rooms/search", body);
|
|
await EnsureSuccessAsync(response, "Modeus rooms search",
|
|
$"name=<empty>, sort=+building.name,+name, size={pageSize}, page={page}, deleted=false");
|
|
|
|
var payload = await ReadJsonAsync<ModeusRoomsResponse>(response, "Modeus rooms search",
|
|
$"name=<empty>, sort=+building.name,+name, size={pageSize}, page={page}, deleted=false")
|
|
?? new ModeusRoomsResponse();
|
|
allRooms.AddRange(payload.RoomItems);
|
|
|
|
totalPages = payload.Page?.TotalPages ?? page + 1;
|
|
page++;
|
|
}
|
|
while (page < totalPages);
|
|
|
|
return new ModeusRoomsResponse { Rooms = allRooms };
|
|
}
|
|
|
|
private static async Task EnsureSuccessAsync(HttpResponseMessage response, string operation, string requestSummary)
|
|
{
|
|
if (response.IsSuccessStatusCode) return;
|
|
|
|
var responseBody = await response.Content.ReadAsStringAsync();
|
|
|
|
throw new HttpRequestException(
|
|
$"{operation} failed with HTTP {(int)response.StatusCode} {response.ReasonPhrase}. Request: {requestSummary}. Response body: {Truncate(responseBody)}",
|
|
null,
|
|
response.StatusCode);
|
|
}
|
|
|
|
private static string BuildEventsRequestSummary(string requestJson) => $"Request JSON: {requestJson}";
|
|
|
|
private static void AddNonEmpty<T>(
|
|
IDictionary<string, object?> body,
|
|
string key,
|
|
IReadOnlyList<T>? values)
|
|
{
|
|
if (values is { Count: > 0 })
|
|
body[key] = values;
|
|
}
|
|
|
|
private static async Task<T?> ReadJsonAsync<T>(HttpResponseMessage response, string operation, string requestSummary)
|
|
{
|
|
var responseBody = await response.Content.ReadAsStringAsync();
|
|
var contentType = response.Content.Headers.ContentType?.ToString() ?? "<empty>";
|
|
var contentLength = response.Content.Headers.ContentLength?.ToString() ?? "<unknown>";
|
|
|
|
if (string.IsNullOrWhiteSpace(responseBody))
|
|
{
|
|
throw new HttpRequestException(
|
|
$"{operation} returned HTTP {(int)response.StatusCode} {response.ReasonPhrase} with an empty response body. Request: {requestSummary}. Content-Type: {contentType}. Content-Length: {contentLength}.",
|
|
null,
|
|
response.StatusCode);
|
|
}
|
|
|
|
try
|
|
{
|
|
return JsonSerializer.Deserialize<T>(responseBody, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
|
}
|
|
catch (JsonException ex)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"{operation} returned invalid JSON. Request: {requestSummary}. Content-Type: {contentType}. Response body: {Truncate(responseBody)}",
|
|
ex);
|
|
}
|
|
}
|
|
|
|
private static string Truncate(string value) =>
|
|
value.Length > 2000 ? string.Concat(value.AsSpan(0, 2000), "...<truncated>") : value;
|
|
|
|
public async Task<List<ModeusEmployee>> SearchEmployeeAsync(string fullname)
|
|
{
|
|
var response = await _http.GetFromJsonAsync<List<ModeusEmployee>>(
|
|
$"/api/schedule/searchemployee?fullname={Uri.EscapeDataString(fullname)}");
|
|
return response ?? new();
|
|
}
|
|
|
|
public async Task<string?> GetSubIdByFullNameAsync(string fullname, CancellationToken cancellationToken = default)
|
|
{
|
|
using var request = new HttpRequestMessage(
|
|
HttpMethod.Get,
|
|
$"/api/universe/subid?fullname={Uri.EscapeDataString(fullname)}");
|
|
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("text/plain"));
|
|
|
|
using var response = await _http.SendAsync(request, cancellationToken);
|
|
if (response.StatusCode == HttpStatusCode.NotFound)
|
|
return null;
|
|
|
|
await EnsureSuccessAsync(response, "Universe user sub lookup", $"fullname={fullname}");
|
|
var body = await response.Content.ReadAsStringAsync(cancellationToken);
|
|
return string.IsNullOrWhiteSpace(body) ? null : body.Trim();
|
|
}
|
|
}
|