using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using BewerbungsSeite.Models.SiteContent;
using BewerbungsSeite.Services;
namespace BewerbungsSeite.Controllers.Api.V1;
///
/// Schreibzugriff auf den Seiteninhalt. Nur fuer angemeldete Administratoren:
/// die Konvention AuthorizeFolder("/Admin") gilt nur fuer Razor Pages, deshalb
/// steht das [Authorize] hier ausdruecklich am Controller.
///
[Authorize]
[AutoValidateAntiforgeryToken]
[ApiController]
[Route("api/v1/[controller]")]
public class PageContentController(
IPageContentService pageContentService,
IWebHostEnvironment environment,
ILogger logger) : ControllerBase
{
private static readonly string[] AllowedImageExtensions = [".jpg", ".jpeg", ".png", ".webp", ".gif"];
private static readonly string[] AllowedDocumentExtensions = [".pdf"];
private const long MaxImageBytes = 5 * 1024 * 1024;
private const long MaxDocumentBytes = 20 * 1024 * 1024;
/// Schreibt den kompletten Seiteninhalt zurueck.
[HttpPut]
public async Task Put([FromBody] PageContent content)
{
if (content is null)
{
return BadRequest(new { error = "Kein Inhalt übermittelt." });
}
await pageContentService.SaveAsync(content, HttpContext.RequestAborted);
return Ok(new { success = true, savedAt = DateTime.UtcNow });
}
///
/// Nimmt ein Bild entgegen, legt es unter wwwroot/uploads/images ab und
/// liefert den Pfad zurueck, der anschliessend im JSON gespeichert wird.
///
[HttpPost("images")]
[RequestSizeLimit(MaxImageBytes)]
public Task UploadImage(IFormFile? file) =>
StoreAsync(file, "images", AllowedImageExtensions, MaxImageBytes,
requiredContentTypePrefix: "image/",
wrongTypeMessage: "Erlaubt sind JPG, PNG, WEBP und GIF.",
tooLargeMessage: "Die Datei ist grösser als 5 MB.");
///
/// Nimmt ein Dokument entgegen (PDF), legt es unter wwwroot/uploads/documents
/// ab und liefert den Pfad zurueck.
///
[HttpPost("documents")]
[RequestSizeLimit(MaxDocumentBytes)]
public Task UploadDocument(IFormFile? file) =>
StoreAsync(file, "documents", AllowedDocumentExtensions, MaxDocumentBytes,
requiredContentTypePrefix: null,
wrongTypeMessage: "Erlaubt sind nur PDF-Dateien.",
tooLargeMessage: "Die Datei ist grösser als 20 MB.");
private async Task StoreAsync(
IFormFile? file,
string folder,
string[] allowedExtensions,
long maxBytes,
string? requiredContentTypePrefix,
string wrongTypeMessage,
string tooLargeMessage)
{
if (file is null || file.Length == 0)
{
return BadRequest(new { error = "Keine Datei übermittelt." });
}
if (file.Length > maxBytes)
{
return BadRequest(new { error = tooLargeMessage });
}
var extension = Path.GetExtension(file.FileName).ToLowerInvariant();
if (!allowedExtensions.Contains(extension))
{
return BadRequest(new { error = wrongTypeMessage });
}
if (requiredContentTypePrefix is not null &&
!file.ContentType.StartsWith(requiredContentTypePrefix, StringComparison.OrdinalIgnoreCase))
{
return BadRequest(new { error = wrongTypeMessage });
}
// Der Dateiname wird selbst vergeben; der Name aus dem Browser wird nie uebernommen.
var fileName = $"{Guid.NewGuid():N}{extension}";
var uploadDirectory = Path.Combine(environment.WebRootPath, "uploads", folder);
Directory.CreateDirectory(uploadDirectory);
var targetPath = Path.Combine(uploadDirectory, fileName);
await using (var stream = System.IO.File.Create(targetPath))
{
await file.CopyToAsync(stream, HttpContext.RequestAborted);
}
logger.LogInformation("Datei hochgeladen: {Folder}/{FileName}", folder, fileName);
return Ok(new { url = $"/uploads/{folder}/{fileName}" });
}
}