refactor(api): inject IEmailSyncService into SyncController; change OAuth2 authorize route to username param; add Swagger UI metadata and description links

This commit is contained in:
2026-08-17 16:05:43 +02:00
parent 8925428e46
commit 993d9cfea0
3 changed files with 55 additions and 19 deletions

View File

@@ -20,20 +20,20 @@ public class OAuth2Controller(IMediator mediator, IHttpContextAccessor httpConte
/// Step 1: Redirects the user to Google's consent screen for the specified email account. /// Step 1: Redirects the user to Google's consent screen for the specified email account.
/// After consent, Google redirects to <c>/api/oauth2/google/callback</c> with an authorization code. /// After consent, Google redirects to <c>/api/oauth2/google/callback</c> with an authorization code.
/// </summary> /// </summary>
/// <param name="accountId">ID of the email account to authorize.</param> /// <param name="username">the email account to authorize.</param>
/// <param name="cancellationToken">Cancellation token.</param> /// <param name="cancellationToken">Cancellation token.</param>
/// <returns>HTTP 302 redirect to Google consent screen.</returns> /// <returns>HTTP 302 redirect to Google consent screen.</returns>
[HttpGet("google/authorize/{accountId:int}")] [HttpGet("google/authorize/{username}")]
[ProducesResponseType(StatusCodes.Status302Found)] [ProducesResponseType(StatusCodes.Status302Found)]
[ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> AuthorizeGoogle(int accountId, CancellationToken cancellationToken) public async Task<IActionResult> AuthorizeGoogle([FromRoute] string username, CancellationToken cancellationToken)
{ {
var redirectUri = BuildCallbackUri(); var redirectUri = BuildCallbackUri();
var authUrl = await mediator.Send(new GetOAuth2AuthorizationUrlQuery var authUrl = await mediator.Send(new GetOAuth2AuthorizationUrlQuery
{ {
AccountId = accountId, Username = username,
RedirectUri = redirectUri RedirectUri = redirectUri
}, cancellationToken); }, cancellationToken);

View File

@@ -1,6 +1,5 @@
using DigitalData.MessagingService.Infrastructure.Services.Background; using DigitalData.MessagingService.Application.Common.Interfaces;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Hosting;
namespace DigitalData.MessagingService.API.Controllers; namespace DigitalData.MessagingService.API.Controllers;
@@ -9,7 +8,7 @@ namespace DigitalData.MessagingService.API.Controllers;
/// </summary> /// </summary>
[ApiController] [ApiController]
[Route("api/[controller]")] [Route("api/[controller]")]
public class SyncController(IEnumerable<IHostedService> hostedServices) : ControllerBase public class SyncController(IEmailSyncService emailSyncService) : ControllerBase
{ {
/// <summary> /// <summary>
/// Triggers an immediate email sync cycle for all configured accounts, /// Triggers an immediate email sync cycle for all configured accounts,
@@ -22,14 +21,7 @@ public class SyncController(IEnumerable<IHostedService> hostedServices) : Contro
[ProducesResponseType(StatusCodes.Status503ServiceUnavailable)] [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
public IActionResult TriggerSync() public IActionResult TriggerSync()
{ {
var syncWorker = hostedServices.OfType<EmailSyncWorker>().FirstOrDefault(); var syncTime = emailSyncService.ForceTriggerSync();
return Accepted(new { syncTime });
if (syncWorker is null)
return StatusCode(StatusCodes.Status503ServiceUnavailable,
new { Message = "EmailSyncWorker is not running." });
syncWorker.TriggerSync();
return Accepted(new { Message = "Sync triggered. The next cycle will start immediately." });
} }
} }

View File

@@ -69,7 +69,35 @@ try
builder.Services.AddControllers(); builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer(); builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(); builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo
{
Title = "DigitalData MessagingService API",
Version = "v1",
Description = """
Die **DigitalData MessagingService API** stellt Endpunkte zur Verwaltung von E-Mail-Konten,
E-Mail-Profilen sowie zur Verarbeitung und Nachverfolgung eingehender und ausgehender Nachrichten bereit.
---
## Authentifizierung
Für OAuth2-geschützte Endpunkte ist eine Authentifizierung erforderlich.
Rufen Sie den folgenden Endpunkt auf und ersetzen Sie `{E-Mail-Adresse}` durch die zu authentifizierende E-Mail-Adresse:
`/api/OAuth2/google/authorize/{E-Mail-Adresse}`
**Beispiel:** **[/api/OAuth2/google/authorize/htek0100@gmail.com &rarr;](/api/OAuth2/google/authorize/htek0100@gmail.com)**
---
## Weiterführende Links
- [Serilog Log-Viewer &nearr;](/serilog-ui)
"""
});
});
// Required by OAuth2Controller to build callback URIs // Required by OAuth2Controller to build callback URIs
builder.Services.AddHttpContextAccessor(); builder.Services.AddHttpContextAccessor();
@@ -92,14 +120,30 @@ try
// Add Serilog request logging // Add Serilog request logging
app.UseSerilogRequestLogging(); app.UseSerilogRequestLogging();
// Configure Swagger enabled in Development always, and in other environments based on appsettings // Configure Swagger <EFBFBD> enabled in Development always, and in other environments based on appsettings
var swaggerEnabled = app.Environment.IsDevelopment() var swaggerEnabled = app.Environment.IsDevelopment()
|| app.Configuration.GetValue<bool>("Swagger:Enabled"); || app.Configuration.GetValue<bool>("Swagger:Enabled");
if (swaggerEnabled) if (swaggerEnabled)
{ {
app.UseSwagger(); app.UseSwagger();
app.UseSwaggerUI(); app.UseSwaggerUI(ui =>
{
ui.SwaggerEndpoint("/swagger/v1/swagger.json", "DigitalData MessagingService API v1");
// Inject CSS so all description links open in a new tab
ui.InjectStylesheet("data:text/css,.renderedMarkdown a{target:_blank}");
ui.InjectJavascript("data:text/javascript," + Uri.EscapeDataString("""
window.addEventListener('load', () => {
const observer = new MutationObserver(() => {
document.querySelectorAll('.renderedMarkdown a').forEach(a => {
a.setAttribute('target', '_blank');
a.setAttribute('rel', 'noopener noreferrer');
});
});
observer.observe(document.body, { childList: true, subtree: true });
});
"""));
});
} }
app.UseHttpsRedirection(); app.UseHttpsRedirection();