diff --git a/ApiDto/Common/CronUpdateSkip.cs b/ApiDto/Common/CronUpdateSkip.cs new file mode 100644 index 0000000..822dfc6 --- /dev/null +++ b/ApiDto/Common/CronUpdateSkip.cs @@ -0,0 +1,24 @@ +using System; + +namespace Mirea.Api.Dto.Common; + +/// +/// Represents a date or date range to skip during cron update scheduling. +/// +public class CronUpdateSkip +{ + /// + /// Gets or sets the start date of the skip range. + /// + public DateOnly? Start { get; set; } + + /// + /// Gets or sets the end date of the skip range. + /// + public DateOnly? End { get; set; } + + /// + /// Gets or sets a specific date to skip. + /// + public DateOnly? Date { get; set; } +} \ No newline at end of file diff --git a/ApiDto/Responses/Configuration/CronUpdateScheduleResponse.cs b/ApiDto/Responses/Configuration/CronUpdateScheduleResponse.cs new file mode 100644 index 0000000..46bab01 --- /dev/null +++ b/ApiDto/Responses/Configuration/CronUpdateScheduleResponse.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Mirea.Api.Dto.Responses.Configuration; + +/// +/// Represents the response containing the cron update schedule and the next scheduled task dates. +/// +public class CronUpdateScheduleResponse +{ + /// + /// Gets or sets the cron expression representing the update schedule. + /// + [Required] + public required string Cron { get; set; } + + /// + /// Gets or sets the list of next scheduled task dates based on the cron expression. + /// + [Required] + public required List NextStart { get; set; } +} \ No newline at end of file diff --git a/Endpoint/Common/MapperDto/CronUpdateSkipConverter.cs b/Endpoint/Common/MapperDto/CronUpdateSkipConverter.cs new file mode 100644 index 0000000..baa6a0d --- /dev/null +++ b/Endpoint/Common/MapperDto/CronUpdateSkipConverter.cs @@ -0,0 +1,19 @@ +using Mirea.Api.Endpoint.Common.Services; +using Mirea.Api.Endpoint.Configuration.Model.GeneralSettings; +using System.Collections.Generic; +using System.Linq; + +namespace Mirea.Api.Endpoint.Common.MapperDto; + +public static class CronUpdateSkipConverter +{ + public static List ConvertToDto(this IEnumerable pairPeriod) => + pairPeriod.Select(x => new Dto.Common.CronUpdateSkip() + { + Start = x.Start, + End = x.End, + Date = x.Date + }).ToList(); + public static List ConvertFromDto(this IEnumerable pairPeriod) => + pairPeriod.Select(x => x.Get()).ToList(); +} diff --git a/Endpoint/Controllers/V1/Configuration/ScheduleController.cs b/Endpoint/Controllers/V1/Configuration/ScheduleController.cs new file mode 100644 index 0000000..6194520 --- /dev/null +++ b/Endpoint/Controllers/V1/Configuration/ScheduleController.cs @@ -0,0 +1,132 @@ +using Asp.Versioning; +using Cronos; +using Microsoft.AspNetCore.Mvc; +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; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; + +namespace Mirea.Api.Endpoint.Controllers.V1.Configuration; + +[ApiVersion("1.0")] +public class ScheduleController(ILogger logger, IOptionsSnapshot config, UberDbContext dbContext) : ConfigurationBaseController +{ + /// + /// Retrieves the cron update schedule and calculates the next scheduled tasks based on the provided depth. + /// + /// The depth of the next tasks to retrieve. + /// Cron expression and the list of next scheduled task dates. + [HttpGet("CronUpdateSchedule")] + public ActionResult CronUpdateSchedule([FromQuery][Range(0, 5)] int depth = 2) + { + 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() + }; + } + + /// + /// Updates the cron update schedule with the provided cron expression. + /// + /// The cron expression to set as the new schedule. + /// Cron expression and the list of next scheduled task dates. + /// Thrown if the provided cron expression is invalid. + [HttpPost("CronUpdateSchedule")] + public ActionResult CronUpdateSchedule([FromBody] string cron) + { + cron = cron.Trim(); + if (!CronExpression.TryParse(cron, CronFormat.Standard, out _)) + throw new ControllerArgumentException("Incorrect cron value."); + + if (config.Value.ScheduleSettings!.CronUpdateSchedule == cron) + return Ok(CronUpdateSchedule()); + + config.Value.ScheduleSettings!.CronUpdateSchedule = cron; + config.Value.SaveSetting(); + + return Ok(CronUpdateSchedule()); + } + + /// + /// Retrieves the start term date from the configuration. + /// + /// Start term date. + [HttpGet("StartTerm")] + public ActionResult StartTerm() => + config.Value.ScheduleSettings!.StartTerm; + + /// + /// Updates the start term date in the configuration. + /// + /// The new start term date to set. + /// If true, forces an update by deleting all existing lessons. + /// Success or failure. + /// Thrown if the start term date is more than 6 months in the past or future. + [HttpPost("StartTerm")] + public ActionResult StartTerm([FromBody] DateOnly startTerm, [FromQuery] bool force = false) + { + 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(); + } + + /// + /// Retrieves the list of cron update skip dates filtered by the current date. + /// + /// Cron update skip dates. + [HttpGet("CronUpdateSkip")] + public ActionResult> CronUpdateSkip() => + config.Value.ScheduleSettings!.CronUpdateSkipDateList.Filter(DateTime.Now).ConvertToDto(); + + /// + /// Updates the list of cron update skip dates in the configuration. + /// + /// The list of cron update skip dates to set. + /// Success or failure. + /// Thrown if the provided list of cron update skip dates is invalid. + [HttpPost("CronUpdateSkip")] + public ActionResult CronUpdateSkip([FromBody] List cronUpdateDate) + { + List result; + try + { + result = cronUpdateDate.ConvertFromDto(); + } + catch (ArgumentException ex) + { + throw new ControllerArgumentException(ex.Message); + } + + config.Value.ScheduleSettings!.CronUpdateSkipDateList = result.Filter(DateTime.Now); + config.Value.SaveSetting(); + + return Ok(); + } +} \ No newline at end of file