feat: добавил отправку уведомлений по почте
🚀 Create and publish a Docker image / Detect changes in backend and frontend (push) Successful in 10s
🚀 Create and publish a Docker image / Build & publish backend image (push) Successful in 1m17s
🚀 Create and publish a Docker image / Build & publish frontend image (push) Successful in 12s
🚀 Create and publish a Docker image / Update stack on Portainer (push) Successful in 6s
🚀 Create and publish a Docker image / Detect changes in backend and frontend (push) Successful in 10s
🚀 Create and publish a Docker image / Build & publish backend image (push) Successful in 1m17s
🚀 Create and publish a Docker image / Build & publish frontend image (push) Successful in 12s
🚀 Create and publish a Docker image / Update stack on Portainer (push) Successful in 6s
This commit is contained in:
@@ -40,7 +40,7 @@ public class AuthController : ControllerBase
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<ActionResult<AuthResponse>> LoginMicrosoft([FromBody] LoginMicrosoftRequest request)
|
||||
{
|
||||
var result = await _auth.LoginWithMicrosoftAsync(request.AuthorizationCode, request.RedirectUri);
|
||||
var result = await _auth.LoginWithMicrosoftAsync(request.AuthorizationCode, request.RedirectUri, GetClientIpAddress());
|
||||
SetRefreshTokenCookie(result.RefreshToken);
|
||||
return Ok(result.Response);
|
||||
}
|
||||
@@ -151,7 +151,7 @@ public class AuthController : ControllerBase
|
||||
|
||||
var redirectUri = _config["AzureAd:RedirectUri"] ?? BuildAbsoluteUrl("/api/v1/auth/callback/microsoft");
|
||||
|
||||
var result = await _auth.LoginWithMicrosoftAsync(code, redirectUri);
|
||||
var result = await _auth.LoginWithMicrosoftAsync(code, redirectUri, GetClientIpAddress());
|
||||
SetRefreshTokenCookie(result.RefreshToken);
|
||||
|
||||
var returnUrl = Request.Cookies[MicrosoftReturnUrlCookieName] ?? _config["AzureAd:PostLoginRedirectUri"];
|
||||
@@ -184,7 +184,7 @@ public class AuthController : ControllerBase
|
||||
{
|
||||
if (!HttpContext.RequestServices.GetRequiredService<IWebHostEnvironment>().IsDevelopment())
|
||||
return NotFound();
|
||||
var result = await _auth.DevLoginAsync(request.Email, request.DisplayName, request.Role);
|
||||
var result = await _auth.DevLoginAsync(request.Email, request.DisplayName, request.Role, GetClientIpAddress());
|
||||
SetRefreshTokenCookie(result.RefreshToken);
|
||||
return Ok(result.Response);
|
||||
}
|
||||
@@ -245,6 +245,21 @@ public class AuthController : ControllerBase
|
||||
return Ok(user);
|
||||
}
|
||||
|
||||
private string? GetClientIpAddress()
|
||||
{
|
||||
if (Request.Headers.TryGetValue("X-Forwarded-For", out var forwardedFor))
|
||||
{
|
||||
var firstForwardedAddress = forwardedFor.ToString().Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).FirstOrDefault();
|
||||
if (!string.IsNullOrWhiteSpace(firstForwardedAddress))
|
||||
return firstForwardedAddress;
|
||||
}
|
||||
|
||||
if (Request.Headers.TryGetValue("X-Real-IP", out var realIp) && !string.IsNullOrWhiteSpace(realIp))
|
||||
return realIp;
|
||||
|
||||
return HttpContext.Connection.RemoteIpAddress?.ToString();
|
||||
}
|
||||
|
||||
private void SetRefreshTokenCookie(string token)
|
||||
{
|
||||
Response.Cookies.Append("refreshToken", token, new CookieOptions
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using UniVerse.Application.DTOs.Notifications;
|
||||
using UniVerse.Application.Interfaces;
|
||||
|
||||
namespace UniVerse.Api.Controllers;
|
||||
|
||||
/// <summary>Отправка и планирование уведомлений через доступные каналы.</summary>
|
||||
[ApiController]
|
||||
[Route("api/v1/notifications")]
|
||||
[Authorize(Roles = "Admin")]
|
||||
[Produces("application/json")]
|
||||
public class NotificationsController : ControllerBase
|
||||
{
|
||||
private readonly INotificationService _notifications;
|
||||
|
||||
public NotificationsController(INotificationService notifications)
|
||||
{
|
||||
_notifications = notifications;
|
||||
}
|
||||
|
||||
/// <summary>Отправить уведомление немедленно.</summary>
|
||||
/// <remarks>
|
||||
/// Канал задаётся строкой, например `email`. Новые провайдеры добавляются через `INotificationProvider`.
|
||||
/// </remarks>
|
||||
/// <param name="request">Канал, получатель, тема и текст уведомления.</param>
|
||||
/// <response code="202">Уведомление принято к отправке.</response>
|
||||
/// <response code="401">Требуется аутентификация.</response>
|
||||
/// <response code="403">Требуется роль Admin.</response>
|
||||
[HttpPost("send")]
|
||||
[ProducesResponseType(StatusCodes.Status202Accepted)]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
public async Task<IActionResult> Send([FromBody] SendNotificationRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
var message = new NotificationMessage(
|
||||
request.Channel,
|
||||
request.Recipient,
|
||||
request.Subject,
|
||||
request.Body,
|
||||
request.RecipientName,
|
||||
request.Metadata);
|
||||
|
||||
await _notifications.SendAsync(message, cancellationToken);
|
||||
return Accepted();
|
||||
}
|
||||
|
||||
/// <summary>Запланировать отложенную отправку уведомления через Quartz.NET.</summary>
|
||||
/// <param name="request">Уведомление и момент отправки.</param>
|
||||
/// <response code="202">Уведомление поставлено в очередь Quartz.NET.</response>
|
||||
/// <response code="401">Требуется аутентификация.</response>
|
||||
/// <response code="403">Требуется роль Admin.</response>
|
||||
[HttpPost("schedule")]
|
||||
[ProducesResponseType(typeof(ScheduledNotificationResponse), StatusCodes.Status202Accepted)]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
public async Task<ActionResult<ScheduledNotificationResponse>> Schedule([FromBody] ScheduleNotificationRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
var response = await _notifications.ScheduleAsync(request, cancellationToken);
|
||||
return Accepted(response);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Microsoft.OpenApi;
|
||||
using Quartz;
|
||||
using Serilog;
|
||||
using UniVerse.Api.BackgroundServices;
|
||||
using UniVerse.Api.Filters;
|
||||
@@ -12,6 +13,7 @@ using UniVerse.Application.Interfaces;
|
||||
using UniVerse.Infrastructure.Services;
|
||||
using UniVerse.Infrastructure.Data;
|
||||
using UniVerse.Infrastructure.ExternalServices;
|
||||
using UniVerse.Infrastructure.Notifications;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
@@ -89,6 +91,17 @@ builder.Services.AddScoped<IGamificationService, GamificationService>();
|
||||
builder.Services.AddScoped<IAchievementService, AchievementService>();
|
||||
builder.Services.AddScoped<ILlmAnalysisService, LlmAnalysisService>();
|
||||
builder.Services.AddScoped<IScheduleSyncService, ScheduleSyncService>();
|
||||
builder.Services.AddScoped<INotificationService, NotificationService>();
|
||||
builder.Services.AddScoped<INotificationProvider, EmailNotificationProvider>();
|
||||
builder.Services.AddSingleton<INotificationScheduler, QuartzNotificationScheduler>();
|
||||
builder.Services.AddTransient<NotificationJob>();
|
||||
builder.Services.Configure<EmailNotificationOptions>(builder.Configuration.GetSection("Email:Smtp"));
|
||||
|
||||
builder.Services.AddQuartz();
|
||||
builder.Services.AddQuartzHostedService(options =>
|
||||
{
|
||||
options.WaitForJobsToComplete = true;
|
||||
});
|
||||
|
||||
// --- HTTP Clients ---
|
||||
builder.Services.AddHttpClient<ILlmClient, LlmClient>(client =>
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.1.1" />
|
||||
<PackageReference Include="FluentValidation.AspNetCore" Version="11.3.1" />
|
||||
<PackageReference Include="Quartz.Extensions.Hosting" Version="3.18.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -33,5 +33,16 @@
|
||||
"System": "Information"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Email": {
|
||||
"Smtp": {
|
||||
"Host": "",
|
||||
"Port": 587,
|
||||
"EnableSsl": true,
|
||||
"UserName": "",
|
||||
"Password": "",
|
||||
"FromAddress": "no-reply@universe.local",
|
||||
"FromName": "UniVerse"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user