158 lines
6.2 KiB
C#
158 lines
6.2 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Logging;
|
|
using UniVerse.Application.DTOs.Sync;
|
|
using UniVerse.Application.Interfaces;
|
|
using UniVerse.Domain.Entities;
|
|
using UniVerse.Infrastructure.Data;
|
|
|
|
namespace UniVerse.Infrastructure.Services;
|
|
|
|
public class ScheduleSyncService : IScheduleSyncService
|
|
{
|
|
private readonly AppDbContext _db;
|
|
private readonly IModeusApiClient _modeus;
|
|
private readonly ILogger<ScheduleSyncService> _logger;
|
|
private static SyncStatusDto _lastStatus = new(null, "idle", null);
|
|
|
|
public ScheduleSyncService(AppDbContext db, IModeusApiClient modeus, ILogger<ScheduleSyncService> logger)
|
|
{
|
|
_db = db; _modeus = modeus; _logger = logger;
|
|
}
|
|
|
|
public async Task<SyncResultDto> SyncScheduleAsync(SyncScheduleRequest request)
|
|
{
|
|
const string stage = "schedule";
|
|
int created = 0, updated = 0, skipped = 0;
|
|
try
|
|
{
|
|
var events = await _modeus.SearchEventsAsync(request);
|
|
foreach (var ev in events.Events)
|
|
{
|
|
var existing = await _db.Lectures.FirstOrDefaultAsync(l => l.ExternalId == ev.Id);
|
|
if (existing != null) { updated++; existing.StartsAt = ev.StartsAt; existing.EndsAt = ev.EndsAt; existing.UpdatedAt = DateTime.UtcNow; }
|
|
else
|
|
{
|
|
var course = await _db.Courses.FirstOrDefaultAsync(c => c.ExternalId == ev.TypeId);
|
|
if (course == null) { course = new Course { Name = ev.Name, ExternalId = ev.TypeId, IsSynced = true }; _db.Courses.Add(course); await _db.SaveChangesAsync(); }
|
|
_db.Lectures.Add(new Lecture { CourseId = course.Id, Title = ev.Name, ExternalId = ev.Id, StartsAt = ev.StartsAt, EndsAt = ev.EndsAt });
|
|
created++;
|
|
}
|
|
}
|
|
await _db.SaveChangesAsync();
|
|
var result = new SyncResultDto(created, updated, skipped, null);
|
|
_lastStatus = new SyncStatusDto(DateTime.UtcNow, "completed", result);
|
|
return result;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Schedule sync failed");
|
|
var result = new SyncResultDto(created, updated, skipped, ex.Message, BuildErrorDetails(
|
|
ex,
|
|
stage,
|
|
created,
|
|
updated,
|
|
skipped,
|
|
[
|
|
$"specialtyCode={request.SpecialtyCode ?? "<empty>"}",
|
|
$"timeMin={request.TimeMin:O}",
|
|
$"timeMax={request.TimeMax:O}",
|
|
$"typeId=[{string.Join(", ", request.TypeId ?? [])}]"
|
|
]));
|
|
_lastStatus = new SyncStatusDto(DateTime.UtcNow, "failed", result);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
public async Task<SyncResultDto> SyncRoomsAsync()
|
|
{
|
|
const string stage = "rooms";
|
|
int created = 0, updated = 0, skipped = 0;
|
|
try
|
|
{
|
|
var rooms = await _modeus.SearchRoomsAsync();
|
|
foreach (var room in rooms?.RoomItems ?? [])
|
|
{
|
|
if (room is null || string.IsNullOrWhiteSpace(room.Id) || string.IsNullOrWhiteSpace(room.Name))
|
|
{
|
|
skipped++;
|
|
continue;
|
|
}
|
|
|
|
var existing = await _db.Locations.FirstOrDefaultAsync(l => l.ExternalId == room.Id);
|
|
if (existing != null)
|
|
{
|
|
existing.Name = room.Name;
|
|
existing.Room = room.NameShort;
|
|
existing.Building = room.Building?.Name ?? room.Building?.NameShort;
|
|
existing.Address = room.Building?.Address;
|
|
updated++;
|
|
}
|
|
else
|
|
{
|
|
_db.Locations.Add(new Location
|
|
{
|
|
Name = room.Name,
|
|
Room = room.NameShort,
|
|
Building = room.Building?.Name ?? room.Building?.NameShort,
|
|
Address = room.Building?.Address,
|
|
ExternalId = room.Id
|
|
});
|
|
created++;
|
|
}
|
|
}
|
|
|
|
await _db.SaveChangesAsync();
|
|
var result = new SyncResultDto(created, updated, skipped, null);
|
|
_lastStatus = new SyncStatusDto(DateTime.UtcNow, "completed", result);
|
|
return result;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Rooms sync failed");
|
|
var result = new SyncResultDto(created, updated, skipped, ex.Message, BuildErrorDetails(
|
|
ex,
|
|
stage,
|
|
created,
|
|
updated,
|
|
skipped,
|
|
["request=name:<empty>, sort:+building.name,+name, deleted:false, page size:100"]));
|
|
_lastStatus = new SyncStatusDto(DateTime.UtcNow, "failed", result);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
public async Task<List<EmployeeDto>> SearchEmployeesAsync(string fullname)
|
|
{
|
|
var employees = await _modeus.SearchEmployeeAsync(fullname);
|
|
return employees.Select(e => new EmployeeDto(e.Id, e.FullName, e.Department)).ToList();
|
|
}
|
|
|
|
public Task<SyncStatusDto> GetLastSyncStatusAsync() => Task.FromResult(_lastStatus);
|
|
|
|
private static IReadOnlyList<string> BuildErrorDetails(
|
|
Exception exception,
|
|
string stage,
|
|
int created,
|
|
int updated,
|
|
int skipped,
|
|
IReadOnlyList<string> context)
|
|
{
|
|
var details = new List<string>
|
|
{
|
|
$"stage={stage}",
|
|
$"exceptionType={exception.GetType().FullName}",
|
|
$"message={exception.Message}",
|
|
$"partialResult=created:{created}, updated:{updated}, skipped:{skipped}"
|
|
};
|
|
|
|
if (exception is HttpRequestException httpException && httpException.StatusCode.HasValue)
|
|
details.Add($"httpStatus={(int)httpException.StatusCode.Value} {httpException.StatusCode.Value}");
|
|
|
|
if (exception.InnerException != null)
|
|
details.Add($"innerException={exception.InnerException.GetType().FullName}: {exception.InnerException.Message}");
|
|
|
|
details.AddRange(context);
|
|
return details;
|
|
}
|
|
}
|