From 801bb689fbbce8963f9d7967ef837c23e14bccb2 Mon Sep 17 00:00:00 2001 From: Sergey Karmanov Date: Mon, 22 Sep 2025 23:22:13 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20IC?= =?UTF-8?q?S?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SfeduSchedule.Abstractions/ScheduleDTO.CS | 709 ++++++++++++++++++ .../Controllers/ScheduleController.cs | 186 +++-- SfeduSchedule/Services/ModeusService.cs | 56 ++ SfeduSchedule/SfeduSchedule.csproj | 1 + 4 files changed, 881 insertions(+), 71 deletions(-) create mode 100644 SfeduSchedule.Abstractions/ScheduleDTO.CS diff --git a/SfeduSchedule.Abstractions/ScheduleDTO.CS b/SfeduSchedule.Abstractions/ScheduleDTO.CS new file mode 100644 index 0000000..eb9e40c --- /dev/null +++ b/SfeduSchedule.Abstractions/ScheduleDTO.CS @@ -0,0 +1,709 @@ +// +// Вот этим сайтом https://app.quicktype.io/?l=csharp +// Не является точной копией ответа, могут быть отличия + +#nullable enable +#pragma warning disable CS8618 +#pragma warning disable CS8601 +#pragma warning disable CS8603 + +namespace SfeduSchedule +{ + using System; + using System.Collections.Generic; + + using System.Text.Json; + using System.Text.Json.Serialization; + using System.Globalization; + + public partial class Schedule + { + [JsonPropertyName("_embedded")] + public Embedded Embedded { get; set; } + + [JsonPropertyName("page")] + public Page Page { get; set; } + } + + public partial class Embedded + { + [JsonPropertyName("events")] + public Event[] Events { get; set; } + + [JsonPropertyName("course-unit-realizations")] + public CourseUnitRealization[] CourseUnitRealizations { get; set; } + + [JsonPropertyName("cycle-realizations")] + public CycleRealization[] CycleRealizations { get; set; } + + [JsonPropertyName("lesson-realization-teams")] + public LessonRealizationTeam[] LessonRealizationTeams { get; set; } + + [JsonPropertyName("lesson-realizations")] + public LessonRealization[] LessonRealizations { get; set; } + + [JsonPropertyName("event-locations")] + public EventLocation[] EventLocations { get; set; } + + [JsonPropertyName("durations")] + public Duration[] Durations { get; set; } + + [JsonPropertyName("event-rooms")] + public EventRoom[] EventRooms { get; set; } + + [JsonPropertyName("rooms")] + public Room[] Rooms { get; set; } + + [JsonPropertyName("buildings")] + public BuildingElement[] Buildings { get; set; } + + [JsonPropertyName("event-teams")] + public EventTeam[] EventTeams { get; set; } + + [JsonPropertyName("event-organizers")] + public EventOrganizer[] EventOrganizers { get; set; } + + [JsonPropertyName("event-attendees")] + public EventAttendee[] EventAttendees { get; set; } + + [JsonPropertyName("persons")] + public Person[] Persons { get; set; } + } + + public partial class BuildingElement + { + [JsonPropertyName("name")] + public string Name { get; set; } + + [JsonPropertyName("nameShort")] + public string NameShort { get; set; } + + [JsonPropertyName("address")] + public string Address { get; set; } + + [JsonPropertyName("searchableAddress")] + public string SearchableAddress { get; set; } + + [JsonPropertyName("displayOrder")] + public long DisplayOrder { get; set; } + + [JsonPropertyName("_links")] + public BuildingLinks Links { get; set; } + + [JsonPropertyName("id")] + public Guid Id { get; set; } + } + + public partial class BuildingLinks + { + [JsonPropertyName("self")] + public Self Self { get; set; } + } + + public partial class Self + { + [JsonPropertyName("href")] + public string Href { get; set; } + } + + public partial class CourseUnitRealization + { + [JsonPropertyName("name")] + public string Name { get; set; } + + [JsonPropertyName("nameShort")] + public string NameShort { get; set; } + + [JsonPropertyName("prototypeId")] + public Guid PrototypeId { get; set; } + + [JsonPropertyName("_links")] + public CourseUnitRealizationLinks Links { get; set; } + + [JsonPropertyName("id")] + public Guid Id { get; set; } + } + + public partial class CourseUnitRealizationLinks + { + [JsonPropertyName("self")] + public Self Self { get; set; } + + [JsonPropertyName("planning-period")] + public Self PlanningPeriod { get; set; } + } + + public partial class CycleRealization + { + [JsonPropertyName("name")] + public string Name { get; set; } + + [JsonPropertyName("nameShort")] + public string NameShort { get; set; } + + [JsonPropertyName("code")] + public string Code { get; set; } + + [JsonPropertyName("courseUnitRealizationNameShort")] + public string CourseUnitRealizationNameShort { get; set; } + + [JsonPropertyName("_links")] + public CycleRealizationLinks Links { get; set; } + + [JsonPropertyName("id")] + public Guid Id { get; set; } + } + + public partial class CycleRealizationLinks + { + [JsonPropertyName("self")] + public Self Self { get; set; } + + [JsonPropertyName("course-unit-realization")] + public Self CourseUnitRealization { get; set; } + } + + public partial class Duration + { + [JsonPropertyName("eventId")] + public Guid EventId { get; set; } + + [JsonPropertyName("value")] + public long Value { get; set; } + + [JsonPropertyName("timeUnitId")] + public string TimeUnitId { get; set; } + + [JsonPropertyName("minutes")] + public long Minutes { get; set; } + + [JsonPropertyName("_links")] + public DurationLinks Links { get; set; } + } + + public partial class DurationLinks + { + [JsonPropertyName("self")] + public Self[] Self { get; set; } + + [JsonPropertyName("time-unit")] + public Self TimeUnit { get; set; } + } + + public partial class EventAttendee + { + [JsonPropertyName("roleId")] + public string RoleId { get; set; } + + [JsonPropertyName("roleName")] + public string RoleName { get; set; } + + [JsonPropertyName("roleNamePlural")] + public string RoleNamePlural { get; set; } + + [JsonPropertyName("roleDisplayOrder")] + public long RoleDisplayOrder { get; set; } + + [JsonPropertyName("_links")] + public EventAttendeeLinks Links { get; set; } + + [JsonPropertyName("id")] + public Guid Id { get; set; } + } + + public partial class EventAttendeeLinks + { + [JsonPropertyName("self")] + public Self Self { get; set; } + + [JsonPropertyName("event")] + public Self Event { get; set; } + + [JsonPropertyName("person")] + public Self Person { get; set; } + } + + public partial class EventLocation + { + [JsonPropertyName("eventId")] + public Guid EventId { get; set; } + + [JsonPropertyName("customLocation")] + public string CustomLocation { get; set; } + + [JsonPropertyName("_links")] + public EventLocationLinks Links { get; set; } + } + + public partial class EventLocationLinks + { + [JsonPropertyName("self")] + public Self[] Self { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("event-rooms")] + public Self EventRooms { get; set; } + } + + public partial class EventOrganizer + { + [JsonPropertyName("eventId")] + public Guid EventId { get; set; } + + [JsonPropertyName("_links")] + public EventOrganizerLinks Links { get; set; } + } + + public partial class EventOrganizerLinks + { + [JsonPropertyName("self")] + public Self Self { get; set; } + + [JsonPropertyName("event")] + public Self Event { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("event-attendees")] + public EventAttendees? EventAttendees { get; set; } + } + + public partial class EventRoom + { + [JsonPropertyName("_links")] + public EventRoomLinks Links { get; set; } + + [JsonPropertyName("id")] + public Guid Id { get; set; } + } + + public partial class EventRoomLinks + { + [JsonPropertyName("self")] + public Self Self { get; set; } + + [JsonPropertyName("event")] + public Self Event { get; set; } + + [JsonPropertyName("room")] + public Self Room { get; set; } + } + + public partial class EventTeam + { + [JsonPropertyName("eventId")] + public Guid EventId { get; set; } + + [JsonPropertyName("size")] + public long Size { get; set; } + + [JsonPropertyName("_links")] + public EventTeamLinks Links { get; set; } + } + + public partial class EventTeamLinks + { + [JsonPropertyName("self")] + public Self Self { get; set; } + + [JsonPropertyName("event")] + public Self Event { get; set; } + } + + public partial class Event + { + [JsonPropertyName("name")] + public string Name { get; set; } + + [JsonPropertyName("nameShort")] + public string NameShort { get; set; } + + [JsonPropertyName("description")] + public object Description { get; set; } + + [JsonPropertyName("typeId")] + public string TypeId { get; set; } + + [JsonPropertyName("formatId")] + public string FormatId { get; set; } + + [JsonPropertyName("start")] + public DateTime Start { get; set; } + + [JsonPropertyName("end")] + public DateTime End { get; set; } + + [JsonPropertyName("startsAtLocal")] + public DateTime StartsAtLocal { get; set; } + + [JsonPropertyName("endsAtLocal")] + public DateTime EndsAtLocal { get; set; } + + [JsonPropertyName("startsAt")] + public DateTime StartsAt { get; set; } + + [JsonPropertyName("endsAt")] + public DateTime EndsAt { get; set; } + + [JsonPropertyName("holdingStatus")] + public HoldingStatus HoldingStatus { get; set; } + + [JsonPropertyName("repeatedLessonRealization")] + public RepeatedLessonRealization RepeatedLessonRealization { get; set; } + + [JsonPropertyName("userRoleIds")] + public string[] UserRoleIds { get; set; } + + [JsonPropertyName("lessonTemplateId")] + public Guid LessonTemplateId { get; set; } + + [JsonPropertyName("__version")] + public long Version { get; set; } + + [JsonPropertyName("_links")] + public Dictionary Links { get; set; } + + [JsonPropertyName("id")] + public Guid Id { get; set; } + } + + public partial class HoldingStatus + { + [JsonPropertyName("id")] + public string Id { get; set; } + + [JsonPropertyName("name")] + public string Name { get; set; } + + [JsonPropertyName("audModifiedAt")] + public DateTimeOffset? AudModifiedAt { get; set; } + + [JsonPropertyName("audModifiedBy")] + public Guid? AudModifiedBy { get; set; } + + [JsonPropertyName("audModifiedBySystem")] + public bool? AudModifiedBySystem { get; set; } + } + + public partial class RepeatedLessonRealization + { + [JsonPropertyName("name")] + public string Name { get; set; } + + [JsonPropertyName("lessonTeamName")] + public string LessonTeamName { get; set; } + } + + public partial class LessonRealizationTeam + { + [JsonPropertyName("name")] + public string Name { get; set; } + + [JsonPropertyName("cycleRealizationId")] + public Guid CycleRealizationId { get; set; } + + [JsonPropertyName("_links")] + public BuildingLinks Links { get; set; } + + [JsonPropertyName("id")] + public Guid Id { get; set; } + } + + public partial class LessonRealization + { + [JsonPropertyName("name")] + public string Name { get; set; } + + [JsonPropertyName("nameShort")] + public string NameShort { get; set; } + + [JsonPropertyName("prototypeId")] + public Guid PrototypeId { get; set; } + + [JsonPropertyName("ordinal")] + public long Ordinal { get; set; } + + [JsonPropertyName("_links")] + public BuildingLinks Links { get; set; } + + [JsonPropertyName("id")] + public Guid Id { get; set; } + } + + public partial class Person + { + [JsonPropertyName("lastName")] + public string LastName { get; set; } + + [JsonPropertyName("firstName")] + public string FirstName { get; set; } + + [JsonPropertyName("middleName")] + public string MiddleName { get; set; } + + [JsonPropertyName("fullName")] + public string FullName { get; set; } + + [JsonPropertyName("_links")] + public BuildingLinks Links { get; set; } + + [JsonPropertyName("id")] + public Guid Id { get; set; } + } + + public partial class Room + { + [JsonPropertyName("name")] + public string Name { get; set; } + + [JsonPropertyName("nameShort")] + public string NameShort { get; set; } + + [JsonPropertyName("building")] + public RoomBuilding Building { get; set; } + + [JsonPropertyName("projectorAvailable")] + public bool ProjectorAvailable { get; set; } + + [JsonPropertyName("totalCapacity")] + public long TotalCapacity { get; set; } + + [JsonPropertyName("workingCapacity")] + public long WorkingCapacity { get; set; } + + [JsonPropertyName("deletedAtUtc")] + public object DeletedAtUtc { get; set; } + + [JsonPropertyName("_links")] + public RoomLinks Links { get; set; } + + [JsonPropertyName("id")] + public Guid Id { get; set; } + } + + public partial class RoomBuilding + { + [JsonPropertyName("id")] + public Guid Id { get; set; } + + [JsonPropertyName("name")] + public string Name { get; set; } + + [JsonPropertyName("nameShort")] + public string NameShort { get; set; } + + [JsonPropertyName("address")] + public string Address { get; set; } + + [JsonPropertyName("displayOrder")] + public long DisplayOrder { get; set; } + } + + public partial class RoomLinks + { + [JsonPropertyName("self")] + public Self Self { get; set; } + + [JsonPropertyName("type")] + public Self Type { get; set; } + + [JsonPropertyName("building")] + public Self Building { get; set; } + } + + public partial class Page + { + [JsonPropertyName("size")] + public long Size { get; set; } + + [JsonPropertyName("totalElements")] + public long TotalElements { get; set; } + + [JsonPropertyName("totalPages")] + public long TotalPages { get; set; } + + [JsonPropertyName("number")] + public long Number { get; set; } + } + + public partial struct EventAttendees + { + public Self Self; + public Self[] SelfArray; + + public static implicit operator EventAttendees(Self Self) => new EventAttendees { Self = Self }; + public static implicit operator EventAttendees(Self[] SelfArray) => new EventAttendees { SelfArray = SelfArray }; + } + + public partial class Schedule + { + public static Schedule FromJson(string json) => JsonSerializer.Deserialize(json, SfeduSchedule.Converter.Settings); + } + + public static class Serialize + { + public static string ToJson(this Schedule self) => JsonSerializer.Serialize(self, SfeduSchedule.Converter.Settings); + } + + internal static class Converter + { + public static readonly JsonSerializerOptions Settings = new(JsonSerializerDefaults.General) + { + Converters = + { + EventAttendeesConverter.Singleton, + new DateOnlyConverter(), + new TimeOnlyConverter(), + IsoDateTimeOffsetConverter.Singleton + }, + }; + } + + internal class EventAttendeesConverter : JsonConverter + { + public override bool CanConvert(Type t) => t == typeof(EventAttendees); + + public override EventAttendees Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + switch (reader.TokenType) + { + case JsonTokenType.StartObject: + var objectValue = JsonSerializer.Deserialize(ref reader, options); + return new EventAttendees { Self = objectValue }; + case JsonTokenType.StartArray: + var arrayValue = JsonSerializer.Deserialize(ref reader, options); + return new EventAttendees { SelfArray = arrayValue }; + } + throw new Exception("Cannot unmarshal type EventAttendees"); + } + + public override void Write(Utf8JsonWriter writer, EventAttendees value, JsonSerializerOptions options) + { + if (value.SelfArray != null) + { + JsonSerializer.Serialize(writer, value.SelfArray, options); + return; + } + if (value.Self != null) + { + JsonSerializer.Serialize(writer, value.Self, options); + return; + } + throw new Exception("Cannot marshal type EventAttendees"); + } + + public static readonly EventAttendeesConverter Singleton = new EventAttendeesConverter(); + } + + public class DateOnlyConverter : JsonConverter + { + private readonly string serializationFormat; + public DateOnlyConverter() : this(null) { } + + public DateOnlyConverter(string? serializationFormat) + { + this.serializationFormat = serializationFormat ?? "yyyy-MM-dd"; + } + + public override DateOnly Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var value = reader.GetString(); + return DateOnly.Parse(value!); + } + + public override void Write(Utf8JsonWriter writer, DateOnly value, JsonSerializerOptions options) + => writer.WriteStringValue(value.ToString(serializationFormat)); + } + + public class TimeOnlyConverter : JsonConverter + { + private readonly string serializationFormat; + + public TimeOnlyConverter() : this(null) { } + + public TimeOnlyConverter(string? serializationFormat) + { + this.serializationFormat = serializationFormat ?? "HH:mm:ss.fff"; + } + + public override TimeOnly Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var value = reader.GetString(); + return TimeOnly.Parse(value!); + } + + public override void Write(Utf8JsonWriter writer, TimeOnly value, JsonSerializerOptions options) + => writer.WriteStringValue(value.ToString(serializationFormat)); + } + + internal class IsoDateTimeOffsetConverter : JsonConverter + { + public override bool CanConvert(Type t) => t == typeof(DateTimeOffset); + + private const string DefaultDateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.FFFFFFFK"; + + private DateTimeStyles _dateTimeStyles = DateTimeStyles.RoundtripKind; + private string? _dateTimeFormat; + private CultureInfo? _culture; + + public DateTimeStyles DateTimeStyles + { + get => _dateTimeStyles; + set => _dateTimeStyles = value; + } + + public string? DateTimeFormat + { + get => _dateTimeFormat ?? string.Empty; + set => _dateTimeFormat = (string.IsNullOrEmpty(value)) ? null : value; + } + + public CultureInfo Culture + { + get => _culture ?? CultureInfo.CurrentCulture; + set => _culture = value; + } + + public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options) + { + string text; + + + if ((_dateTimeStyles & DateTimeStyles.AdjustToUniversal) == DateTimeStyles.AdjustToUniversal + || (_dateTimeStyles & DateTimeStyles.AssumeUniversal) == DateTimeStyles.AssumeUniversal) + { + value = value.ToUniversalTime(); + } + + text = value.ToString(_dateTimeFormat ?? DefaultDateTimeFormat, Culture); + + writer.WriteStringValue(text); + } + + public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + string? dateText = reader.GetString(); + + if (string.IsNullOrEmpty(dateText) == false) + { + if (!string.IsNullOrEmpty(_dateTimeFormat)) + { + return DateTimeOffset.ParseExact(dateText, _dateTimeFormat, Culture, _dateTimeStyles); + } + else + { + return DateTimeOffset.Parse(dateText, Culture, _dateTimeStyles); + } + } + else + { + return default(DateTimeOffset); + } + } + + + public static readonly IsoDateTimeOffsetConverter Singleton = new IsoDateTimeOffsetConverter(); + } +} +#pragma warning restore CS8618 +#pragma warning restore CS8601 +#pragma warning restore CS8603 diff --git a/SfeduSchedule/Controllers/ScheduleController.cs b/SfeduSchedule/Controllers/ScheduleController.cs index 87a6b26..1aa5daa 100644 --- a/SfeduSchedule/Controllers/ScheduleController.cs +++ b/SfeduSchedule/Controllers/ScheduleController.cs @@ -6,80 +6,124 @@ using Microsoft.AspNetCore.RateLimiting; using SfeduSchedule.Abstractions; using SfeduSchedule.Services; -namespace SfeduSchedule.Controllers +namespace SfeduSchedule.Controllers; + +[ApiController] +[Route("api/[controller]")] +[EnableRateLimiting("throttle")] +public class ScheduleController(ModeusService modeusService, ILogger logger) : ControllerBase { - [ApiController] - [Route("api/[controller]")] - [EnableRateLimiting("throttle")] - public class ScheduleController(ModeusService modeusService, ILogger logger) : ControllerBase + /// + /// Получить расписание по пользовательскому запросу. + /// + /// Объект запроса, содержащий параметры фильтрации расписания. + /// Список событий расписания. + /// Возвращает расписание + /// Слишком много запросов + [HttpPost] + public async Task Post([FromBody] ModeusScheduleRequest request) { - - /// - /// Получить расписание по пользовательскому запросу. - /// - /// Объект запроса, содержащий параметры фильтрации расписания. - /// Список событий расписания. - /// Возвращает расписание - /// Слишком много запросов - [HttpPost] - public async Task Post([FromBody] ModeusScheduleRequest request) + string? schedule; + try { - string? schedule; - try - { - schedule = await modeusService.GetScheduleAsync(request); - } - catch (HttpRequestException e) - { - logger.LogError("Ошибка при получении расписания\n\n" + e.Message + "\n\n" + e.StackTrace + "\n\n JSON: " + JsonSerializer.Serialize(request, GlobalVariables.jsonSerializerOptions)); - return StatusCode((int)(e.StatusCode ?? HttpStatusCode.InternalServerError), "Proxied Modeus: " + e.Message); - } - return Ok(schedule); + schedule = await modeusService.GetScheduleAsync(request); + } + catch (HttpRequestException e) + { + logger.LogError("Ошибка при получении расписания\n\n" + e.Message + "\n\n" + e.StackTrace + + "\n\n JSON: " + + JsonSerializer.Serialize(request, GlobalVariables.jsonSerializerOptions)); + return StatusCode((int)(e.StatusCode ?? HttpStatusCode.InternalServerError), + "Proxied Modeus: " + e.Message); } - /// - /// Поиск аудиторий по пользовательскому запросу. - /// - /// Объект запроса, содержащий параметры фильтрации аудиторий. - /// Список аудиторий. - /// Возвращает список аудиторий - /// Слишком много запросов - [HttpPost] - [Route("rooms/search")] - public async Task SearchRooms([FromBody] RoomSearchRequest request) - { - string? rooms; - try - { - rooms = await modeusService.SearchRoomsAsync(request); - } - catch (HttpRequestException e) - { - logger.LogError("Ошибка при поиске аудиторий\n\n" + e.Message + "\n\n" + e.StackTrace + "\n\n JSON: " + JsonSerializer.Serialize(request, GlobalVariables.jsonSerializerOptions)); - return StatusCode((int)(e.StatusCode ?? HttpStatusCode.InternalServerError), "Proxied Modeus: " + e.Message); - } - return Ok(rooms); - } - - /// - /// Получить GUID пользователя по полному имени. (требуется авторизация) - /// - /// Полное имя пользователя. - /// GUID пользователя. - /// Возвращает GUID пользователя - /// Пользователь не найден - /// Неавторизованный - [HttpGet] - [Authorize(AuthenticationSchemes = "ApiKey")] - [DisableRateLimiting] - [Route("getguid")] - public async Task GetGuid(string fullname) - { - var guid = await modeusService.GetGuidAsync(fullname); - if (string.IsNullOrEmpty(guid)) - return NotFound(); - - return Ok(guid); - } + return Ok(schedule); } -} + + /// + /// Поиск аудиторий по пользовательскому запросу. + /// + /// Объект запроса, содержащий параметры фильтрации аудиторий. + /// Список аудиторий. + /// Возвращает список аудиторий + /// Слишком много запросов + [HttpPost] + [Route("rooms/search")] + public async Task SearchRooms([FromBody] RoomSearchRequest request) + { + string? rooms; + try + { + rooms = await modeusService.SearchRoomsAsync(request); + } + catch (HttpRequestException e) + { + logger.LogError("Ошибка при поиске аудиторий\n\n" + e.Message + "\n\n" + e.StackTrace + "\n\n JSON: " + + JsonSerializer.Serialize(request, GlobalVariables.jsonSerializerOptions)); + return StatusCode((int)(e.StatusCode ?? HttpStatusCode.InternalServerError), + "Proxied Modeus: " + e.Message); + } + + return Ok(rooms); + } + + /// + /// Получить GUID пользователя по полному имени. (требуется авторизация) + /// + /// Полное имя пользователя. + /// GUID пользователя. + /// Возвращает GUID пользователя + /// Пользователь не найден + /// Неавторизованный + [HttpGet] + [Authorize(AuthenticationSchemes = "ApiKey")] + [DisableRateLimiting] + [Route("getguid")] + public async Task GetGuid(string fullname) + { + var guid = await modeusService.GetGuidAsync(fullname); + if (string.IsNullOrEmpty(guid)) + return NotFound(); + + return Ok(guid); + } + + /// + /// Получить расписание в формате ICS по пользовательскому запросу. + /// + /// Объект запроса, содержащий параметры фильтрации расписания. + /// Файл ICS с расписанием за -1 неделя + 1 месяц + /// Возвращает файл ICS с расписанием + /// Расписание не найдено + /// Слишком много запросов + [HttpPost] + [Route("ics")] + public async Task PostIcs([FromBody] ModeusScheduleRequest request) + { + var ics = await modeusService.GetIcsAsync(request); + if (string.IsNullOrEmpty(ics)) + return NotFound(); + + return new FileContentResult(System.Text.Encoding.UTF8.GetBytes(ics), "text/calendar") + { + FileDownloadName = "schedule.ics" + }; + } + + /// + /// Получить расписание в формате ICS для указанного пользователя за -1 неделя + 1 месяц. + /// + /// + /// Файл ICS с расписанием + /// Возвращает файл ICS с расписанием + /// Расписание не найдено + /// Слишком много запросов + [HttpGet] + [Route("ics")] + public async Task GetIcs([FromQuery] Guid attendeePersonId) + { + return await PostIcs(new ModeusScheduleRequest(1000, DateTime.UtcNow.AddDays(-7), + DateTime.UtcNow.AddMonths(1), null, new List { attendeePersonId }, null, null, null, null, null, + null, null)); + } +} \ No newline at end of file diff --git a/SfeduSchedule/Services/ModeusService.cs b/SfeduSchedule/Services/ModeusService.cs index e33dbae..e785683 100644 --- a/SfeduSchedule/Services/ModeusService.cs +++ b/SfeduSchedule/Services/ModeusService.cs @@ -1,4 +1,7 @@ using System.Text.Json; +using Ical.Net.CalendarComponents; +using Ical.Net.DataTypes; +using Ical.Net.Serialization; using Microsoft.Net.Http.Headers; using SfeduSchedule.Abstractions; @@ -75,5 +78,58 @@ namespace SfeduSchedule.Services return personId; } + public async Task GetIcsAsync(ModeusScheduleRequest msr) + { + var schedule = await GetScheduleAsync(msr); + if (schedule == null) + { + _logger.LogError("GetIcsAsync: Schedule is null. Request: {@msr}", msr); + return null; + } + + Schedule? scheduleJson; + try + { + scheduleJson = Schedule.FromJson(schedule); + } + catch (Exception ex) + { + _logger.LogError(ex, "GetIcsAsync: Deserialization failed. Schedule: {Schedule}", schedule); + return null; + } + + if (scheduleJson?.Embedded?.Events is not { Length: > 0 } events) + { + if (scheduleJson == null) + _logger.LogError("GetIcsAsync: scheduleJson is null. Schedule: {Schedule}", schedule); + else if (scheduleJson.Embedded == null) + _logger.LogError("GetIcsAsync: scheduleJson.Embedded is null. scheduleJson: {@scheduleJson}", scheduleJson); + else if (scheduleJson.Embedded.Events == null) + _logger.LogError("GetIcsAsync: scheduleJson.Embedded.Events is null. Embedded: {@Embedded}", scheduleJson.Embedded); + else + _logger.LogWarning("GetIcsAsync: scheduleJson.Embedded.Events is empty. Embedded: {@Embedded}", scheduleJson.Embedded); + return null; + } + + var calendar = new Ical.Net.Calendar(); + calendar.AddTimeZone(new VTimeZone(_configuration["TZ"]!)); + + foreach (var e in scheduleJson.Embedded.Events) + { + calendar.Events.Add(new CalendarEvent + { + Summary = e.Name, + Description = e.NameShort, + Start = new CalDateTime(e.StartsAtLocal, _configuration["TZ"]!), + End = new CalDateTime(e.EndsAtLocal, _configuration["TZ"]!), + }); + } + var serializer = new CalendarSerializer(); + var serializedCalendar = serializer.SerializeToString(calendar); + _logger.LogInformation("GetIcsAsync: Serialized calendar created. Length: {Length}", serializedCalendar?.Length ?? 0); + return serializedCalendar; + + } + } } diff --git a/SfeduSchedule/SfeduSchedule.csproj b/SfeduSchedule/SfeduSchedule.csproj index 1c16d82..fbbee2b 100644 --- a/SfeduSchedule/SfeduSchedule.csproj +++ b/SfeduSchedule/SfeduSchedule.csproj @@ -10,6 +10,7 @@ +