2025-02-02 03:39:30 +03:00
|
|
|
|
using Asp.Versioning;
|
|
|
|
|
using Cronos;
|
2025-02-03 03:44:40 +03:00
|
|
|
|
using Microsoft.AspNetCore.Http;
|
2025-02-02 03:39:30 +03:00
|
|
|
|
using Microsoft.AspNetCore.Mvc;
|
2025-02-03 10:55:47 +03:00
|
|
|
|
using Microsoft.EntityFrameworkCore;
|
2025-02-03 03:44:40 +03:00
|
|
|
|
using Microsoft.Extensions.DependencyInjection;
|
2025-02-02 03:39:30 +03:00
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
|
using Microsoft.Extensions.Options;
|
|
|
|
|
using Mirea.Api.DataAccess.Persistence;
|
|
|
|
|
using Mirea.Api.Dto.Common;
|
|
|
|
|
using Mirea.Api.Dto.Responses.Configuration;
|
|
|
|
|
using Mirea.Api.Endpoint.Common.Exceptions;
|
|
|
|
|
using Mirea.Api.Endpoint.Common.MapperDto;
|
|
|
|
|
using Mirea.Api.Endpoint.Common.Services;
|
|
|
|
|
using Mirea.Api.Endpoint.Configuration.Model;
|
|
|
|
|
using Mirea.Api.Endpoint.Configuration.Model.GeneralSettings;
|
2025-02-03 03:44:40 +03:00
|
|
|
|
using Mirea.Api.Endpoint.Sync;
|
2025-02-02 03:39:30 +03:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.ComponentModel.DataAnnotations;
|
2025-02-03 03:44:40 +03:00
|
|
|
|
using System.IO;
|
2025-02-02 03:39:30 +03:00
|
|
|
|
using System.Linq;
|
2025-02-03 03:44:40 +03:00
|
|
|
|
using System.Threading;
|
|
|
|
|
using System.Threading.Tasks;
|
2025-02-02 03:39:30 +03:00
|
|
|
|
|
|
|
|
|
namespace Mirea.Api.Endpoint.Controllers.V1.Configuration;
|
|
|
|
|
|
|
|
|
|
[ApiVersion("1.0")]
|
2025-02-03 03:44:40 +03:00
|
|
|
|
public class ScheduleController(ILogger<ScheduleController> logger, IOptionsSnapshot<GeneralConfig> config, UberDbContext dbContext, IServiceProvider provider) : ConfigurationBaseController
|
2025-02-02 03:39:30 +03:00
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Retrieves the cron update schedule and calculates the next scheduled tasks based on the provided depth.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="depth">The depth of the next tasks to retrieve.</param>
|
|
|
|
|
/// <returns>Cron expression and the list of next scheduled task dates.</returns>
|
|
|
|
|
[HttpGet("CronUpdateSchedule")]
|
2025-02-02 04:50:35 +03:00
|
|
|
|
public ActionResult<CronUpdateScheduleResponse> CronUpdateSchedule([FromQuery][Range(0, 10)] int depth = 5)
|
2025-02-02 03:39:30 +03:00
|
|
|
|
{
|
|
|
|
|
var cronExpression = CronExpression.Parse(config.Value.ScheduleSettings!.CronUpdateSchedule);
|
|
|
|
|
var nextTasks = config.Value.ScheduleSettings!.CronUpdateSkipDateList.GetNextTask(cronExpression, depth);
|
|
|
|
|
|
|
|
|
|
return new CronUpdateScheduleResponse()
|
|
|
|
|
{
|
|
|
|
|
Cron = config.Value.ScheduleSettings!.CronUpdateSchedule,
|
|
|
|
|
NextStart = nextTasks.Select(x => DateTime.SpecifyKind(x.DateTime, DateTimeKind.Local)).ToList()
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Updates the cron update schedule with the provided cron expression.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="cron">The cron expression to set as the new schedule.</param>
|
|
|
|
|
/// <returns>Cron expression and the list of next scheduled task dates.</returns>
|
|
|
|
|
/// <exception cref="ControllerArgumentException">Thrown if the provided cron expression is invalid.</exception>
|
|
|
|
|
[HttpPost("CronUpdateSchedule")]
|
2025-02-02 04:51:09 +03:00
|
|
|
|
public ActionResult<CronUpdateScheduleResponse> CronUpdateSchedule([FromQuery] string cron)
|
2025-02-02 03:39:30 +03:00
|
|
|
|
{
|
|
|
|
|
cron = cron.Trim();
|
|
|
|
|
if (!CronExpression.TryParse(cron, CronFormat.Standard, out _))
|
|
|
|
|
throw new ControllerArgumentException("Incorrect cron value.");
|
|
|
|
|
|
|
|
|
|
if (config.Value.ScheduleSettings!.CronUpdateSchedule == cron)
|
2025-02-02 04:50:54 +03:00
|
|
|
|
return CronUpdateSchedule();
|
2025-02-02 03:39:30 +03:00
|
|
|
|
|
|
|
|
|
config.Value.ScheduleSettings!.CronUpdateSchedule = cron;
|
|
|
|
|
config.Value.SaveSetting();
|
|
|
|
|
|
2025-02-02 04:50:54 +03:00
|
|
|
|
return CronUpdateSchedule();
|
2025-02-02 03:39:30 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Retrieves the start term date from the configuration.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns>Start term date.</returns>
|
|
|
|
|
[HttpGet("StartTerm")]
|
|
|
|
|
public ActionResult<DateOnly> StartTerm() =>
|
|
|
|
|
config.Value.ScheduleSettings!.StartTerm;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Updates the start term date in the configuration.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="startTerm">The new start term date to set.</param>
|
|
|
|
|
/// <param name="force">If true, forces an update by deleting all existing lessons.</param>
|
|
|
|
|
/// <returns>Success or failure.</returns>
|
|
|
|
|
/// <exception cref="ControllerArgumentException">Thrown if the start term date is more than 6 months in the past or future.</exception>
|
|
|
|
|
[HttpPost("StartTerm")]
|
2025-02-02 04:51:09 +03:00
|
|
|
|
public ActionResult StartTerm([FromQuery] DateOnly startTerm, [FromQuery] bool force = false)
|
2025-02-02 03:39:30 +03:00
|
|
|
|
{
|
|
|
|
|
var differentByTime = DateTime.Now - startTerm.ToDateTime(new TimeOnly(0, 0, 0));
|
|
|
|
|
if (differentByTime > TimeSpan.FromDays(190) || differentByTime.Multiply(-1) > TimeSpan.FromDays(190))
|
|
|
|
|
throw new ControllerArgumentException("The semester can't start more than 6 months from now, and it can't have started more than 6 months ago either.");
|
|
|
|
|
|
|
|
|
|
config.Value.ScheduleSettings!.StartTerm = startTerm;
|
|
|
|
|
config.Value.SaveSetting();
|
|
|
|
|
|
|
|
|
|
if (!force)
|
|
|
|
|
return Ok();
|
|
|
|
|
|
|
|
|
|
logger.LogWarning("A force update is being performed at the beginning of the semester (all classes will be deleted).");
|
|
|
|
|
|
|
|
|
|
dbContext.Lessons.RemoveRange(dbContext.Lessons.ToList());
|
|
|
|
|
dbContext.SaveChanges();
|
|
|
|
|
|
|
|
|
|
return Ok();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Retrieves the list of cron update skip dates filtered by the current date.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns>Cron update skip dates.</returns>
|
|
|
|
|
[HttpGet("CronUpdateSkip")]
|
2025-02-02 20:31:52 +03:00
|
|
|
|
public ActionResult<List<CronUpdateSkip>> CronUpdateSkip()
|
|
|
|
|
{
|
|
|
|
|
var generalConfig = config.Value;
|
|
|
|
|
|
|
|
|
|
generalConfig.ScheduleSettings!.CronUpdateSkipDateList =
|
|
|
|
|
generalConfig.ScheduleSettings.CronUpdateSkipDateList.Filter();
|
|
|
|
|
generalConfig.SaveSetting();
|
|
|
|
|
|
|
|
|
|
return generalConfig.ScheduleSettings!.CronUpdateSkipDateList
|
2025-02-02 20:28:04 +03:00
|
|
|
|
.ConvertToDto();
|
2025-02-02 20:31:52 +03:00
|
|
|
|
}
|
2025-02-03 03:44:40 +03:00
|
|
|
|
|
2025-02-02 03:39:30 +03:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Updates the list of cron update skip dates in the configuration.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="cronUpdateDate">The list of cron update skip dates to set.</param>
|
|
|
|
|
/// <returns>Success or failure.</returns>
|
|
|
|
|
/// <exception cref="ControllerArgumentException">Thrown if the provided list of cron update skip dates is invalid.</exception>
|
|
|
|
|
[HttpPost("CronUpdateSkip")]
|
|
|
|
|
public ActionResult CronUpdateSkip([FromBody] List<CronUpdateSkip> cronUpdateDate)
|
|
|
|
|
{
|
|
|
|
|
List<ScheduleSettings.CronUpdateSkip> result;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
result = cronUpdateDate.ConvertFromDto();
|
|
|
|
|
}
|
|
|
|
|
catch (ArgumentException ex)
|
|
|
|
|
{
|
|
|
|
|
throw new ControllerArgumentException(ex.Message);
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-02 20:28:04 +03:00
|
|
|
|
config.Value.ScheduleSettings!.CronUpdateSkipDateList = result.Filter();
|
2025-02-02 03:39:30 +03:00
|
|
|
|
config.Value.SaveSetting();
|
|
|
|
|
|
|
|
|
|
return Ok();
|
|
|
|
|
}
|
2025-02-03 03:44:40 +03:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Uploads schedule files and initiates synchronization.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="files">The list of schedule files to upload.</param>
|
|
|
|
|
/// <param name="defaultCampus">The default campus for each uploaded file. Must match the number of files.</param>
|
|
|
|
|
/// <param name="force">If true, removes all existing lessons before synchronization. Default is false.</param>
|
|
|
|
|
/// <returns>Success or failure.</returns>
|
|
|
|
|
/// <exception cref="ControllerArgumentException">
|
|
|
|
|
/// Thrown if:
|
|
|
|
|
/// - No files are provided.
|
|
|
|
|
/// - The number of default campuses does not match the number of files.
|
|
|
|
|
/// - Any default campus is null or empty.
|
|
|
|
|
/// </exception>
|
|
|
|
|
[HttpPost("Upload")]
|
|
|
|
|
public async Task<ActionResult> UploadScheduleFiles(List<IFormFile>? files, [FromQuery] string[]? defaultCampus, [FromQuery] bool force = false)
|
|
|
|
|
{
|
|
|
|
|
if (files == null || files.Count == 0)
|
|
|
|
|
throw new ControllerArgumentException("No files were found.");
|
|
|
|
|
|
|
|
|
|
if (defaultCampus == null || files.Count != defaultCampus.Length)
|
|
|
|
|
throw new ControllerArgumentException("No default campuses are specified for the file.");
|
|
|
|
|
|
|
|
|
|
if (defaultCampus.Any(string.IsNullOrEmpty))
|
|
|
|
|
throw new ControllerArgumentException("Each file should have a default campus.");
|
|
|
|
|
|
|
|
|
|
var tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetFileNameWithoutExtension(Path.GetRandomFileName()));
|
|
|
|
|
|
|
|
|
|
if (!Directory.Exists(tempDirectory))
|
|
|
|
|
Directory.CreateDirectory(tempDirectory);
|
|
|
|
|
|
|
|
|
|
List<(string, string)> filePaths = [];
|
|
|
|
|
|
|
|
|
|
for (var i = 0; i < files.Count; i++)
|
|
|
|
|
{
|
|
|
|
|
if (files[i].Length <= 0)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
var filePath = Path.Combine(tempDirectory, files[i].FileName);
|
|
|
|
|
|
|
|
|
|
await using var stream = new FileStream(filePath, FileMode.Create);
|
|
|
|
|
await files[i].CopyToAsync(stream);
|
|
|
|
|
|
|
|
|
|
filePaths.Add((filePath, defaultCampus[i]));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (force)
|
|
|
|
|
{
|
2025-02-03 10:55:47 +03:00
|
|
|
|
dbContext.Lessons.RemoveRange(await dbContext.Lessons.ToListAsync());
|
2025-02-03 03:44:40 +03:00
|
|
|
|
await dbContext.SaveChangesAsync();
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-03 10:55:47 +03:00
|
|
|
|
var scopeFactory = provider.GetRequiredService<IServiceScopeFactory>();
|
2025-02-03 11:25:39 +03:00
|
|
|
|
ThreadPool.QueueUserWorkItem(async void (_) =>
|
2025-02-03 10:55:47 +03:00
|
|
|
|
{
|
2025-02-03 11:25:39 +03:00
|
|
|
|
try
|
|
|
|
|
{
|
2025-02-11 17:13:13 +03:00
|
|
|
|
using var scope = scopeFactory.CreateScope();
|
2025-02-03 11:25:39 +03:00
|
|
|
|
var sync = (ScheduleSynchronizer)ActivatorUtilities.GetServiceOrCreateInstance(scope.ServiceProvider, typeof(ScheduleSynchronizer));
|
2025-02-03 10:55:47 +03:00
|
|
|
|
|
2025-02-11 17:13:13 +03:00
|
|
|
|
await sync.StartSync(filePaths, CancellationToken.None);
|
2025-02-03 11:25:39 +03:00
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine(ex.Message);
|
|
|
|
|
}
|
|
|
|
|
});
|
2025-02-03 03:44:40 +03:00
|
|
|
|
|
|
|
|
|
return Ok();
|
|
|
|
|
}
|
2025-02-02 03:39:30 +03:00
|
|
|
|
}
|