Implement identity verification feature with image upload and token management
All checks were successful
Build And Push Dev Docker Image / docker (push) Successful in 2m2s
All checks were successful
Build And Push Dev Docker Image / docker (push) Successful in 2m2s
This commit is contained in:
95
FoodsharingSiegen.Server/Pages/UploadVerification.razor
Normal file
95
FoodsharingSiegen.Server/Pages/UploadVerification.razor
Normal file
@@ -0,0 +1,95 @@
|
||||
@page "/verify/{Token:guid}"
|
||||
@using FoodsharingSiegen.Contracts.Entity
|
||||
@using System.IO
|
||||
@layout LoginLayout
|
||||
|
||||
<div class="row min-vh-100 align-items-center justify-content-center p-0 p-md-5 m-0">
|
||||
<div class="col-12 col-md-10 col-lg-8 col-xl-5 login-form p-2">
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-body p-3 p-md-5">
|
||||
<div class="text-center mb-4">
|
||||
|
||||
<h5 class="mb-0 text-success d-block d-sm-none"><i class="fa-solid fa-address-card me-2"></i><br>Identitätsprüfung</h5>
|
||||
<h4 class="mb-0 text-success d-none d-sm-block"><i class="fa-solid fa-address-card me-2"></i>Identitätsprüfung</h4>
|
||||
|
||||
<p class="text-muted mt-2">@(AppSettings.Value.Terms.Title)</p>
|
||||
</div>
|
||||
|
||||
@if (_isLoading)
|
||||
{
|
||||
<div class="text-center my-5">
|
||||
<div class="spinner-border text-success" role="status">
|
||||
<span class="visually-hidden">Laden...</span>
|
||||
</div>
|
||||
<p class="mt-3 text-muted">Lade Daten...</p>
|
||||
</div>
|
||||
}
|
||||
else if (_prospect == null)
|
||||
{
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<i class="fa-solid fa-triangle-exclamation me-2"></i> @(_message ?? "Der Link ist ungültig oder abgelaufen. Bitte fordere einen neuen Link an.")
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="alert alert-info mb-4">
|
||||
<strong>Hinweis:</strong> Dies ist die Upload-Seite für den Foodsaver <b>@_prospect.FsId</b>.
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<h5 class="fw-bold">Anleitung:</h5>
|
||||
<ul class="text-muted">
|
||||
<li>Lade hier die Vorder- und Rückseite deines Personalausweises oder Reisepasses hoch.</li>
|
||||
<li>Dein Name und Adresse müssen gut und lesbar erkennbar sein.</li>
|
||||
<li>Wir nutzen diese Bilder ausschließlich zur Identitätsprüfung.</li>
|
||||
<li>Ausschließlich die Botschafter haben Zugriff auf diese Bilder.</li>
|
||||
<li>Die Bilder werden nach der Überprüfung sofort und unwiderruflich von uns gelöscht.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="mb-4 text-center">
|
||||
<span class="badge bg-secondary mb-2 text-wrap">Es können noch bis zu @(5 - _uploadedCount) Bilder hochgeladen werden</span>
|
||||
|
||||
@if (_uploadedCount >= 5)
|
||||
{
|
||||
<div class="alert alert-warning py-2 mb-0">Du hast die maximale Anzahl von 5 Bildern erreicht.</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<InputFile id="fileInput" OnChange="OnInputFileChange" class="d-none" accept="image/*" />
|
||||
<label for="fileInput" class="btn btn-outline-success w-100">
|
||||
<i class="fa-solid fa-images me-2"></i>Bild auswählen
|
||||
</label>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (_isUploading)
|
||||
{
|
||||
<div class="text-center my-3">
|
||||
<div class="spinner-border text-primary spinner-border-sm" role="status">
|
||||
<span class="visually-hidden">Laden...</span>
|
||||
</div>
|
||||
<span class="ms-2">Bilder werden hochgeladen und verarbeitet...</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrEmpty(_message))
|
||||
{
|
||||
<div class="alert alert-@(_isSuccess ? "success" : "danger") alert-dismissible fade show" role="alert">
|
||||
@_message
|
||||
<button type="button" class="btn-close" @onclick="() => _message = null"></button>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (_uploadedCount > 0)
|
||||
{
|
||||
<div class="alert alert-success text-center">
|
||||
<i class="fa-solid fa-check-circle fa-2x mb-2"></i><br/>
|
||||
Vielen Dank für den Upload. Du kannst diese Seite nun schließen.
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
141
FoodsharingSiegen.Server/Pages/UploadVerification.razor.cs
Normal file
141
FoodsharingSiegen.Server/Pages/UploadVerification.razor.cs
Normal file
@@ -0,0 +1,141 @@
|
||||
using FoodsharingSiegen.Contracts.Entity;
|
||||
using FoodsharingSiegen.Contracts.Model;
|
||||
using FoodsharingSiegen.Server.Data.Service;
|
||||
using FoodsharingSiegen.Shared.Helper;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace FoodsharingSiegen.Server.Pages
|
||||
{
|
||||
public partial class UploadVerification : ComponentBase
|
||||
{
|
||||
[Inject]
|
||||
public ProspectService ProspectService { get; set; } = null!;
|
||||
|
||||
[Inject]
|
||||
public IOptions<AppSettings> AppSettings { get; set; } = null!;
|
||||
|
||||
[Parameter]
|
||||
public Guid Token { get; set; }
|
||||
|
||||
private bool _isLoading = true;
|
||||
private Prospect? _prospect;
|
||||
|
||||
private int _uploadedCount = 0;
|
||||
private bool _isUploading = false;
|
||||
|
||||
private string? _message;
|
||||
private bool _isSuccess;
|
||||
|
||||
private const int MaxAllowedFiles = 5;
|
||||
private const long MaxFileSize = 10 * 1024 * 1024; // 10 MB
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadData();
|
||||
}
|
||||
|
||||
private async Task LoadData()
|
||||
{
|
||||
_isLoading = true;
|
||||
|
||||
try
|
||||
{
|
||||
var result = await ProspectService.GetProspectByVerificationTokenAsync(Token);
|
||||
|
||||
if (result.Success && result.Data != null)
|
||||
{
|
||||
_prospect = result.Data;
|
||||
}
|
||||
else
|
||||
{
|
||||
_prospect = null;
|
||||
_message = result.Exception?.Message ?? "Ein Fehler ist aufgetreten.";
|
||||
_isSuccess = false;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_message = $"Fehler: {ex.Message}";
|
||||
_isSuccess = false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnInputFileChange(InputFileChangeEventArgs e)
|
||||
{
|
||||
if (_prospect == null) return;
|
||||
|
||||
_message = null;
|
||||
|
||||
var files = e.GetMultipleFiles(MaxAllowedFiles);
|
||||
|
||||
if (_uploadedCount + files.Count > MaxAllowedFiles)
|
||||
{
|
||||
_message = $"Es sind maximal {MaxAllowedFiles} Bilder erlaubt.";
|
||||
_isSuccess = false;
|
||||
return;
|
||||
}
|
||||
|
||||
_isUploading = true;
|
||||
|
||||
int successCount = 0;
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (file.Size > MaxFileSize)
|
||||
{
|
||||
_message = $"Bild '{file.Name}' überschreitet die erlaubte Größe von 10 MB.";
|
||||
_isSuccess = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Resize the image to a max dimension of 1000 pixels (longest edge) to save DB space
|
||||
var resizedImageFile = await file.RequestImageFileAsync(file.ContentType, 1000, 1000);
|
||||
|
||||
using var stream = resizedImageFile.OpenReadStream(MaxFileSize);
|
||||
using var memoryStream = new MemoryStream();
|
||||
|
||||
await stream.CopyToAsync(memoryStream);
|
||||
|
||||
byte[] imageData = memoryStream.ToArray();
|
||||
|
||||
var saveResult = await ProspectService.AddVerificationImageAsync(_prospect.Id, imageData, resizedImageFile.ContentType);
|
||||
|
||||
if (saveResult.Success)
|
||||
{
|
||||
successCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
_message = $"Fehler beim Speichern von {file.Name}: {saveResult.Exception?.Message}";
|
||||
_isSuccess = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_message = $"Fehler bei {file.Name}: {ex.Message}";
|
||||
_isSuccess = false;
|
||||
}
|
||||
}
|
||||
|
||||
_isUploading = false;
|
||||
|
||||
if (successCount > 0)
|
||||
{
|
||||
_message = $"{successCount} Bild(er) erfolgreich hinzugefügt.";
|
||||
_isSuccess = true;
|
||||
_uploadedCount += successCount;
|
||||
}
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user