Compare commits
5 Commits
88dbbbee5d
...
ad5576958f
| Author | SHA1 | Date | |
|---|---|---|---|
| ad5576958f | |||
| e0fffbd4b5 | |||
| ec389bb596 | |||
| 498a183be5 | |||
| 496def0166 |
@@ -1,5 +1,7 @@
|
||||
using Microsoft.Playwright;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.Playwright;
|
||||
|
||||
namespace SfeduSchedule.BrowserScripts;
|
||||
|
||||
public static class MicrosoftLoginHelper
|
||||
{
|
||||
69
SfeduSchedule/Controllers/ProxyController.cs
Normal file
69
SfeduSchedule/Controllers/ProxyController.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.RateLimiting;
|
||||
using SfeduSchedule.Abstractions;
|
||||
using SfeduSchedule.Services;
|
||||
|
||||
namespace SfeduSchedule.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/proxy")]
|
||||
[EnableRateLimiting("throttle")]
|
||||
public class ProxyController(ModeusService modeusService, ILogger<ScheduleController> logger) : ControllerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Получить расписание по пользовательскому запросу.
|
||||
/// </summary>
|
||||
/// <param name="request">Объект запроса, содержащий параметры фильтрации расписания.</param>
|
||||
/// <returns>Список событий расписания.</returns>
|
||||
/// <response code="200">Возвращает расписание</response>
|
||||
/// <response code="429">Слишком много запросов</response>
|
||||
[HttpPost]
|
||||
[Route("events/search")]
|
||||
public async Task<IActionResult> Post([FromBody] ModeusScheduleRequest request)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Поиск аудиторий по пользовательскому запросу.
|
||||
/// </summary>
|
||||
/// <param name="request">Объект запроса, содержащий параметры фильтрации аудиторий.</param>
|
||||
/// <returns>Список аудиторий.</returns>
|
||||
/// <response code="200">Возвращает список аудиторий</response>
|
||||
/// <response code="429">Слишком много запросов</response>
|
||||
[HttpPost]
|
||||
[Route("rooms/search")]
|
||||
public async Task<IActionResult> 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);
|
||||
}
|
||||
}
|
||||
@@ -9,12 +9,12 @@ using SfeduSchedule.Services;
|
||||
namespace SfeduSchedule.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
[Route("api/schedule")]
|
||||
[EnableRateLimiting("throttle")]
|
||||
public class ScheduleController(ModeusService modeusService, ILogger<ScheduleController> logger) : ControllerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Получить расписание по пользовательскому запросу.
|
||||
/// [УСТАРЕЛО] Получить расписание по пользовательскому запросу.
|
||||
/// </summary>
|
||||
/// <param name="request">Объект запроса, содержащий параметры фильтрации расписания.</param>
|
||||
/// <returns>Список событий расписания.</returns>
|
||||
@@ -32,7 +32,7 @@ public class ScheduleController(ModeusService modeusService, ILogger<ScheduleCon
|
||||
{
|
||||
logger.LogError("Ошибка при получении расписания\n\n" + e.Message + "\n\n" + e.StackTrace +
|
||||
"\n\n JSON: " +
|
||||
JsonSerializer.Serialize(request, GlobalVariables.jsonSerializerOptions));
|
||||
JsonSerializer.Serialize(request, GlobalVariables.JsonSerializerOptions));
|
||||
return StatusCode((int)(e.StatusCode ?? HttpStatusCode.InternalServerError),
|
||||
"Proxied Modeus: " + e.Message);
|
||||
}
|
||||
@@ -41,7 +41,7 @@ public class ScheduleController(ModeusService modeusService, ILogger<ScheduleCon
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Поиск аудиторий по пользовательскому запросу.
|
||||
/// [УСТАРЕЛО] Поиск аудиторий по пользовательскому запросу.
|
||||
/// </summary>
|
||||
/// <param name="request">Объект запроса, содержащий параметры фильтрации аудиторий.</param>
|
||||
/// <returns>Список аудиторий.</returns>
|
||||
@@ -59,7 +59,7 @@ public class ScheduleController(ModeusService modeusService, ILogger<ScheduleCon
|
||||
catch (HttpRequestException e)
|
||||
{
|
||||
logger.LogError("Ошибка при поиске аудиторий\n\n" + e.Message + "\n\n" + e.StackTrace + "\n\n JSON: " +
|
||||
JsonSerializer.Serialize(request, GlobalVariables.jsonSerializerOptions));
|
||||
JsonSerializer.Serialize(request, GlobalVariables.JsonSerializerOptions));
|
||||
return StatusCode((int)(e.StatusCode ?? HttpStatusCode.InternalServerError),
|
||||
"Proxied Modeus: " + e.Message);
|
||||
}
|
||||
|
||||
@@ -5,14 +5,10 @@ using SfeduSchedule.Services;
|
||||
namespace SfeduSchedule.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
[Route("api/sfedu")]
|
||||
[Authorize(AuthenticationSchemes = "OpenIdConnect")]
|
||||
public class SfeduController : ControllerBase
|
||||
public class SfeduController(ModeusService modeusService) : ControllerBase
|
||||
{
|
||||
private readonly ModeusService _modeusService;
|
||||
public SfeduController(ModeusService modeusService) =>
|
||||
_modeusService = modeusService;
|
||||
|
||||
/// <summary>
|
||||
/// Получить GUID пользователя через авторизацию Microsoft.
|
||||
/// </summary>
|
||||
@@ -30,7 +26,7 @@ namespace SfeduSchedule.Controllers
|
||||
if (string.IsNullOrEmpty(name))
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
|
||||
var guid = await _modeusService.GetGuidAsync(name);
|
||||
var guid = await modeusService.GetGuidAsync(name);
|
||||
if (string.IsNullOrEmpty(guid))
|
||||
return NotFound();
|
||||
|
||||
|
||||
@@ -5,6 +5,6 @@ namespace SfeduSchedule
|
||||
public static class GlobalVariables
|
||||
{
|
||||
public static string JwtFilePath { get; set; } = "data/jwt.txt";
|
||||
public static readonly JsonSerializerOptions jsonSerializerOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping };
|
||||
public static readonly JsonSerializerOptions JsonSerializerOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping };
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using Microsoft.Playwright;
|
||||
using Quartz;
|
||||
using SfeduSchedule.BrowserScripts;
|
||||
|
||||
namespace SfeduSchedule.Jobs;
|
||||
|
||||
|
||||
@@ -18,20 +18,26 @@ public static class PluginLoader
|
||||
|
||||
foreach (var file in Directory.EnumerateFiles(pluginsDir, "*.plugin.dll", SearchOption.AllDirectories))
|
||||
{
|
||||
var path = Path.GetFullPath(file);
|
||||
var alc = new PluginLoadContext(path);
|
||||
var asm = alc.LoadFromAssemblyPath(path);
|
||||
try
|
||||
{
|
||||
var path = Path.GetFullPath(file);
|
||||
var alc = new PluginLoadContext(path);
|
||||
var asm = alc.LoadFromAssemblyPath(path);
|
||||
|
||||
// Ищем реализацию IPlugin
|
||||
var pluginType = asm
|
||||
.GetTypes()
|
||||
.FirstOrDefault(t => typeof(IPlugin).IsAssignableFrom(t) && !t.IsAbstract && !t.IsInterface);
|
||||
var pluginType = asm
|
||||
.GetTypes()
|
||||
.FirstOrDefault(t => typeof(IPlugin).IsAssignableFrom(t) && !t.IsAbstract && !t.IsInterface);
|
||||
|
||||
if (pluginType is null)
|
||||
continue;
|
||||
if (pluginType is null)
|
||||
continue;
|
||||
|
||||
var instance = (IPlugin)Activator.CreateInstance(pluginType)!;
|
||||
result.Add(new LoadedPlugin(instance, asm, alc));
|
||||
var instance = (IPlugin)Activator.CreateInstance(pluginType)!;
|
||||
result.Add(new LoadedPlugin(instance, asm, alc));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Ошибка загрузки плагина {file}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -39,15 +45,9 @@ public static class PluginLoader
|
||||
}
|
||||
|
||||
// Отдельный контекст загрузки для изоляции зависимостей плагина
|
||||
public sealed class PluginLoadContext : AssemblyLoadContext
|
||||
public sealed class PluginLoadContext(string pluginMainAssemblyPath) : AssemblyLoadContext(isCollectible: true)
|
||||
{
|
||||
private readonly AssemblyDependencyResolver _resolver;
|
||||
|
||||
public PluginLoadContext(string pluginMainAssemblyPath)
|
||||
: base(isCollectible: true)
|
||||
{
|
||||
_resolver = new AssemblyDependencyResolver(pluginMainAssemblyPath);
|
||||
}
|
||||
private readonly AssemblyDependencyResolver _resolver = new(pluginMainAssemblyPath);
|
||||
|
||||
// Разрешаем управляемые зависимости плагина из его папки.
|
||||
// Возвращаем null, чтобы отдать решение в Default ALC для общих сборок (например, SfeduSchedule.Abstractions).
|
||||
|
||||
@@ -20,9 +20,7 @@ string updateJwtCron = configuration["UPDATE_JWT_CRON"] ?? "0 0 4 ? * *";
|
||||
|
||||
// Если не указана TZ, ставим Europe/Moscow
|
||||
if (string.IsNullOrEmpty(configuration["TZ"]))
|
||||
{
|
||||
configuration["TZ"] = "Europe/Moscow";
|
||||
}
|
||||
|
||||
int permitLimit = int.TryParse(configuration["PERMIT_LIMIT"], out var parsedPermitLimit) ? parsedPermitLimit : 40;
|
||||
int timeLimit = int.TryParse(configuration["TIME_LIMIT"], out var parsedTimeLimit) ? parsedTimeLimit : 10;
|
||||
@@ -39,6 +37,7 @@ var pluginsPath = Path.Combine(dataDirectory, "Plugins");
|
||||
|
||||
builder.Logging.ClearProviders();
|
||||
builder.Logging.AddConsole();
|
||||
builder.Logging.AddFilter("Quartz", LogLevel.Warning);
|
||||
if (!string.IsNullOrEmpty(tgChatId) && !string.IsNullOrEmpty(tgToken))
|
||||
builder.Logging.AddTelegram(options =>
|
||||
{
|
||||
@@ -50,7 +49,8 @@ if (!string.IsNullOrEmpty(tgChatId) && !string.IsNullOrEmpty(tgToken))
|
||||
{
|
||||
{ "Default", LogLevel.Error },
|
||||
{ "SfeduSchedule.Jobs.UpdateJwtJob", LogLevel.Information },
|
||||
{ "Program", LogLevel.Information }
|
||||
{ "Program", LogLevel.Information },
|
||||
{ "Quartz", LogLevel.Warning }
|
||||
};
|
||||
});
|
||||
|
||||
@@ -67,9 +67,9 @@ builder.Services.AddAuthorization();
|
||||
builder.Services.AddMicrosoftIdentityWebAppAuthentication(builder.Configuration);
|
||||
|
||||
// Загружаем плагины (изолированно), даём им зарегистрировать DI и подключаем контроллеры
|
||||
var loaded = PluginLoader.LoadPlugins(pluginsPath);
|
||||
Console.WriteLine("Plugins count: " + loaded.Count);
|
||||
foreach (var p in loaded)
|
||||
var loadedPlugins = PluginLoader.LoadPlugins(pluginsPath);
|
||||
Console.WriteLine("Plugins count: " + loadedPlugins.Count);
|
||||
foreach (var p in loadedPlugins)
|
||||
{
|
||||
Console.WriteLine("Loading plugin: " + p.Instance.Name);
|
||||
|
||||
@@ -108,6 +108,14 @@ builder.Services.AddSwaggerGen(options =>
|
||||
var pluginXmlFile = "SfeduSchedule.Abstractions.xml";
|
||||
var pluginXmlPath = Path.Combine(AppContext.BaseDirectory, pluginXmlFile);
|
||||
options.IncludeXmlComments(pluginXmlPath);
|
||||
|
||||
// Добавление документации плагинов
|
||||
foreach (var p in loadedPlugins)
|
||||
{
|
||||
var pluginXmlFullPath = p.Assembly.Location.Replace("dll", "xml");
|
||||
if (File.Exists(pluginXmlFullPath))
|
||||
options.IncludeXmlComments(pluginXmlFullPath);
|
||||
}
|
||||
|
||||
// Добавляем только схему авторизации по ApiKey
|
||||
options.AddSecurityDefinition(ApiKeyAuthenticationDefaults.Scheme, new Microsoft.OpenApi.Models.OpenApiSecurityScheme
|
||||
@@ -225,9 +233,8 @@ app.MapGet("/", async context =>
|
||||
app.MapControllers();
|
||||
|
||||
// Маршруты Minimal API из плагинов
|
||||
foreach (var p in loaded)
|
||||
foreach (var p in loadedPlugins)
|
||||
{
|
||||
logger.LogInformation("Mapping endpoints for plugin: {PluginName}", p.Instance.Name);
|
||||
p.Instance.MapEndpoints(app);
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace SfeduSchedule.Services
|
||||
{
|
||||
var request = new HttpRequestMessage(HttpMethod.Post,
|
||||
$"schedule-calendar-v2/api/calendar/events/search?tz={_configuration["TZ"]!}");
|
||||
request.Content = new StringContent(JsonSerializer.Serialize(msr, GlobalVariables.jsonSerializerOptions),
|
||||
request.Content = new StringContent(JsonSerializer.Serialize(msr, GlobalVariables.JsonSerializerOptions),
|
||||
System.Text.Encoding.UTF8, "application/json");
|
||||
var response = await _httpClient.SendAsync(request);
|
||||
_logger.LogInformation("GetScheduleAsync: Ответ получен: {StatusCode}", response.StatusCode);
|
||||
@@ -59,7 +59,7 @@ namespace SfeduSchedule.Services
|
||||
{
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, $"schedule-calendar-v2/api/campus/rooms/search");
|
||||
request.Content =
|
||||
new StringContent(JsonSerializer.Serialize(requestDto, GlobalVariables.jsonSerializerOptions),
|
||||
new StringContent(JsonSerializer.Serialize(requestDto, GlobalVariables.JsonSerializerOptions),
|
||||
System.Text.Encoding.UTF8, "application/json");
|
||||
var response = await _httpClient.SendAsync(request);
|
||||
_logger.LogInformation("SearchRoomsAsync: Ответ получен: {StatusCode}", response.StatusCode);
|
||||
@@ -122,22 +122,22 @@ namespace SfeduSchedule.Services
|
||||
case null:
|
||||
_logger.LogError(
|
||||
"GetScheduleJsonAsync: scheduleJson is null. Schedule: {Schedule}\n Request: {msr}",
|
||||
schedule, JsonSerializer.Serialize(msr, GlobalVariables.jsonSerializerOptions));
|
||||
schedule, JsonSerializer.Serialize(msr, GlobalVariables.JsonSerializerOptions));
|
||||
break;
|
||||
case { Embedded: null }:
|
||||
_logger.LogError(
|
||||
"GetScheduleJsonAsync: scheduleJson.Embedded is null. scheduleJson: {@scheduleJson}\n Request: {msr}",
|
||||
scheduleJson, JsonSerializer.Serialize(msr, GlobalVariables.jsonSerializerOptions));
|
||||
scheduleJson, JsonSerializer.Serialize(msr, GlobalVariables.JsonSerializerOptions));
|
||||
break;
|
||||
case { Embedded.Events: null }:
|
||||
_logger.LogError(
|
||||
"GetScheduleJsonAsync: scheduleJson.Embedded.Events is null. Embedded: {@Embedded}\n Request: {msr}",
|
||||
scheduleJson.Embedded, JsonSerializer.Serialize(msr, GlobalVariables.jsonSerializerOptions));
|
||||
scheduleJson.Embedded, JsonSerializer.Serialize(msr, GlobalVariables.JsonSerializerOptions));
|
||||
break;
|
||||
case { Embedded.Events.Length: 0 }:
|
||||
_logger.LogWarning(
|
||||
"GetScheduleJsonAsync: scheduleJson.Embedded.Events is empty. Embedded: {@Embedded}\n Request: {msr}",
|
||||
scheduleJson.Embedded, JsonSerializer.Serialize(msr, GlobalVariables.jsonSerializerOptions));
|
||||
scheduleJson.Embedded, JsonSerializer.Serialize(msr, GlobalVariables.JsonSerializerOptions));
|
||||
break;
|
||||
default:
|
||||
return scheduleJson;
|
||||
@@ -147,7 +147,7 @@ namespace SfeduSchedule.Services
|
||||
{
|
||||
_logger.LogError(ex,
|
||||
"GetScheduleJsonAsync: Deserialization failed. Schedule: {Schedule}\n Request: {msr}", schedule,
|
||||
JsonSerializer.Serialize(msr, GlobalVariables.jsonSerializerOptions));
|
||||
JsonSerializer.Serialize(msr, GlobalVariables.JsonSerializerOptions));
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -158,7 +158,7 @@ namespace SfeduSchedule.Services
|
||||
Schedule? scheduleJson = await GetScheduleJsonAsync(msr);
|
||||
if (scheduleJson == null)
|
||||
{
|
||||
_logger.LogError("GetIcsAsync: scheduleJson is null after deserialization. Request: " + JsonSerializer.Serialize(msr, GlobalVariables.jsonSerializerOptions));
|
||||
_logger.LogError("GetIcsAsync: scheduleJson is null after deserialization. Request: " + JsonSerializer.Serialize(msr, GlobalVariables.JsonSerializerOptions));
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -252,7 +252,7 @@ namespace SfeduSchedule.Services
|
||||
shortNameCourse = courseUnitRealization.NameShort ?? "";
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (Exception)
|
||||
{
|
||||
// Ignored
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user