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

This commit is contained in:
a.beging@eas-solutions.de
2026-04-20 15:54:17 +02:00
parent a93de45270
commit b3212acf6d
15 changed files with 1043 additions and 6 deletions

View File

@@ -27,6 +27,11 @@ namespace FoodsharingSiegen.Server.Data
/// </summary>
public DbSet<Prospect>? Prospects { get; set; }
/// <summary>
/// Gets or sets the uploaded verification images mapping.
/// </summary>
public DbSet<ProspectImage>? ProspectImages { get; set; }
/// <summary>
/// Gets or sets the value of the users (ab)
/// </summary>

View File

@@ -111,7 +111,10 @@ namespace FoodsharingSiegen.Server.Data.Service
{
try
{
var prospectsQuery = Context.Prospects!.AsNoTracking().Include(x => x.Interactions.OrderBy(i => i.Date)).ThenInclude(x => x.User).OrderBy(x => x.Name).AsQueryable();
var prospectsQuery = Context.Prospects!.AsNoTracking()
.Include(x => x.Images)
.Include(x => x.Interactions.OrderBy(i => i.Date)).ThenInclude(x => x.User)
.OrderBy(x => x.Name).AsQueryable();
if(parameter.MustHaveInteractions != null && parameter.MustHaveInteractions.Any())
prospectsQuery = prospectsQuery.Where(x => x.Interactions.Any(i => parameter.MustHaveInteractions.Contains(i.Type)));
@@ -206,5 +209,138 @@ namespace FoodsharingSiegen.Server.Data.Service
}
#endregion
#region Image Upload Features
public async Task<OperationResult<Guid>> GenerateVerificationTokenAsync(Guid prospectId)
{
try
{
var prospect = await Context.Prospects!
.Include(x => x.Interactions)
.FirstOrDefaultAsync(x => x.Id == prospectId);
if (prospect == null) return new(new Exception("Prospect not found"));
if (prospect.Interactions.Any(x => x.Type == InteractionType.Verify))
return new(new Exception("Die Identitätsprüfung wurde bereits abgeschlossen. Es kann kein neuer Token generiert werden."));
prospect.VerificationToken = Guid.NewGuid();
prospect.Modified = DateTime.UtcNow;
await Context.SaveChangesAsync();
return new(prospect.VerificationToken.Value);
}
catch (Exception e)
{
return new(e);
}
}
public async Task<OperationResult<Prospect>> GetProspectByVerificationTokenAsync(Guid token)
{
try
{
var prospect = await Context.Prospects!
.Include(x => x.Interactions)
.AsNoTracking()
.FirstOrDefaultAsync(x => x.VerificationToken == token);
if (prospect == null) return new(new Exception("Ungültiger oder abgelaufener Token."));
if (prospect.Interactions.Any(x => x.Type == InteractionType.Verify))
return new(new Exception("Die Identitätsprüfung wurde bereits abgeschlossen. Ein Hochladen weiterer Bilder ist nicht mehr möglich."));
return new(prospect);
}
catch (Exception e)
{
return new(e);
}
}
public async Task<OperationResult> AddVerificationImageAsync(Guid prospectId, byte[] imageData, string contentType)
{
try
{
var prospect = await Context.Prospects!
.Include(x => x.Interactions)
.AsNoTracking()
.FirstOrDefaultAsync(x => x.Id == prospectId);
if (prospect == null) return new(new Exception("Foodsaver nicht gefunden."));
if (prospect.Interactions.Any(x => x.Type == InteractionType.Verify))
return new(new Exception("Die Identitätsprüfung wurde bereits abgeschlossen. Ein Hochladen weiterer Bilder ist nicht mehr möglich."));
// Verify max 5 images
var imgCount = await Context.ProspectImages!.CountAsync(x => x.ProspectId == prospectId);
if (imgCount >= 5) return new(new Exception("Maximum 5 images allowed"));
var image = new ProspectImage
{
Id = Guid.NewGuid(),
ProspectId = prospectId,
ImageData = imageData,
ContentType = contentType,
Created = DateTime.UtcNow
};
await Context.ProspectImages!.AddAsync(image);
await Context.SaveChangesAsync();
return new();
}
catch (Exception e)
{
return new(e);
}
}
public async Task<OperationResult<List<ProspectImage>>> GetVerificationImagesAsync(Guid prospectId)
{
try
{
var images = await Context.ProspectImages!
.AsNoTracking()
.Where(x => x.ProspectId == prospectId)
.OrderBy(x => x.Created)
.ToListAsync();
return new(images);
}
catch (Exception e)
{
return new(e);
}
}
public async Task<OperationResult> DeleteVerificationImagesAsync(Guid prospectId)
{
try
{
var images = await Context.ProspectImages!.Where(x => x.ProspectId == prospectId).ToListAsync();
if (images.Any())
{
Context.ProspectImages!.RemoveRange(images);
var prospect = await Context.Prospects!.FirstOrDefaultAsync(x => x.Id == prospectId);
if (prospect != null)
{
prospect.VerificationToken = null; // Clear token when images are deleted
}
await Context.SaveChangesAsync();
}
return new();
}
catch (Exception e)
{
return new(e);
}
}
#endregion
}
}