219 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			219 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using Asp.Versioning;
 | |
| using Cronos;
 | |
| using Microsoft.AspNetCore.Http;
 | |
| using Microsoft.AspNetCore.Mvc;
 | |
| using Microsoft.EntityFrameworkCore;
 | |
| using Microsoft.Extensions.DependencyInjection;
 | |
| 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 Mirea.Api.Endpoint.Sync;
 | |
| using System;
 | |
| using System.Collections.Generic;
 | |
| using System.ComponentModel.DataAnnotations;
 | |
| using System.IO;
 | |
| using System.Linq;
 | |
| using System.Threading;
 | |
| using System.Threading.Tasks;
 | |
| 
 | |
| namespace Mirea.Api.Endpoint.Controllers.V1.Configuration;
 | |
| 
 | |
| [ApiVersion("1.0")]
 | |
| public class ScheduleController(ILogger<ScheduleController> logger, IOptionsSnapshot<GeneralConfig> config, UberDbContext dbContext, IServiceProvider provider) : ConfigurationBaseController
 | |
| {
 | |
|     /// <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")]
 | |
|     public ActionResult<CronUpdateScheduleResponse> CronUpdateSchedule([FromQuery][Range(0, 10)] int depth = 5)
 | |
|     {
 | |
|         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")]
 | |
|     public ActionResult<CronUpdateScheduleResponse> CronUpdateSchedule([FromQuery] 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 CronUpdateSchedule();
 | |
| 
 | |
|         config.Value.ScheduleSettings!.CronUpdateSchedule = cron;
 | |
|         config.Value.SaveSetting();
 | |
| 
 | |
|         return CronUpdateSchedule();
 | |
|     }
 | |
| 
 | |
|     /// <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")]
 | |
|     public ActionResult StartTerm([FromQuery] 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();
 | |
|     }
 | |
| 
 | |
|     /// <summary>
 | |
|     /// Retrieves the list of cron update skip dates filtered by the current date.
 | |
|     /// </summary>
 | |
|     /// <returns>Cron update skip dates.</returns>
 | |
|     [HttpGet("CronUpdateSkip")]
 | |
|     public ActionResult<List<CronUpdateSkip>> CronUpdateSkip()
 | |
|     {
 | |
|         var generalConfig = config.Value;
 | |
| 
 | |
|         generalConfig.ScheduleSettings!.CronUpdateSkipDateList =
 | |
|             generalConfig.ScheduleSettings.CronUpdateSkipDateList.Filter();
 | |
|         generalConfig.SaveSetting();
 | |
| 
 | |
|         return generalConfig.ScheduleSettings!.CronUpdateSkipDateList
 | |
|             .ConvertToDto();
 | |
|     }
 | |
| 
 | |
| 
 | |
|     /// <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);
 | |
|         }
 | |
| 
 | |
|         config.Value.ScheduleSettings!.CronUpdateSkipDateList = result.Filter();
 | |
|         config.Value.SaveSetting();
 | |
| 
 | |
|         return Ok();
 | |
|     }
 | |
| 
 | |
|     /// <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)
 | |
|         {
 | |
|             dbContext.Lessons.RemoveRange(await dbContext.Lessons.ToListAsync());
 | |
|             await dbContext.SaveChangesAsync();
 | |
|         }
 | |
| 
 | |
|         var scopeFactory = provider.GetRequiredService<IServiceScopeFactory>();
 | |
|         ThreadPool.QueueUserWorkItem(async void (_) =>
 | |
|         {
 | |
|             try
 | |
|             {
 | |
|                 using var scope = scopeFactory.CreateScope();
 | |
|                 var sync = (ScheduleSynchronizer)ActivatorUtilities.GetServiceOrCreateInstance(scope.ServiceProvider, typeof(ScheduleSynchronizer));
 | |
| 
 | |
|                 await sync.StartSync(filePaths, CancellationToken.None);
 | |
|             }
 | |
|             catch (Exception ex)
 | |
|             {
 | |
|                 Console.WriteLine(ex.Message);
 | |
|             }
 | |
|         });
 | |
| 
 | |
|         return Ok();
 | |
|     }
 | |
| } |