📄 MatDenDagen/Components/Pages/Admin/Kokbok.razor.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using MatDenDagen.Infrastructure.Storage.BlobStorage;
using MatDenDagen.Infrastructure.Storage.Database;
using MatDenDagen.Models;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;

namespace MatDenDagen.Components.Pages.Admin;

public partial class Kokbok(BlobStorageService blobService, QuestionnaireContext questionnaireContext)
{
    [CascadingParameter]
    private HttpContext? HttpContext { get; set; }

    private bool success;
    private string? errorMessage;
    private List<Cookbook> cookbooks = [];

    protected override async Task OnInitializedAsync()
    {
        var cancellationToken = HttpContext?.RequestAborted ?? CancellationToken.None;
        cookbooks = await questionnaireContext.Cookbooks.OrderBy(c => c.Id).ToListAsync(cancellationToken);

        if (HttpContext?.Request.Method is not "POST")
            return;

        var form = HttpContext.Request.Form;
        var id = form["id"].FirstOrDefault()?.Trim();
        if (id is null || !validId.IsMatch(id))
        {
            errorMessage = "Ogiltigt ID. AnvΓ€nd bokstΓ€ver, siffror, bindestreck och understreck.";
            return;
        }

        var file = form.Files.GetFile("file");
        if (file is null || file.Length == 0)
        {
            errorMessage = "Ingen PDF-fil vald.";
            return;
        }

        if (!file.FileName.EndsWith(".pdf", StringComparison.OrdinalIgnoreCase))
        {
            errorMessage = "Endast PDF-filer Γ€r tillΓ₯tna.";
            return;
        }

        await using var stream = file.OpenReadStream();
        var result = await blobService.SaveBlob(stream, cancellationToken);

        var existing = await questionnaireContext.Cookbooks.FindAsync([id], cancellationToken);
        if (existing is not null)
        {
            blobService.DeleteBlob(existing.BlobId);
            existing.BlobId = result.Id;
            existing.FileName = file.FileName;
        }
        else
        {
            questionnaireContext.Cookbooks.Add(
                new Cookbook
                {
                    Id = id,
                    BlobId = result.Id,
                    FileName = file.FileName,
                }
            );
        }

        await questionnaireContext.SaveChangesAsync(cancellationToken);

        cookbooks = await questionnaireContext.Cookbooks.OrderBy(c => c.Id).ToListAsync(cancellationToken);
        success = true;
    }

    [GeneratedRegex(@"^[a-zA-Z0-9][a-zA-Z0-9_-]*$")]
    private static partial Regex validId { get; }
}