FEAT: Implement deceased/givenaway enclosure visibility rules, preserve external clan name, and hide receiver fields for deceased gerbils

This commit is contained in:
2026-06-13 01:40:19 +02:00
parent 5732398e60
commit 396d8f05d8
53 changed files with 7431 additions and 274 deletions

View File

@@ -1,4 +1,5 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using GerbilManagerWebAPI.Models;
using Microsoft.EntityFrameworkCore;
@@ -8,13 +9,18 @@ namespace GerbilManagerWebAPI.Import
{
private readonly ApplicationContext _db;
private readonly string _resolvedJsonPath;
private readonly string _sourceDir;
private readonly string _photoRoot;
public IngestResolvedService(ApplicationContext db, IConfiguration config, IWebHostEnvironment env)
public IngestResolvedService(ApplicationContext db, IConfiguration config, IWebHostEnvironment? env)
{
_db = db;
var sourceDir = config["Import:SourcePath"]
?? Path.GetFullPath(Path.Combine(env.ContentRootPath, "..", "tools", "import", "output"));
_resolvedJsonPath = Path.Combine(sourceDir, "resolved_import.json");
var contentRoot = env?.ContentRootPath ?? Directory.GetCurrentDirectory();
_sourceDir = config["Import:SourcePath"]
?? Path.GetFullPath(Path.Combine(contentRoot, "..", "tools", "import", "output"));
_resolvedJsonPath = Path.Combine(_sourceDir, "resolved_import.json");
_photoRoot = config["Photos:RootPath"]
?? Path.Combine(contentRoot, "photo-storage");
}
public async Task<string> RunAsync()
@@ -76,13 +82,25 @@ namespace GerbilManagerWebAPI.Import
await _db.Litters.ExecuteDeleteAsync();
}
// 2. Import Contacts (Only add new ones to preserve manually entered contacts)
var existingContactIds = await _db.Contacts.Select(c => c.Id).ToHashSetAsync();
// 2. Import Contacts (Add new ones, and update roles/details of existing ones)
var existingContacts = await _db.Contacts.ToDictionaryAsync(c => c.Id);
var addedContactIds = new HashSet<Guid>();
int contactsAdded = 0;
int contactsUpdated = 0;
foreach (var c in data.Contacts)
{
if (!existingContactIds.Contains(c.Id) && addedContactIds.Add(c.Id))
if (existingContacts.TryGetValue(c.Id, out var existingContact))
{
existingContact.Name = c.Name;
existingContact.Email = c.Email;
existingContact.Phone = c.Phone;
existingContact.Address = c.Address;
existingContact.Notes = c.Notes;
existingContact.IsBreeder = c.IsBreeder;
existingContact.IsReceiver = c.IsReceiver;
contactsUpdated++;
}
else if (addedContactIds.Add(c.Id))
{
_db.Contacts.Add(c);
contactsAdded++;
@@ -171,14 +189,45 @@ namespace GerbilManagerWebAPI.Import
}
await _db.SaveChangesAsync();
// 6. Insert all Photos
// 6. Insert all Photos & copy physical files
Directory.CreateDirectory(_photoRoot);
foreach (var p in data.GerbilPhotos)
{
_db.GerbilPhotos.Add(p);
if (!string.IsNullOrEmpty(p.SourcePath))
{
var relPath = p.SourcePath.Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar);
var src = Path.Combine(_sourceDir, relPath);
if (File.Exists(src))
{
var dest = Path.Combine(_photoRoot, p.FileName);
try
{
File.Copy(src, dest, overwrite: true);
}
catch (Exception ex)
{
Console.WriteLine($"Warning: Failed to copy photo {src} to {dest}: {ex.Message}");
}
}
else
{
Console.WriteLine($"Warning: Source photo file not found at {src}");
}
}
_db.GerbilPhotos.Add(new GerbilPhoto
{
Id = p.Id,
GerbilId = p.GerbilId,
FileName = p.FileName,
Caption = p.Caption,
SortOrder = p.SortOrder,
CreatedAt = DateTimeOffset.UtcNow
});
}
await _db.SaveChangesAsync();
return $"Ingestion successful! Imported {contactsAdded} contacts, {data.Litters.Count} litters, {data.Gerbils.Count} gerbils, and {data.GerbilPhotos.Count} photos.";
return $"Ingestion successful! Imported {contactsAdded} contacts ({contactsUpdated} updated), {data.Litters.Count} litters, {data.Gerbils.Count} gerbils, and {data.GerbilPhotos.Count} photos.";
}
}
@@ -187,6 +236,18 @@ namespace GerbilManagerWebAPI.Import
public List<Contact> Contacts { get; set; } = new();
public List<Litter> Litters { get; set; } = new();
public List<Gerbil> Gerbils { get; set; } = new();
public List<GerbilPhoto> GerbilPhotos { get; set; } = new();
public List<ResolvedPhoto> GerbilPhotos { get; set; } = new();
}
public class ResolvedPhoto
{
public Guid Id { get; set; }
public Guid GerbilId { get; set; }
public required string FileName { get; set; }
public string? Caption { get; set; }
public int SortOrder { get; set; }
[JsonPropertyName("_source_path")]
public string? SourcePath { get; set; }
}
}