Files
UniVerse/backend/UniVerse.Infrastructure/ExternalServices/ModeusApiClient.cs
T
serega404 860964e3c2
🚀 Create and publish a Docker image / Detect changes in backend and frontend (push) Successful in 9s
🚀 Create and publish a Docker image / Build & publish backend image (push) Successful in 1m3s
🚀 Create and publish a Docker image / Build & publish frontend image (push) Successful in 25s
🚀 Create and publish a Docker image / Update stack on Portainer (push) Successful in 7s
fix: синхронизации лекций
2026-05-12 00:44:34 +03:00

146 lines
5.8 KiB
C#

using System.Net.Http.Json;
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)
{
const int pageSize = 900;
var specialtyCodes = string.IsNullOrWhiteSpace(request.SpecialtyCode)
? []
: request.SpecialtyCode
.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
var typeIds = request.TypeId ?? [];
var body = new Dictionary<string, object?>
{
["size"] = pageSize,
["timeMin"] = request.TimeMin,
["timeMax"] = request.TimeMax,
["specialtyCode"] = specialtyCodes
};
if (typeIds.Count > 0)
body["typeId"] = typeIds;
var response = await _http.PostAsJsonAsync("/api/proxy/events/search", body);
var requestJson = JsonSerializer.Serialize(body);
await EnsureSuccessAsync(response, "Modeus events search",
BuildEventsRequestSummary(pageSize, specialtyCodes, request.TimeMin, request.TimeMax, typeIds, requestJson));
return await ReadJsonAsync<ModeusEventsResponse>(response, "Modeus events search",
BuildEventsRequestSummary(pageSize, specialtyCodes, request.TimeMin, request.TimeMax, typeIds, 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(
int size,
IReadOnlyList<string> specialtyCodes,
DateTime? timeMin,
DateTime? timeMax,
IReadOnlyList<string> typeIds,
string requestJson)
{
var typeFilter = typeIds.Count > 0 ? $"typeId=[{string.Join(", ", typeIds)}]" : "typeId=<omitted>";
return $"size={size}, specialtyCode=[{string.Join(", ", specialtyCodes)}], timeMin={timeMin:O}, timeMax={timeMax:O}, {typeFilter}. Request JSON: {requestJson}";
}
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();
}
}